Topic 16

Why JOINs Exist

SQL

Chapter 2 taught you a discipline: split the data across tables and point with ids. Store the film once in films, the customer once in customers, and let a booking carry nothing but pointers. The reward was data that cannot drift apart. The bill arrives now — because every interesting answer at the Marquee lives in two or three tables at once. A booking row says customer_id 214, screening_id 88; what Lora wants to read is "Anna, Night Bus, Friday 20:00, seat G7".

JOIN is the operation that pays that bill: it follows the pointers between tables and reassembles the split facts into one readable answer. This page shows you the problem at full size and lets you solve it once by hand, so that when the syntax arrives on the next page it feels like a convenience, not a mystery.

The Problem, Concretely

Take a question you could already write after Chapter 3: who has booked seats for screening 88, the Friday 20:00 show? It is a plain SELECT with a WHERE, asking the bookings table for every row whose screening_id is 88.

A correct query with an unreadable answer
SELECT * FROM bookings WHERE screening_id = 88;

The database answers instantly, and the answer is entirely correct. It is also useless to a human being. Here is what comes back:

booking_idcustomer_idscreening_idseatbooked_at
50121488G72026-05-12 18:04
50221588G82026-05-12 19:31
50321688G92026-05-13 09:12

Three bookings, and not one name, not one film title, not one showtime. Ids are for machines; answers are for people. What Lora actually needs on her screen is something like this — the same three facts, reassembled into sentences:

nametitlestarts_atseat
AnnaNight Bus2026-05-15 20:00G7
BenNight Bus2026-05-15 20:00G8
ChloeNight Bus2026-05-15 20:00G9

Following the Pointer by Hand

Before any new syntax, do the lookup yourself, once. Take the first booking row. It says customer_id 214 — so open the customers table, find the row whose customer_id is 214, and there she is: Anna. The booking also says screening_id 88 — so open screenings, find the row whose screening_id is 88, and read it: screen 1, Friday 20:00, film_id 3. That row holds a pointer of its own, so one more flip into films turns 3 into Night Bus. Assemble the pieces and you have the sentence Lora wanted.

Notice how mechanical that was. At every step you did the same thing: took an id from one table and found the row with the matching id in another. No judgment, no interpretation — just matching values. Anything that mechanical is exactly what a database is good at.

If the back-and-forth felt familiar, it should: it is how footnotes work in a book. The superscript number in the text is small on purpose — it doesn't repeat the source, it points at the bibliography, where the full details wait. Reading a footnoted page means flipping between the two. The ids in bookings are the superscripts, the other tables are the bibliography, and JOIN is the flipping, automated. That is the whole idea; from here on we can drop the book and say it plainly.

The manual trace — one booking, followed pointer by pointer
bookings row 501customer_id 214 · screening_id 88 · G7
customers, id 214Anna
screenings, id 88Fri 20:00 · film_id 3 → Night Bus
The sentenceAnna · Night Bus · Fri 20:00 · G7

JOIN Is That Lookup, Declared

Here is the preview, in one sentence: in SQL you write JOIN, name the second table, and state the matching rule after the word ON — and the database performs the lookup you just did by hand, for every row at once. The full anatomy of that sentence is the next page's entire job; for now it is enough to know that the flip you performed has a name and costs one line.

One thing JOIN does not do deserves saying early, because it worries beginners: joining changes nothing in the stored tables. bookings keeps its ids, customers keeps its names, and the combined, readable rows exist only in the answer — the same way a SELECT with arithmetic in Chapter 3 computed values without storing them. You can join all day and the tables will not have moved.

Why This Design Still Wins

It is fair to ask the skeptical question: if reading the data back requires reassembly, was splitting it up a mistake? Consider the alternative honestly. Suppose bookings carried the customer's name and the film's title in every row. The day a title is corrected — Night Bus was entered as Nightbus, say — someone must now fix it in every booking that mentions it, and the one row they miss becomes a quiet lie. Chapter 2 showed you this disease: one fact stored twice will eventually disagree with itself.

So the trade is this: splitting protects writes — every fact has exactly one home, so it can never contradict itself. Joining serves reads, reassembling the split facts on demand. Both directions are the design working, not the design failing. And the cost is smaller than it looks: matching ids is the single operation database engines are most ruthlessly built to do fast, as Chapter 8 will show. Rot is expensive; JOINs are cheap.

Common Confusions
  • "Splitting the data was a mistake if I have to re-join it." Splitting protects writes: one fact, one place, no drift. Joining serves reads. The two are halves of one design, and both directions are it working as intended.
  • "JOIN copies the tables together permanently." JOIN builds the combined rows in the answer only. The stored tables never change — you can join a thousand times and bookings still holds nothing but ids.
  • "I'll avoid JOINs by keeping everything in one big table." That is the spreadsheet, rediscovered: names and titles copied into every row, drifting apart with every correction. Chapter 6 names the disease properly (redundancy) — this page is where you learn not to catch it.
  • "The ids are clutter the database should hide from me." The ids are the connective tissue — they are how a booking knows its customer at all. What should be hidden is the manual flipping, and that is precisely the job JOIN does.
Why It Matters
  • Nearly every real query in every real job is a JOIN or three. This chapter is the toll gate between "can read SQL" and "can actually use a database" — and this page is why the gate exists.
  • Understanding why JOINs exist inoculates you against the one-big-flat-table instinct that quietly ruins schemas, spreadsheets, and eventually weekends.

Knowledge Check

Why does the bookings table store customer_id 214 instead of the name "Anna"?

  • Because numbers take up less disk space than names
  • So the name is stored once and every booking just points at it
  • Because SQL forbids you from storing the same text in more than one table
  • To keep customer names private from queries

You run a query that joins bookings with customers and screenings. What happens to the stored tables?

  • The three tables are merged into one new stored table
  • The bookings table gains name and title columns
  • Nothing — the combined rows exist only in the query's answer
  • A temporary copy of each of the tables is saved until you delete it

What is the trade-off this page calls "the read-side payment for the write-side virtue"?

  • Writes are slower, but reads are faster
  • Facts stored once can't drift, but readable answers must be reassembled
  • The database checks many more rules, but the queries need far more typing
  • Pointers save space, but make the data less secure

A colleague suggests putting customer names and film titles directly into every bookings row, "so we never need JOINs". What is the predictable result?

  • The database engine will simply refuse to create such a wide duplicated table
  • Queries become too slow to be usable
  • Copied facts eventually disagree — a fix misses a row and the data lies
  • WHERE clauses stop working on duplicated columns

You got correct