An unnormalised database stores the same fact in multiple places. This redundancy creates three types of anomaly — errors that arise from normal database operations.
The same fact is stored in multiple rows. Updating one copy and missing another leaves the database in a contradictory state.
An entity can only be stored if it is already linked to another entity — even when that link shouldn't be required.
Removing one entity accidentally destroys information about a different, unrelated entity stored in the same row.
An online bookshop stores order data in a single flat table. Each order can contain multiple books — producing multi-value cells and repeating groups, highlighted below.
| OrderID | OrderDate | CustomerID | CustomerName | CustomerEmail | BookISBN | BookTitle | AuthorID | AuthorName | Price | Qty |
|---|---|---|---|---|---|---|---|---|---|---|
| 1001 | 15/01/24 | C01 | Alice Brown | alice@mail.com | 978-0, 978-1 | Python Crash, Java Pro | A01, A02 | Matthes, Bloch | 18.99, 29.99 | 2, 1 |
| 1002 | 16/01/24 | C02 | Bob Smith | bob@mail.com | 978-0 | Python Crash | A01 | Matthes | 18.99 | 3 |
ORDERS(OrderID, OrderDate, CustomerID, CustomerName, CustomerEmail, {BookISBN, BookTitle, AuthorID, AuthorName, Price, Qty})
One row per book per order. All cells are atomic. The composite primary key is OrderID + BookISBN. Partial dependencies (highlighted in amber) remain.
| OrderID ↑ | BookISBN ↑ | OrderDate | CustomerID | CustomerName | CustomerEmail | BookTitle | AuthorID | AuthorName | Price | Qty |
|---|---|---|---|---|---|---|---|---|---|---|
| 1001 | 978-0 | 15/01/24 | C01 | Alice Brown | alice@mail.com | Python Crash | A01 | Matthes | 18.99 | 2 |
| 1001 | 978-1 | 15/01/24 | C01 | Alice Brown | alice@mail.com | Java Pro | A02 | Bloch | 29.99 | 1 |
| 1002 | 978-0 | 16/01/24 | C02 | Bob Smith | bob@mail.com | Python Crash | A01 | Matthes | 18.99 | 3 |
ORDER_DATA(OrderID, BookISBN, OrderDate, CustomerID, CustomerName, CustomerEmail, BookTitle, AuthorID, AuthorName, Price, Qty)
In 2NF: every non-key attribute must depend on the whole primary key — not just part of it. We identify each partial dependency and extract it into its own relation.
| Attribute(s) | Depends on… | Status |
|---|---|---|
| OrderDate, CustomerID, CustomerName, CustomerEmail | OrderID only | Partial ✗ |
| BookTitle, AuthorID, AuthorName, Price | BookISBN only | Partial ✗ |
| Qty | OrderID AND BookISBN | Fully dependent ✓ |
ORDER
(OrderID, OrderDate, CustomerID,
CustomerName, CustomerEmail)
BOOK
(BookISBN, BookTitle, AuthorID,
AuthorName, Price)
ORDER_LINE
(OrderID, BookISBN, Qty)
FK: OrderID → ORDER
FK: BookISBN → BOOK
In 3NF: every non-key attribute must depend on the key, the whole key, and nothing but the key. A transitive dependency is when A → B → C (C depends on A only via B).
| Relation | Dependency chain | Status |
|---|---|---|
| ORDER | OrderID → CustomerID CustomerID → CustomerName, CustomerEmail | Transitive ✗ |
| BOOK | BookISBN → AuthorID AuthorID → AuthorName | Transitive ✗ |
| ORDER_LINE | Qty depends only on (OrderID, BookISBN) | In 3NF ✓ |
CUSTOMER
(CustomerID, CustomerName,
CustomerEmail)
ORDER
(OrderID, OrderDate, CustomerID)
FK: CustomerID → CUSTOMER
AUTHOR
(AuthorID, AuthorName)
BOOK
(BookISBN, BookTitle, AuthorID, Price)
FK: AuthorID → AUTHOR
ORDER_LINE
(OrderID, BookISBN, Qty)
FK: OrderID → ORDER
FK: BookISBN → BOOK
Non-atomic cells. Repeating groups of columns. No unique row identifier. All data in one flat table.
All cells atomic. One row per unique combination. Composite primary key established. Partial dependencies remain.
No partial dependencies. Each non-key attribute depends on the whole primary key. Relations split by what each attribute actually determines.
No transitive dependencies. Every non-key attribute depends on the key and nothing but the key. No redundancy remains.
CUSTOMER(CustomerID, CustomerName, CustomerEmail)
ORDER(OrderID, OrderDate, CustomerID)
AUTHOR(AuthorID, AuthorName)
BOOK(BookISBN, BookTitle, AuthorID, Price)
ORDER_LINE(OrderID, BookISBN, Qty)
The following question is from an AQA specimen paper. Read it carefully before answering — note the command word.
Athlete(AthleteNumber, Forename, Surname, DateOfBirth, Nationality)
Race(RaceNumber, RaceDate, Distance, Venue)
RaceEntryAndResult(RaceNumber, AthleteNumber, TimeSet)
Supplier(SupplierID, SupplierName, ContactEmail)
Product(ProductID, ProductName, Price, SupplierID, SupplierName)
Problems:
Non-atomic cells (multi-value)
Repeating groups of columns
No unique row identifier
Notation:
Entity({RepeatingGroup})
Fix: expand to one value per cell
one row per combination
establish composite PK
Test: all cells atomic?
every row uniquely identifiable?
Remaining issue: partial dependencies
Fix: extract attributes that depend
on only PART of the PK into
their own relation
Test: does every non-key attribute
depend on the WHOLE PK?
Only relevant with a composite PK
Remaining issue: transitive deps
Fix: extract A→B→C chains
B becomes PK of new relation
B stays as FK in original
Test: does every non-key attribute
depend ONLY on the key?
(nothing but the key)
No redundancy remains in 3NF