Skip to content

Expert: design, speed and engines · Schema design and normalisation · topic 43 of 62 · 40 min

Normalisation: 1NF, 2NF, 3NF and BCNF

Explanation

Normalisation means splitting data so that each fact is stored in exactly one place. A flat, spreadsheet-like table repeats facts, which causes update, insert and delete anomalies. The normal forms (1NF, 2NF, 3NF and BCNF) are checks that remove those repeats step by step, using the idea of functional dependency.

The problem: one big sheet

Many businesses start with one wide sheet, one row per order line, with everything copied in. It is easy to read, so people ask "why not keep it like that?" The answer shows up as soon as the data changes.

output
order_id | order_date | customer   | customer_phone        | city | product      | category | unit_price | qty
101      | 2025-01-05 | Asha Verma | 9876500001,9876500002 | Pune | Basmati Rice | Grocery  | 650        | 2
101      | 2025-01-05 | Asha Verma | 9876500001,9876500002 | Pune | Toor Dal     | Grocery  | 160        | 3
103      | 2025-02-14 | Asha Verma | 9876500001,9876500002 | Pune | Cotton Kurta | Fashion  | 899        | 2
A flat orders sheet. Asha's phone and city appear on every line she ever bought.

Three anomalies

An anomaly is a way the data can become wrong or impossible to store because of the table's shape.

  • Update anomaly: Asha moves to Mumbai. Her city sits on 3 rows (or 3,000). Miss one and the database now says two different things.
  • Insert anomaly: the shop adds a new product, the Power Bank, before anyone orders it. There is no order line to put it on, so the product cannot be recorded at all.
  • Delete anomaly: the only order containing Cotton Kurta is deleted, and the fact that Cotton Kurta costs ₹899 and is Fashion disappears with it.

Functional dependency, in words

A functional dependency A → B ("A determines B") means: if you know A, there is exactly one B. Knowing a customer's id tells you her name; knowing a product tells you its category. In the sheet, order_id → order_date and customer, product → category and unit_price, and only the pair (order_id, product) → qty. Normalisation is simply making sure every column depends on the key of its own table.

First normal form (1NF): one value per cell

A table is in 1NF when every cell holds a single, atomic value, there are no repeating groups of columns, and rows have a key. The phone cell "9876500001,9876500002" breaks it: you cannot easily search, join or validate one number inside a list. Columns like phone1, phone2, phone3 break it too, in a sneakier way: a customer with four phones does not fit, and "find this phone" needs three ORs.

sql
CREATE TABLE customer_phones (
  customer_id INTEGER NOT NULL REFERENCES customers(id),
  phone       TEXT NOT NULL,
  PRIMARY KEY (customer_id, phone)
);
The 1NF fix: one row per phone number, in a child table.

Second normal form (2NF): the whole key

2NF matters when the key has more than one column. A table is in 2NF when it is in 1NF and no non-key column depends on only part of the key. In the sheet the key is (order_id, product). But order_date depends on order_id alone, and category depends on product alone. These are partial dependencies. The fix is to move them out: order details go to orders, product details to products, and the line table keeps only what needs the whole pair, qty and the price paid.

Third normal form (3NF): nothing but the key

A table is in 3NF when it is in 2NF and no non-key column depends on another non-key column. After the 2NF split, orders still holds customer, phone and city. city depends on the customer, not on the order: order_id → customer → city. That chain is a transitive dependency. The fix is a customers table, with orders keeping only customer_id.

Tip: A well-known memory aid sums up 1NF to 3NF: every non-key column must depend on the key, the whole key, and nothing but the key.

BCNF, briefly

Boyce-Codd normal form is a slightly stricter 3NF: for every dependency A → B, A must be a candidate key. It only bites in unusual tables with overlapping candidate keys. Classic case: tutoring(student, subject, teacher), where each teacher teaches one subject. The key is (student, subject), but teacher → subject and teacher is not a key. The BCNF fix is teachers(teacher, subject) plus tutoring(student, teacher). There are higher forms (4NF, 5NF), but 3NF or BCNF is the everyday goal.

The result, and getting the report back

The normalised version of the sheet is exactly the SHOP database you have used all course: customers, products, orders and order_items. The flat report is not lost; a JOIN rebuilds it whenever you need it.

sql
SELECT o.id AS order_id, o.order_date, c.name AS customer, c.city, p.name AS product, oi.qty
FROM orders o
JOIN customers c    ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p     ON p.id = oi.product_id
WHERE o.id = 101;
-- 101 | 2025-01-05 | Asha Verma | Pune | Basmati Rice 5kg | 2
-- 101 | 2025-01-05 | Asha Verma | Pune | Toor Dal 1kg     | 3
Store each fact once; join to present it.

Common mistakes

  • Storing lists in one cell ("1,5,7" or JSON arrays) for things you search or join on. Fix: a child table with one row per item.
  • Thinking order_items.price is a 3NF violation because products also has a price. It is not: it is the price paid at that moment, a different fact. Copy it on purpose.
  • Splitting tables so far that every screen needs ten joins for no benefit. Fix: aim for 3NF, then denormalise only with a measured reason (next topic).
  • Mixing up 2NF and 3NF. Fix: 2NF is about part of a composite key; 3NF is about one non-key column determining another.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Normalisation: 1NF, 2NF, 3NF and BCNF · 1/7

Normalisation

Normalisation means storing each fact in exactly one place. Let us see why that matters.

Prefer a video by a teacher? Find free videos on “database normalization 1nf 2nf 3nf bcnf explained with examples” on YouTube ↗

In real life: A school marks register moves off Excel

A school kept marks in a workbook: one row per student per exam, with the class teacher's name and phone copied onto every row, and subjects as columns (maths, science, english). When a teacher changed her number, hundreds of cells were out of date, and adding a new subject meant a new column everywhere. The normalised design has students, teachers, classes, subjects, exams and a marks table (student_id, exam_id, subject_id, marks). A new subject is now one new row, and the old report card is one JOIN away.

sql
CREATE TABLE marks (
  student_id INTEGER NOT NULL REFERENCES students(id),
  exam_id    INTEGER NOT NULL REFERENCES exams(id),
  subject_id INTEGER NOT NULL REFERENCES subjects(id),
  marks      INTEGER CHECK (marks BETWEEN 0 AND 100),
  PRIMARY KEY (student_id, exam_id, subject_id)
);
Subjects are rows, not columns, so the design never changes when a subject is added.

Practice (0/4 done)

Task 1 · Code · SQL

SHOP is the normalised version of a flat orders sheet. Rebuild the flat report for Delivered orders only. Return order_id, order_date, customer (the customer's name), city, product (the product's name), qty and price (from order_items), sorted by order_id and then by product.

Task 2 · Code · SQL

customers_raw breaks 1NF: its phones column holds a comma-separated list such as '9876500001,9876500002'. Split it into one row per phone. Return customer_id and phone (row order does not matter). Use a recursive CTE, or SQLite's json_each.

Task 3 · Question

enrolments(student_id, course_id, student_name, grade) has the primary key (student_id, course_id). Which normal form does it break, and why?

Task 4 · Question

employees(id, name, dept_id, dept_city) has the primary key id. Each department is in one city. What is wrong, and what is the fix?

Quick quiz

Question 1

1.A product's category is copied onto every order line. You rename the category on some lines but not others. What is this called?

Question 2

2.Which table breaks first normal form?

Question 3

3.What does the functional dependency customer_id → city mean?

Question 4

4.order_items.price repeats the price from products. Is that a normalisation mistake?

Question 5

5.How is BCNF stricter than 3NF?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

A product's category is copied onto every order line. You rename the category on some lines but not others. What is this called?

Answer: An update anomaly

One fact stored in many places can be changed in some places and not others.

Which table breaks first normal form?

Answer: customers(id, name, phone1, phone2, phone3)

phone1, phone2, phone3 is a repeating group. Use a child table with one row per phone.

What does the functional dependency customer_id → city mean?

Answer: Knowing customer_id tells you exactly one city

A → B means one A value determines one B value.

order_items.price repeats the price from products. Is that a normalisation mistake?

Answer: No: it records the price paid at the time, which is a different fact from today's price

Today's price and the price on an old bill are different facts. Copying it on purpose keeps old bills correct.

How is BCNF stricter than 3NF?

Answer: Every determinant (the A in A → B) must be a candidate key

BCNF closes a gap in 3NF that appears with overlapping candidate keys.

Which design breaks first normal form (1NF)?Related

Answer: A column phones containing "98100 11111, 98100 22222"

1NF needs one value per cell. A list of phones belongs in its own table with one phone per row.

From Normalisation and table design

A weekly sales report over five years of data, run by the business team, is typical of...Related

Answer: OLAP

Big read-only analysis over history is OLAP.

From Theory questions you will be asked

To get each customer's product categories in SHOP, which tables must the query pass through?Related

Answer: customers, orders, order_items and products

Keys connect customers → orders → order_items → products; you cannot skip a link.

From Joining three or more tables

Asha has three orders. In orders JOIN customers, how many result rows mention her?Related

Answer: Three, one per matching order

A join returns one row per matching pair, not one per key.

From Why joins, and INNER JOIN

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Schema design and normalisation” before its exam: Denormalisation, OLTP and OLAP, Data types, naming and good habits, Design project: an online store.
  2. Module 9 exam: Schema design and normalisation · 13 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Indexes, query plans and views. 62 topics left in the course before the final exam.

← Previous: Modelling data: entities and relationships