The relational model organizes data into tables, relates rows through keys, and enforces constraints at the database boundary. SQL describes the result you want; the optimizer chooses scans, seeks, join order, and physical operators that preserve that result. Relational databases are the default when integrity constraints, multi-row transactions, and ad-hoc joins matter more than storing one access pattern in its final read shape.

Relational Boundary

Keep data relational when the database must reject invalid relationships, commit several row changes atomically, or support new query combinations without rebuilding the storage model. Denormalization can remove expensive joins from a hot path, but it creates duplicate state and a write-side consistency obligation; normalization and denormalization owns that decision.

Query Processing and Joins

SQL has a logical meaning and a separate physical plan. The common logical order is FROM/JOINWHEREGROUP BYHAVINGSELECTORDER BYLIMIT/TOP. The optimizer may push predicates or reorder joins physically only when duplicates, NULL, and the final result remain equivalent.

SELECT department, COUNT(*) AS headcount
FROM employees
WHERE hire_date >= DATE '2024-01-01'
GROUP BY department
HAVING COUNT(*) > 5
ORDER BY headcount DESC;

WHERE cannot see headcount because projection happens later; ORDER BY generally can. Alias visibility is dialect-specific: PostgreSQL permits a simple output alias in GROUP BY, while SQL Server requires the original expression. Portable SQL repeats the grouped or aggregate expression.

graph LR
    P1["Parse syntax to tree"] --> P2["Bind names and types"] --> P3["Optimize candidate plans"] --> P4["Execute physical operators"] --> P5["Read pages and indexes"]

Cardinality estimates connect the optimizer to storage. If a predicate is estimated at 10 rows but returns 1,000,000, a nested loop or join order that looked cheap can spill or repeat millions of probes. The SQL remains correct while the plan is expensive.

Join Semantics

Assume customers contains Ada and Lin, while orders contains two rows for Ada and none for Lin. A left join returns Ada twice and extends Lin’s missing order columns with NULL; joins do not deduplicate.

SELECT c.name, o.total
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
ORDER BY c.id, o.total;
name | total
Ada  | 40
Ada  | 70
Lin  | NULL

Putting o.total >= 50 in ON preserves Lin as an unmatched left row. Putting it in WHERE removes Lin because NULL >= 50 is unknown.

Physical joinStrong fitCost to watch
Nested loopSmall outer input with indexed inner probesRepeated inner work when estimates are wrong
Hash joinLarge equality joins with enough memoryBuild memory and spills
Merge joinInputs already ordered on the join keySorting when order is absent

No join operator is universally fastest. Row counts, ordering, widths, indexes, memory, and cache state determine the plan.

Transactions and Scale

Database locks and MVCC enforce isolation inside one database; the same note contrasts pessimistic locks with optimistic version predicates for stale application writes. Replication copies data for availability and read scale, while sharding partitions ownership when one primary can no longer handle the write or storage load.

Questions

References

13 items under this folder.