A language tour
Say what you want, not how to find it. A fifty-year-old language that still runs the world's data.
01 — Philosophy
Most languages tell a computer what to do, step by step. SQL tells it what you want, and lets the query optimizer decide how to get there. You describe the shape of the result; the database figures out the joins, the indexes, the execution plan. This is the declarative model — and it's remarkably powerful.
"SQL is the closest thing we have to a universal language for asking questions about structured data. It has survived every wave of the database wars because the idea behind it is simply correct."
— Edgar F. Codd's relational model, first described in 1970-- You describe the *shape* of what you want. -- The database decides how to retrieve it. SELECT c.name, o.order_date, o.total FROM customers c JOIN orders o ON o.customer_id = c.id WHERE o.total > 1000 AND o.order_date >= DATE_TRUNC('month', CURRENT_DATE - INTERVAL '1 month') AND o.order_date < DATE_TRUNC('month', CURRENT_DATE) ORDER BY o.total DESC; -- No loop. No index management. No iteration logic. -- The planner chooses the index, the join strategy, the sort method. -- You only said: "give me this month's big orders, newest first."
The query optimizer may use a B-tree index, a hash join, or a sequential scan — you wrote none of that. You wrote a question, and the database answered it.
02 — The Relational Model
Relational databases store each fact once. JOINs are the mechanism for bringing related facts together at query time. Rather than duplicating data — or nesting it in documents — the relational model normalises it, then composes it on demand. A three-way join that would take pages of code in another language is a single, readable query in SQL.
-- Three tables. One question: -- "Which UK authors wrote which books in which genres?" SELECT a.name AS author, b.title AS book, g.name AS genre, b.published_year FROM authors a INNER JOIN books b ON b.author_id = a.id INNER JOIN genres g ON g.id = b.genre_id WHERE a.country = 'UK' ORDER BY a.name, b.published_year; -- LEFT JOIN: include authors with no books yet SELECT a.name, COUNT(b.id) AS book_count FROM authors a LEFT JOIN books b ON b.author_id = a.id GROUP BY a.name ORDER BY book_count DESC;
LEFT JOIN includes rows from the left table even when no match exists on the right — authors with zero books appear with a count of 0, not silently dropped.
03 — Window Functions
A regular GROUP BY collapses many rows into one. Window functions are different — they compute across a set of rows but keep every row intact. Running totals, rankings, moving averages: all expressed through the elegant OVER() clause, which defines the window of rows each calculation sees.
-- Running total and rank — per category, not globally SELECT product_name, category, revenue, -- OVER() defines the window: "within each category, ordered by revenue" SUM(revenue) OVER ( PARTITION BY category ORDER BY revenue DESC ) AS running_total, RANK() OVER ( PARTITION BY category ORDER BY revenue DESC ) AS rank_in_category, -- Look one row back — no self-join needed LAG(revenue) OVER ( PARTITION BY category ORDER BY revenue DESC ) AS prev_revenue FROM product_sales ORDER BY category, rank_in_category;
PARTITION BY resets the window per category — so RANK() = 1 is the top product in each category, not globally. Every source row is preserved.
04 — Common Table Expressions
A Common Table Expression — the WITH clause — lets you name an intermediate result and reference it like a table. Large queries stop being single monolithic expressions and become a sequence of named, readable steps. Chain them together, and even recursive problems like tree traversal become approachable.
-- Month-over-month revenue growth — built in readable steps WITH monthly AS ( -- Step 1: aggregate orders by month SELECT DATE_TRUNC('month', order_date) AS month, SUM(total) AS revenue FROM orders GROUP BY 1 ), with_prev AS ( -- Step 2: pull in the previous month's revenue SELECT month, revenue, LAG(revenue) OVER (ORDER BY month) AS prev_revenue FROM monthly ) -- Step 3: compute the percentage change SELECT TO_CHAR(month, 'YYYY-MM') AS month, revenue, ROUND( 100.0 * (revenue - prev_revenue) / NULLIF(prev_revenue, 0), 1) AS growth_pct FROM with_prev WHERE prev_revenue IS NOT NULL ORDER BY month;
NULLIF(prev_revenue, 0) prevents a division-by-zero without an explicit CASE expression — a small example of SQL rewarding careful thought about edge cases.
05 — Transactions & ACID
SQL databases don't just store data — they make promises about it. ACID transactions guarantee that a group of operations either all succeed or none of them do. There is no state in which the money has left Alice's account but not arrived in Bob's. This guarantee, taken for granted by most applications, is one of the most important properties in all of computing.
"Atomicity, Consistency, Isolation, Durability. Four words that mean: your data will not lie to you."
— the ACID guarantee, formalised by Jim Gray and Andreas Reuter-- A bank transfer: both updates happen, or neither does. BEGIN; UPDATE accounts SET balance = balance - 500 WHERE id = 'alice'; -- If this raises an error, the whole transaction is rolled back. -- Alice's balance stays unchanged. No money disappears. UPDATE accounts SET balance = balance + 500 WHERE id = 'bob'; COMMIT; -- Durability: once COMMIT returns, the data is on disk. -- A power failure after this point loses nothing. -- SAVEPOINT lets you nest rollback points within a transaction BEGIN; INSERT INTO audit_log (event) VALUES ('transfer_started'); SAVEPOINT before_debit; UPDATE accounts SET balance = balance - 500 WHERE id = 'alice'; -- can ROLLBACK TO before_debit without losing the audit log entry COMMIT;
The COMMIT on the last line is a hard promise — the database has flushed to durable storage. Without it, a crash mid-transaction leaves both accounts untouched.
06 — The Whole Picture
NOT NULL, UNIQUE, CHECK, FOREIGN KEY — the database enforces your data rules even when your application code forgets to. The schema is the contract.
Hierarchies — org charts, bill of materials, file systems — are naturally expressed with WITH RECURSIVE. Tree traversal without writing a tree traversal algorithm.
B-trees, hash indexes, partial indexes, expression indexes. The planner chooses automatically — but understanding them turns a 10-second query into a 10-millisecond one.
SQL uses three-valued logic: true, false, and unknown. NULL means "no value" — it propagates through expressions and requires explicit IS NULL checks. Misunderstood but precise.
EXISTS, IN, scalar subqueries, lateral joins — SQL composes queries within queries, giving you the full power of relational algebra in a single statement.
SQL was formalised in 1974. It has outlived every "SQL killer" that came for it. NewSQL, NoSQL, graph databases — most of them eventually added a SQL interface.