๐ Example 1: The School Library
The librarian keeps everything in one giant table. Every time a student borrows books, she adds a row:
| StudentID | StudentName | FormGroup | FormTeacher | BooksBorrowed |
|---|---|---|---|---|
| 001 | Amy | 10B | Ms Lopez | Dracula, Frankenstein |
| 002 | Ben | 10A | Mr Khan | Dracula |
| 003 | Cara | 10B | Ms Lopez | Frankenstein, Holes, Wonder |
Let's fix it โ one normal form at a time. ๐ช
Step 1 Get to 1NF โ one box, one value
๐ต๏ธ Spot the problem: the BooksBorrowed column has whole lists inside single cells. Amy's cell holds two books!
๐ค Think first: how would YOU fix it? Then click to reveal.
Give each borrowed book its own row. Yes, we repeat the student info for now โ we'll deal with that in the next steps!
โ Now in 1NF
| ๐ StudentID | ๐ Book | StudentName | FormGroup | FormTeacher |
|---|---|---|---|---|
| 001 | Dracula | Amy | 10B | Ms Lopez |
| 001 | Frankenstein | Amy | 10B | Ms Lopez |
| 002 | Dracula | Ben | 10A | Mr Khan |
| 003 | Frankenstein | Cara | 10B | Ms Lopez |
| 003 | Holes | Cara | 10B | Ms Lopez |
| 003 | Wonder | Cara | 10B | Ms Lopez |
Every cell holds one value โ . The key is now StudentID + Book together (each pairing appears once).
Step 2 Get to 2NF โ facts about the WHOLE key
๐ต๏ธ Spot the problem: our key is StudentID + Book. Ask of every other column: "do you need BOTH parts of the key, or just one?"
- ๐ StudentName โ depends only on StudentID. Partial! โ
- ๐ FormGroup โ depends only on StudentID. Partial! โ
- ๐ FormTeacher โ depends only on StudentID. Partial! โ
๐ค Think first: what should we do with those columns? Then click to reveal.
Move all the student facts into their own Students table, and leave the Loans table to record just who borrowed what.
โ Now in 2NF โ two tables
| ๐๐ StudentID | ๐ Book |
|---|---|
| 001 | Dracula |
| 001 | Frankenstein |
| 002 | Dracula |
| 003 | Frankenstein |
| 003 | Holes |
| 003 | Wonder |
| ๐ StudentID | StudentName | FormGroup | FormTeacher |
|---|---|---|---|
| 001 | Amy | 10B | Ms Lopez |
| 002 | Ben | 10A | Mr Khan |
| 003 | Cara | 10B | Ms Lopez |
Amy's name now appears once, no matter how many books she borrows. The ๐ StudentID in Loans links back to the Students table.
Step 3 Get to 3NF โ nothing but the key
๐ต๏ธ Spot the problem: in the Students table, FormTeacher depends on FormGroup (an ordinary column) โ not on StudentID (the key). Every 10B student automatically has Ms Lopez.
๐ค Think first: how do we fix it? Then click to reveal.
Give form groups their own table. Students keep just the FormGroup code as a link.
โ Now in 3NF โ three tables, fully tidy! ๐
| ๐๐ StudentID | ๐ Book |
|---|---|
| 001 | Dracula |
| 001 | Frankenstein |
| 002 | Dracula |
| 003 | Frankenstein |
| 003 | Holes |
| 003 | Wonder |
| ๐ StudentID | StudentName | ๐ FormGroup |
|---|---|---|
| 001 | Amy | 10B |
| 002 | Ben | 10A |
| 003 | Cara | 10B |
| ๐ FormGroup | FormTeacher |
|---|---|
| 10A | Mr Khan |
| 10B | Ms Lopez |
Every fact lives in exactly one place:
- ๐ Who borrowed what โ Loans
- ๐ Who each student is โ Students
- ๐ซ Who teaches each form โ FormGroups
๐ Example 2: The Pizza Shop (quicker run-through)
Try to spot each problem before opening the answer!
| OrderID | CustomerName | CustomerPhone | PizzasOrdered |
|---|---|---|---|
| P100 | Dev | 555-1234 | Margherita, Pepperoni |
| P101 | Ella | 555-9876 | Hawaiian |
| P102 | Dev | 555-1234 | Pepperoni |
๐ฅ 1NF check: what's wrong? (click to reveal)
PizzasOrdered holds lists! Split them so each pizza on each order gets its own row. The key becomes OrderID + Pizza.
| ๐ OrderID | ๐ Pizza | CustomerName | CustomerPhone |
|---|---|---|---|
| P100 | Margherita | Dev | 555-1234 |
| P100 | Pepperoni | Dev | 555-1234 |
| P101 | Hawaiian | Ella | 555-9876 |
| P102 | Pepperoni | Dev | 555-1234 |
๐ฅ 2NF check: which columns depend on only PART of the key? (click to reveal)
CustomerName and CustomerPhone depend only on OrderID โ not on which pizza. Move order facts to an Orders table:
| ๐๐ OrderID | ๐ Pizza |
|---|---|
| P100 | Margherita |
| P100 | Pepperoni |
| P101 | Hawaiian |
| P102 | Pepperoni |
| ๐ OrderID | CustomerName | CustomerPhone |
|---|---|---|
| P100 | Dev | 555-1234 |
| P101 | Ella | 555-9876 |
| P102 | Dev | 555-1234 |
๐ฅ 3NF check: is any column describing another ordinary column? (click to reveal)
CustomerPhone depends on CustomerName (well โ on the customer!), not on the OrderID. Dev's phone number is stored twice. Give customers their own table:
| ๐ OrderID | ๐ CustomerID |
|---|---|
| P100 | C1 |
| P101 | C2 |
| P102 | C1 |
| ๐ CustomerID | CustomerName | CustomerPhone |
|---|---|---|
| C1 | Dev | 555-1234 |
| C2 | Ella | 555-9876 |
๐ Done! Three linked tables (OrderLines โก๏ธ Orders โก๏ธ Customers), every fact stored once.
๐งญ Your normalisation checklist
Use these three questions on ANY table, in order:
| Step | Ask yourselfโฆ | If YESโฆ |
|---|---|---|
| ๐ฅ 1NF | Is there a list squashed into one cell? | Split it into separate rows |
| ๐ฅ 2NF | Does any column depend on only part of a two-part key? | Move it to its own table |
| ๐ฅ 3NF | Does any column describe another ordinary column instead of the key? | Move it to its own table |