Topic 26

Normalization Without the Jargon

Design

This page teaches by demolition. We are going to take the Marquee's clean four-table schema and deliberately wreck it: rebuild the bookings as one wide table with the film title, the customer's name, and the price copied into every row. It is the table every beginner is tempted to build, because it looks so convenient — everything about a booking in one place, no JOINs, one SELECT and done.

Then we are going to watch it rot in real time. One innocent UPDATE, and the database will contradict itself: two titles for one film, and no query in the world able to say which is true. The habit of not letting that happen has a name, normalization, and here is the twist this page builds to: you have been doing it since Chapter 2 without ever hearing the word.

The Wide-Table Experiment

Here is the specimen. Instead of bookings that point at customers and screenings, every booking row carries full copies of everything anyone might want to read:

The cautionary specimen — do not build this at home
CREATE TABLE bookings_wide (
  booking_id     INTEGER PRIMARY KEY,
  customer_name  TEXT,
  customer_email TEXT,
  film_title     TEXT,
  screen         INTEGER,
  starts_at      TIMESTAMP,
  price          DECIMAL(6,2),
  seat           TEXT
);

Let's be honest about its charm first, because the charm is real. Every question about a booking is one SELECT away, no JOINs, no pointer-following. Friday's 8 p.m. showing of Night Bus with three bookings looks like this:

booking_idcustomer_namefilm_titlescreenstarts_atpriceseat
501AnnaNight Bus1Fri 20:0011.00G7
502BenNight Bus1Fri 20:0011.00G8
503PriyaNight Bus1Fri 20:0011.00H4

Readable, compact, friendly. Now the wreck. The distributor renames the film for its re-release, and someone dutifully updates the booking they happen to be looking at:

One correct-looking UPDATE — and the rot begins
UPDATE bookings_wide
SET film_title = 'Night Bus (Director''s Cut)'
WHERE booking_id = 501;
booking_idcustomer_namefilm_titlescreenstarts_atpriceseat
501AnnaNight Bus (Director's Cut)1Fri 20:0011.00G7
502BenNight Bus1Fri 20:0011.00G8
503PriyaNight Bus1Fri 20:0011.00H4

Look at rows 501 and 502. Same showing, same film, two different titles. The database now contradicts itself, and — this is the part that should bother you — it has no way of knowing it. Both rows are perfectly valid; the table's shape permits the contradiction, so no rule was broken. Anna's ticket says one film, Ben's says another, and a report counting sales per title now splits one film's revenue across two names. Nothing crashed. The data just quietly stopped being true.

Three Ways It Rots

What you just watched has a name: an update anomaly — the same fact stored in many rows, an update that reaches some copies and misses others, and a contradiction left behind. It is the double-sold seat's quieter cousin, and in a wide table it is not bad luck; it is a standing invitation. Every copy of the title is a chance to miss one.

The second rot is stranger: the insertion anomaly. Lora signs a new film, The Glass Coast, opening next month. Where does it go? In bookings_wide, film facts only exist inside booking rows — and nobody has booked the new film yet. There is nowhere to write down its title, runtime, or rating without inventing a fake booking to carry them. The fact is held hostage by a table shape that never gave it a home of its own.

The third is the mirror image: the deletion anomaly. Paper Lanterns has one remaining booking, and the customer cancels. Delete that row and the film's title, its showing, its price all vanish with it — the table's only record that the film ever existed rode out the door on someone else's cancellation. You meant to delete one fact and destroyed several unrelated ones.

The three anomalies — one wide table, three ways to lose the truth
UPDATEFix one row, miss its twinstwo titles, one film
INSERTNew film, no bookingsnowhere to write it down
DELETECancel the last bookingthe film vanishes with it

The Rules of Thumb, De-jargoned

All three anomalies grow from the same root, and an everyday picture shows it. Write your phone number in five friends' paper address books, then change your number. Unless you track down all five books and correct every one, somebody is calling the old number forever — and no one can tell which book is right. The fix was never "be more careful with the books". The fix is one book, one entry, and everyone looks it up there. That is the whole of normalization; map the picture onto tables and you can drop it.

As table rules, it comes out like this. Every column in a row should be a fact about that row's key — about this booking, not about the film the booking happens to mention. No repeating groups: the moment you're tempted by customer1, customer2, customer3 columns, a table is trying to be born. And no fact should depend on only part of the story: the film's title depends on the film alone, so it has no business living in a row keyed by booking. Different phrasings, one instinct — one fact, one home.

One honest sentence for the curious, because you will meet the terms eventually: the numbered "normal forms" — 1NF, 2NF, 3NF — are formal labels for exactly these rules of thumb, in increasing strictness, and that simplified summary is all you need until a deep-dive course makes them precise.

You Have Been Normalizing Since Chapter 2

Now the reveal this page has been walking toward. Splitting the Marquee's data into films, screenings, customers, and bookings — the thing you did in Chapter 2 because it seemed sensible — was normalization. The title lives once, in films. Rename Night Bus there and it is one UPDATE to one row; every booking sees the correction instantly, because bookings never copied the title in the first place. A new film INSERTs into films with zero bookings, no hostage-taking. Cancel the last booking and the film sits untouched in its own table. All three anomalies, structurally impossible.

You learned the cure before the disease, which is why the cure never felt like medicine. And the price of the cure is one you have already paid, gladly: JOINs. Chapter 4 taught you to reassemble the split facts at read time, and that reassembly is the entire cost of storing every fact exactly once. The wide table saves you a JOIN and charges you the truth. It is the worst trade in the field — though the next page shows the rare moments when paying a little truth-risk for speed is a deal professionals knowingly make.

One fact, many places vs one fact, one place
bookings_wide one fact, many places
"Night Bus (Director's Cut)" in row 501 · "Night Bus" in rows 502 and 503 · same film, two truths · every report now quietly wrong
films + bookings one fact, one place
title stored once in films · bookings point, never copy · one UPDATE corrects everything · contradiction structurally impossible
Common Confusions
  • "Normalization is an advanced ritual with numbered forms." It is "one fact, one home" applied consistently — a habit, not a ceremony. The numbered forms are labels for degrees of the same idea, and you already practice it every time you point instead of copy.
  • "Wide tables are faster, so denormalizing is smart by default." Sometimes faster for reads, always at a cost to correctness. The next page is the honest negotiation about when that trade is worth it; the default stays normalized.
  • "The anomalies are rare edge cases." They arrive on schedule, like the Saturday double-sale did: the first renamed film, the first not-yet-booked release, the first cancelled last booking. Every contradiction in every messy dataset you will ever inherit traces back to one of the three.
  • "The database should have caught the two-title contradiction." It can't — both rows were valid under the table's shape. Constraints catch rule-breaking rows (next page); only the schema's shape can make a contradiction impossible to store at all.
Why It Matters
  • This page is the "why" underneath the entire relational design you now own: every pointer, every JOIN, every split table exists so that one fact lives in one place.
  • "Which anomaly is this?" is a diagnostic lens you can point at any broken spreadsheet or legacy database — and being able to name the rot is what turns you from its victim into its fixer.

Knowledge Check

A clinic stores the patient's phone number in every appointment row. The patient changes numbers, and the receptionist updates only the newest appointment. Which anomaly is this?

  • An update anomaly
  • An insertion anomaly
  • A deletion anomaly
  • Not an anomaly, just careless data entry

Lora signs a new film that opens next month. In bookings_wide, why can't the database hold it?

  • The new film's title does not match the table's declared column types
  • Film facts live only inside booking rows, and this film has none yet
  • Wide tables are read-only once created
  • The primary key can't be generated without a customer

What does the rule "one fact, one home" actually forbid?

  • Storing more than one row about the same film
  • Tables pointing at other tables
  • Storing the same fact in more than one place
  • Putting more than one fact in the same row

What are 1NF, 2NF, and 3NF?

  • Three tools that automatically restructure a wide table for you
  • Successive numbered versions of the SQL standard itself
  • Formal labels for the "one fact, one home" rules, in increasing strictness
  • The official names for the three anomalies: update, insertion, and deletion

In what sense had you already normalized the Marquee's data before this page?

  • By giving every one of the tables its own primary key back in Chapter 2
  • By splitting the data into four tables, each fact living in one home
  • By learning to write JOINs across those tables in Chapter 4
  • By declaring a type on every single column in the tables

You got correct