What is PostgreSQL? Basic
A database stores data permanently and lets you query it quickly. PostgreSQL (often called "Postgres") is a relational database. Data lives in tables made of rows and columns, and tables are linked through keys.
| id | name | city | |
|---|---|---|---|
| 1 | Asha | asha@mail.com | Pune |
| 2 | Rahul | rahul@mail.com | Delhi |
| 3 | Meera | meera@mail.com | Pune |
Primary key = unique ID for each row · highlighted = one row (record) · each header = a column (field)
Why Postgres?
Reliable (ACID)
Your data is safe even if the server crashes mid-write.
Feature-rich
JSONB, arrays, full-text search, window functions, extensions such as PostGIS and pgvector.
Free & open source
Used by Instagram, Reddit and Apple, and hosted by Neon, Supabase, RDS and others.
Kinds of SQL commands
| Group | Purpose | Commands |
|---|---|---|
| DDL Definition | Define structure | CREATE ALTER DROP TRUNCATE |
| DML Manipulation | Change data | INSERT UPDATE DELETE |
| DQL Query | Read data | SELECT |
| DCL Control | Permissions | GRANT REVOKE |
| TCL Transaction | Group changes | BEGIN COMMIT ROLLBACK |
Install & the psql shell Basic
Windows / macOS
Download the installer from postgresql.org. It includes pgAdmin, a GUI tool.
Docker (recommended)
One command, and it's easy to reset.
Cloud
Neon or Supabase give you a free hosted Postgres with a connection URL.
psql meta-commands
\llist databases\c dbnameconnect\dtlist tables\d usersdescribe table\dulist roles\xexpanded output\i file.sqlrun a file\qquit
GUI tools
- pgAdminofficial GUI
- DBeaverfree, multi-DB
- TablePlusfast & clean
- VS CodePostgreSQL extension
;. Keywords are case-insensitive, but people usually write them in UPPERCASE. Use 'single quotes' for text and "double quotes" for identifiers. Comments start with --.Data types Basic
| Category | Type | Use for | Example |
|---|---|---|---|
| Numbers | INT / BIGINT | Whole numbers | 42 |
SERIAL / GENERATED … AS IDENTITY | Auto-increment IDs | 1, 2, 3… | |
NUMERIC(10,2) | Money and exact decimals | 1999.99 | |
REAL / DOUBLE PRECISION | Scientific values (approximate) | 3.14159 | |
| Text | VARCHAR(n) | Text with a max length | 'Asha' |
TEXT | Any length | blog body | |
| Boolean | BOOLEAN | true/false | TRUE |
| Date/time | DATE | Calendar date | '2026-10-06' |
TIMESTAMPTZ | Date + time with time zone (preferred) | NOW() | |
INTERVAL | A duration | '7 days' | |
| IDs | UUID | Globally unique IDs | gen_random_uuid() |
| Structured | JSONB | Flexible JSON documents | '{"color":"red"}' |
TEXT[] | Arrays | '{a,b,c}' |
REAL/FLOAT, because 0.1 + 0.2 ≠ 0.3. Use NUMERIC(12,2), or an INT holding paise/cents.Creating tables & constraints Basic
Constraints are rules the database enforces, so bad data can't get in, whichever app writes it.
PRIMARY KEY
Unique + not null. Identifies each row.
FOREIGN KEY
Must match a row in another table (REFERENCES).
NOT NULL
A value is required.
UNIQUE
No duplicates allowed in this column.
CHECK
A custom condition, e.g. price >= 0.
DEFAULT
The value used when none is given.
ON DELETE options
| Option | When the parent user is deleted… |
|---|---|
CASCADE | Their posts are deleted too |
SET NULL | posts.user_id becomes NULL |
RESTRICT / default | The delete is blocked while posts exist |
Changing a table
CRUD: Insert, Select, Update, Delete Basic
Create
INSERT
POST
Read
SELECT
GET
Update
UPDATE
PATCH/PUT
Delete
DELETE
DELETE
DELETE FROM users; deletes every row. To be safe, run the matching SELECT … WHERE first, or wrap the change in BEGIN; … ROLLBACK; to test it.Upsert (insert or update)
Filtering, sorting & limits Basic
That's why you can't use a SELECT alias inside WHERE: WHERE runs before SELECT.
| Operator | Meaning |
|---|---|
= <> != < > <= >= | Comparison |
AND OR NOT | Combine conditions (use brackets!) |
LIKE 'a%' / ILIKE | Pattern (case-sensitive / insensitive) |
IN (…), BETWEEN a AND b | List, range |
IS NULL, COALESCE(x, 'default') | Handle missing values |
Which query finds users with no phone number?
Aggregates & GROUP BY Intermediate
Aggregate functions turn many rows into one value: COUNT, SUM, AVG, MIN and MAX. GROUP BY makes one group per distinct value, and the aggregate runs once per group.
| name | city |
|---|---|
| Asha | Pune |
| Rahul | Delhi |
| Meera | Pune |
| Vikram | Pune |
| Neha | Delhi |
| city | count |
|---|---|
| Pune | 3 |
| Delhi | 2 |
WHERE
- Filters individual rows
- Runs before GROUP BY
- Can't use aggregates
HAVING
- Filters groups
- Runs after GROUP BY
- Uses aggregates:
HAVING SUM(x) > 100
Relationships & keys Intermediate
One-to-One
user ↔ profile. FK + UNIQUE on the child.
One-to-Many
user → orders. The FK goes on the "many" side (orders.user_id).
Many-to-Many
orders ↔ products. Needs a junction table with two FKs.
JOINs Intermediate
A JOIN combines rows from two tables using a matching column, usually FK = PK. A = users (left), B = orders (right).
| id | name |
|---|---|
| 1 | Asha |
| 2 | Rahul |
| 3 | Meera |
| id | user_id | total |
|---|---|---|
| 10 | 1 | 500 |
| 11 | 1 | 300 |
| 12 | 2 | 900 |
| name | order | total |
|---|---|---|
| Asha | 10 | 500 |
| Asha | 11 | 300 |
| Rahul | 12 | 900 |
| Meera | NULL | NULL |
Meera has no orders. A LEFT JOIN keeps her with NULLs; an INNER JOIN would drop her.
You need ALL products, including ones never ordered. Which join from products to order_items?
Subqueries & CTEs Intermediate
A subquery is a query inside another query. A CTE (WITH) gives a subquery a name, so complex SQL reads top-to-bottom like steps.
Normalization (table design) Intermediate
Normalization means storing each fact in one place, so updates can't leave copies that disagree.
| id | customer | cust_email | products |
|---|---|---|---|
| 1 | Asha | asha@m.com | Mouse, Pad |
| 2 | Asha | asha@m.com | Keyboard |
| id | name | |
|---|---|---|
| 1 | Asha | asha@m.com |
| id | user_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| Form | Rule | Fix |
|---|---|---|
| 1NF | One value per cell, no repeating groups | "Mouse, Pad" → separate rows in order_items |
| 2NF | Every column depends on the whole key | Product name moves to products, not order_items |
| 3NF | No column depends on another non-key column | customer_email moves to users |
order_items.unit_price stores the price at purchase time. That's a deliberate choice.Indexes Advanced
A lookup takes a few hops (O(log n)) instead of scanning millions of rows.
Index when…
- Column is used in WHERE / JOIN / ORDER BY
- Foreign keys
- High selectivity (many distinct values)
Avoid when…
- Tiny tables
- Write-heavy tables (each index slows INSERT/UPDATE)
- Low selectivity (e.g. a boolean)
EXPLAIN ANALYZE & tuning Advanced
| Plan node | Meaning |
|---|---|
Seq Scan | Reads the whole table. Fine for small tables, a red flag for big ones. |
Index Scan / Index Only Scan | Uses an index (best) |
Bitmap Heap Scan | An index finds many rows, which are then fetched in batches |
Nested Loop / Hash Join / Merge Join | Join strategies |
WHERE LOWER(email) = … doesn't use a plain index on email; create an expression index instead. A leading wildcard like LIKE '%abc' can't use a B-tree. Run ANALYZE after big imports so the planner has fresh statistics.Transactions & ACID Advanced
Atomic
All or nothing
Consistent
Constraints always hold
Isolated
Concurrent transactions don't interfere
Durable
Committed data survives a crash
| Isolation level | Prevents | Note |
|---|---|---|
READ COMMITTED | Dirty reads | Postgres default |
REPEATABLE READ | + non-repeatable reads | A consistent snapshot for the whole transaction |
SERIALIZABLE | + serialization anomalies | Safest. Be ready to retry on error 40001. |
VACUUM (usually run by autovacuum) cleans up old row versions.Window functions Advanced
Like GROUP BY, but rows are not collapsed. Each row keeps its detail and also gets a value calculated over a "window" of related rows.
| name | dept | salary | rank |
|---|---|---|---|
| Asha | Eng | 150k | 1 |
| Rahul | Eng | 120k | 2 |
| Dev | Eng | 120k | 2 |
| Meera | Sales | 90k | 1 |
| Neha | Sales | 70k | 2 |
Ranking restarts in each partition (dept). Every row is still there.
Views, functions & triggers Advanced
View
A saved query you can use like a table. It always shows live data.
Materialized view
Stores the result. Very fast to read, but you must REFRESH it.
Trigger
Runs a function automatically on INSERT, UPDATE or DELETE.
JSONB, arrays & full-text search Advanced
JSONB stores JSON in a binary format that can be indexed. Use it for flexible attributes (product specs, settings). Keep core fields in normal columns.
Roles, security & backups Advanced
Interview questions
COUNT, SUM and the other aggregates.SELECT DISTINCT salary FROM employees ORDER BY salary DESC LIMIT 1 OFFSET 1; You can also use DENSE_RANK() = 2 in a subquery.Practice exercises
- Design a schemaLibrary system: books, authors (many-to-many), members and loans.
- Write queriesTop 5 customers by spend, products never ordered, monthly revenue growth using LAG.
- OptimizeInsert 1M rows with
generate_series, compare EXPLAIN ANALYZE before and after adding indexes.
Cheat sheet
Structure
CREATE TABLEnew tableALTER TABLE … ADD COLUMNchangeREFERENCES t(id)foreign keyCREATE INDEXspeed up
Data
INSERT … RETURNINGcreateON CONFLICT DO UPDATEupsertUPDATE … WHEREupdateDELETE … WHEREdelete
Query
JOIN … ONcombineGROUP BY / HAVINGaggregateWITH x AS (…)CTEOVER (PARTITION BY)window
Ops
EXPLAIN ANALYZEprofileBEGIN / COMMITtransactionpg_dumpbackupVACUUM ANALYZEmaintenance