Curriculum
Seven tracks. Every lesson runs against a seeded database, and every claim in the prose is a query you can edit and re-run without leaving the page.
Foundations
7 lessons · 10 drillsThe parts everyone thinks they know. Three-valued logic, what a join does with the rows that found no partner, and why COUNT(*) and COUNT(col) are different numbers.
- 1SELECT, and what a column really is8 min
A select list is a list of expressions, not a list of columns. Once that lands, half of SQL stops being a special case.
- 2NULL, and the third truth value14 min
NULL is not a value. It is the absence of one, and every comparison it touches returns neither true nor false. Half of all real SQL bugs live here.
- 3Joins, as sets and as loops12 min
A join is a nested loop with a condition. Every join type is a rule about what to do with the rows that found no partner.
- 4Grouping, and the two ways to count12 min
COUNT(*) counts rows. COUNT(col) counts values. They are different numbers, and knowing which one you asked for is most of the skill.
- 5Subqueries, and when they are secretly a loop12 min
A subquery that mentions the outer query runs once per outer row. One that does not runs once. The difference does not show in the SQL.
- 6Set operations, and what they cost8 min
UNION removes duplicates and UNION ALL does not — which is not a stylistic difference. One of them has to remember every row it has already emitted.
- 7The order clauses actually run in10 min
Written in one order, evaluated in another. Almost every “why can’t I use my alias there” question is answered by this, and so are several performance ones.
Modelling
2 lessons · 1 drillConstraints as the one guarantee every application inherits, and what a foreign key costs on the write path. Then the JSONB column every generated schema has, and what the planner cannot see inside it.
- 1Constraints are correctness, not decoration12 min
A constraint is the one guarantee that survives every bug in every application that will ever talk to this database. It also has a cost, and the cost is on the write path.
- 2What a data JSONB column costs14 min
Every AI-written schema has one. It is not free, it is not indexed, and the planner has no idea what is inside it — and the plan will say all three out loud.
Indexes
3 lessons · 4 drillsSelectivity, composite column order and the left-prefix rule, covering indexes, partial and expression indexes, and the corollary that a function around a column throws the index away.
- 1What an index is, and what it costs12 min
A B-tree is a sorted structure with a real height. Reading it is cheap; going back to the table for each row it found is not, and that is the whole story.
- 2Column order, and the index that answers on its own12 min
An index on (a, b) can seek on a, and an index on (b, a) cannot. And an index that carries every column the query wants never touches the table.
- 3Functions, and the index that only holds some rows10 min
Wrapping a column in a function throws its index away, because the index stores the column and you asked about something else.
The planner
3 lessons · 3 drillsEstimated against actual. Where the estimate comes from and how it goes stale after ANALYZE. The three join algorithms, and when each one wins.
- 1Reading a plan14 min
A plan is a tree of numbers. Estimated against actual is one column, and it localises more slow queries than everything else put together.
- 2Where the estimate comes from12 min
ANALYZE takes a snapshot. Everything the planner knows is from that moment, and the gap between it and now is where bad plans come from.
- 3The three join algorithms12 min
Nested loop, hash and merge. Each wins somewhere, and the plan tells you which one you got and what it cost.
Transactions
3 lessons · 0 drillsWhat a transaction guarantees and what it does not. The four anomalies and which isolation level permits each. Deadlock as a wait-for graph, and the bloat a long transaction leaves behind.
- 1What a transaction actually guarantees12 min
Not that nothing else happens. Only that your work lands all at once or not at all — and that what you read stays still for as long as your isolation level says it does.
- 2The four anomalies, and what each level costs16 min
Dirty read, non-repeatable read, phantom, and write skew. Postgres never permits the first, permits the next two at READ COMMITTED, and refuses all four at SERIALIZABLE.
- 3Locks, deadlock, and what a long transaction leaves behind14 min
Writers block writers. Two of them waiting for each other is a deadlock, and the fix is lock ordering. And any open transaction stops the whole table from being cleaned up.
Production
4 lessons · 2 drillsThe N+1 from the database side. Keyset against offset pagination, measured. Migrations that lock and the patterns that do not, and EXPLAIN triage as a procedure.
- 1The N+1, from the database side12 min
One query for the list and one for each row. From the application it looks like a loop; from the database it looks like a thousand identical statements, and the plan shows you the loops.
- 2Pagination, at page 1 and at page 20,00012 min
OFFSET is not a seek. It reads every row it skips, so page 20,000 costs twenty thousand pages more than page 1 — and the last page is the slowest thing your app does.
- 3Migrations that lock, and the ones that do not12 min
Most of what makes a migration dangerous is which lock it takes and for how long. A few of them take a lock that stops every read on the table, and they look identical in the file.
- 4EXPLAIN triage, as a procedure10 min
It is three in the morning and something is slow. Here is the order to look in, and what each answer rules out.
Generated SQL
1 lesson · 1 drillThe thesis track. Six shapes that make a correct generated query slow — a function around an indexed column, OFFSET pagination, an IN list of thousands, SELECT * in a hot path, a correlated subquery per row, ORDER BY random() — and how to read each one before it runs.
- 1A query an agent wrote14 min
It compiles, it returns the right rows on your laptop, and it passes review because the reviewer read the SQL and not the plan. The thesis track.
23 lessons written so far. The topics still to come are listed in the repository’s handoff document rather than stubbed here — a thin lesson is worse than an honest gap.