Index ยท How it works
2 min readHow it works
The relational model
- Table (relation) โ a set of rows. Row (tuple) โ one record. Column (attribute) โ a typed field.
- Primary key (PK) โ uniquely identifies a row; not null, unique. Often a surrogate
id(auto-increment /bigint/uuid) rather than a natural key. - Foreign key (FK) โ a column that references another table's PK; the database enforces referential integrity (you can't reference a row that doesn't exist, and
ON DELETEdecides what happens to children). - Constraints โ
NOT NULL,UNIQUE,CHECK,DEFAULTโ push invariants into the database so bad data can't get in regardless of which app writes it. - Schema โ the set of table/column/constraint definitions (DDL). Changing it = a migration.
ACID โ what a transaction guarantees
A transaction groups statements so they succeed or fail as a unit (BEGIN โฆ COMMIT / ROLLBACK).
- Atomicity โ all-or-nothing; a failure rolls the whole thing back.
- Consistency โ constraints hold before and after; the DB never persists a state that violates them.
- Isolation โ concurrent transactions don't step on each other (tunable, see isolation levels below).
- Durability โ once committed, it survives a crash (write-ahead log / fsync).
The classic example: transferring money โ debit one account and credit another must both happen or neither.
SQL in one breath
-- DDL (structure)
CREATE TABLE orders (
id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
customer_id bigint NOT NULL REFERENCES customers(id),
total numeric(10,2) NOT NULL CHECK (total >= 0),
status text NOT NULL DEFAULT 'pending',
created_at timestamptz NOT NULL DEFAULT now()
);
-- DML (data)
SELECT c.name, COUNT(*) AS order_count, SUM(o.total) AS revenue
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.created_at >= '2026-01-01'
GROUP BY c.name
HAVING SUM(o.total) > 1000
ORDER BY revenue DESC
LIMIT 20;
Logical evaluation order is not the written order โ it's roughly FROM โ JOIN โ WHERE โ GROUP BY โ HAVING โ SELECT โ ORDER BY โ LIMIT. That's why you can't use a SELECT alias in WHERE (the alias doesn't exist yet) but you can in ORDER BY.
Joins
- INNER JOIN โ only rows matching in both tables.
- LEFT (OUTER) JOIN โ all left rows; right side is
NULLwhen there's no match (use to find "customers with no orders":LEFT JOIN โฆ WHERE o.id IS NULL). - CROSS JOIN โ Cartesian product. Usually accidental (a forgotten join condition) and a performance disaster โ
n ร mrows.
Normalization vs denormalization
Normalization removes redundancy so each fact lives in exactly one place:
- 1NF โ atomic columns, no repeating groups (no comma-separated lists in a cell).
- 2NF โ no partial dependency on part of a composite key.
- 3NF โ no transitive dependency (non-key columns depend only on the key, "the key, the whole key, and nothing but the key").
Normalized schemas avoid update anomalies (change a customer's name once, not in 10 000 order rows). Denormalization deliberately re-introduces redundancy (a cached order_count, a duplicated column) to avoid expensive joins on read-heavy paths โ at the cost of having to keep the copies in sync. Normalize first; denormalize as a targeted optimization with evidence.