Module 6 · Data Modelling
Normalisation
Remove repetition step by step - first, second and third normal form - so every fact is stored exactly once.
About 35 minutes
The problem
Kolanut's old invoice sheet had one row per product sold, with the customer's name and city and the product's category typed on every row. Peace Provisions' city appeared on every line of every invoice. When they moved, some rows were updated and others weren't. That's an update anomaly, and a flat sheet makes it almost inevitable.
The concept
Normalisation reorganises tables so each fact lives in exactly one place. It prevents three problems:
| Anomaly | What goes wrong | Kolanut example |
|---|---|---|
| Update | A fact changed in some rows but not others | Half the rows say Kano, half say Abuja |
| Insert | You can't record something until something else exists | A new product can't be added until someone buys it |
| Delete | Removing one row erases an unrelated fact | Deleting the only invoice for a product loses its category |
It's done in steps called normal forms:
- First normal form (1NF): one value per cell, no repeating groups (no
product1,product2,product3columns), and a key for every row. - Second normal form (2NF): 1NF, and every non-key column depends on the whole key. In a line table keyed by
(invoice_id, product_id),invoice_datedepends only oninvoice_id, so it moves to an invoices table. - Third normal form (3NF): 2NF, and no non-key column depends on another non-key column.
customer_citydepends oncustomer, not on the invoice, so it moves to a customers table.
A memory aid: every non-key column should depend on the key, the whole key, and nothing but the key.
What stays on the line? unit_price stays on invoice_lines even though products have a list price. The price charged is a fact about that sale (discounts, the January 2026 price rise), not about the product. Deciding which facts belong to which entity is the judgement at the heart of normalisation.
Example
Kolanut's customers.csv stores the sales rep's name on every customer row. Should reps be their own table?
There are only a few reps, each named on many customers. If a rep's surname changed, it would need updating on every one of their customers. In a fully normalised operational database, sales_reps(rep_id, rep_name, region) becomes its own table and customers keep a rep_id.
For analysis, though, a rep name on the customer table is often fine, as the next lesson explains.
Walkthrough
Normalising a flat sheet, in order:
- Find the grain and key of the flat sheet: here
(invoice, product). - Split out repeating facts about the first key part: invoice date and customer →
invoices. - Split out facts that depend on a non-key column: customer city →
customers; product category →products. - Keep facts about the combination on the line table: quantity, price charged.
- Add foreign keys so the tables join back together.
- Test: can you rebuild the original sheet with joins? You should get exactly the same rows.
Practice
Practice
In the flat sheet in the diagram, how many times is Peace Provisions' city (Kano) stored? And after normalising? Give the first number.
Practice
How many different sales reps appear in Kolanut's customers.csv? That's how many rows a sales_reps table would have.
Check your understanding
Answer every question to check.