Normalisation

Databases

02

Important Notice

This is an unofficial support material, created by students with the help of artificial intelligence. It does not represent the official position of the institution or its professors.
Do not question or bother the professors about this content. The material is based on notes taken during classes and supplementary research done by the students themselves.
This material may contain inaccuracies. When in doubt, always refer to the official material provided by the professor and the references listed at the end.
Suggestions and Corrections
Found an error or have a suggestion? Send your contribution through the class WhatsApp group. All collaboration is welcome to improve the material for everyone.
03
TABLE OF CONTENTS

Contents

04
INTRO

What is Normalisation?

Imagine a spreadsheet where one cell contains multiple values separated by slashes — like storing several item numbers, descriptions and prices all in a single row. That is an unnormalised table, and it makes updating, searching and maintaining data a nightmare. Normalisation is the step-by-step process of reorganising that messy table into a set of well-structured tables that eliminate redundancy and protect data integrity.

The process follows a series of normal forms, each building on the previous one. First Normal Form (1NF) ensures every cell holds a single, atomic value — no repeating groups. Second Normal Form (2NF) removes partial dependencies, where a non-key attribute depends on only part of a composite key. Third Normal Form (3NF) removes transitive dependencies, where a non-key attribute depends on another non-key attribute rather than the primary key.

Think of it like organising a messy filing cabinet: 1NF makes sure each drawer has one type of document, 2NF ensures documents are filed under the right category, and 3NF moves cross-referenced information to its own dedicated drawer. By the end, each piece of data lives in exactly one place — update it once and it is correct everywhere.

In this lesson, we will walk through two complete examples — a computer hardware order system and a hospital visit system — transforming each from a single chaotic table into a clean set of normalised tables in 3NF. Along the way, you will learn to identify primary keys, foreign keys, composite keys, and the dependencies that drive each normalisation step.

05
DIAGRAM

UNF to 1NF — Removing Repeating Groups

Hardware order example: one value per cell, one fact per row

The problem: A computer hardware company stores orders in a single table. When an order has multiple items, the item details are crammed into one cell separated by slashes — X3412/X2189. This violates 1NF because each cell must hold exactly one value.

How to spot it: Look at each cell in your table. Does any cell contain more than one value (separated by commas, slashes, or listed vertically)? If yes, those attributes form a repeating group. The fix is always the same: move the repeating attributes into a new table, give it a meaningful name, and link back using the original primary key as a foreign key.
✗ Before — UNF (repeating groups in orange)
Order NoDateCust NoNameItem NoDescriptionCostQty
100105-Feb0123AmalX3412/X2189120Gb HD/Cisco NIC40/205/25
100210-Feb0345AliY7674/B3456/F6758417" Monitor/Wireless Mouse/2GB Ram50/10/806/10/7
⬇ First: expand every value into its own row
1NF — All values atomic (one per cell)
Order NoDateCust NoNameAddressContactItem NoDescriptionCostQty
100105-Feb0123AmalDublin39112200X3412120Gb HD405
100105-Feb0123AmalDublin39112200X2189Cisco NIC2025
100210-Feb0345AliMeath3321001Y767417" Monitor506
100210-Feb0345AliMeath3321001B3456Wireless Mouse1010
100210-Feb0345AliMeath3321001F675842GB Ram807
Notice the redundancy: Amal's details (name, address, contact) repeat for every item in order 1001. Ali's details repeat three times for order 1002. This is now in 1NF (one value per cell), but the repeated data wastes space and creates update problems. Next: split into ORDER (order data) and ORDER_ITEM (item data per order), linked by Order Number as a foreign key.
⬇ Split into two tables to reduce redundancy
✓ ORDER table
Order No (PK)DateCust NoNameAddressContact
100105-Feb-20240123AmalDublin39112200
100210-Feb-20240345AliMeath3321001
✓ ORDER_ITEM table (new)
Order No (FK)Item No (PK)DescriptionCostQty
1001X3412120Gb HD405
1001X2189Cisco NIC2025
1002Y767417" Monitor506
1002B3456Wireless Mouse1010
1002F675842GB Ram807
Result: The composite key of ORDER_ITEM is (Order Number + Item Number) — neither alone can uniquely identify a row. Order Number is also a foreign key linking back to ORDER.

The repeating item columns (orange) move to their own table. Each item now has its own row, and the two tables are linked by Order Number.

06
DIAGRAM

1NF to 2NF — Removing Partial Dependencies

Hardware order example: Description and Unit Cost depend only on Item Number

The problem: After 1NF, ORDER_ITEM has a composite key (Order Number + Item Number). But Description and Unit Cost only depend on Item Number — they have nothing to do with which order it is. This is called a partial dependency.

How to spot it: First, check if any table has a composite key (two or more columns forming the primary key). If it does, ask yourself for each non-key attribute: "Does this attribute need BOTH parts of the key to be identified, or just one?" If the answer is "just one", that attribute has a partial dependency and must move to its own table. Here, "What is item X3412 called?" only needs Item Number — it does not matter which order it belongs to.
✗ Partial Dependency Detected
COMPOSITE KEY
Order No + Item No
DEPENDS ONLY ON ITEM NO ✗
Description, Unit Cost
|
DEPENDS ON FULL KEY ✓
Quantity
⬇ Apply 2NF: move partially dependent attributes
✓ ORDER_ITEM (updated)
Order No (FK)Item No (FK)Qty
1001X34125
1001X218925
1002Y76746
1002B345610
1002F675847
✓ ITEM (new table)
Item No (PK)DescriptionUnit Cost
X3412120Gb HD40
X2189Cisco NIC20
Y767417" Monitor50
B3456Wireless Mouse10
F675842GB Ram80
Result: We now have three tables — ORDER, ORDER_ITEM, and ITEM. Item Number becomes a foreign key in ORDER_ITEM referencing the ITEM table. No attribute depends on only part of a key any more.

Description and Unit Cost move to ITEM because they depend only on Item Number, not on the full composite key.

07
DIAGRAM

2NF to 3NF — Removing Transitive Dependencies

Hardware order example: Name, Address and Contact depend on Customer Number, not Order Number

The problem: The ORDER table still contains Name, Address and Contact Number. These attributes do not depend on Order Number (the primary key) — they depend on Customer Number, which is itself a non-key attribute. This chain is called a transitive dependency: Order Number → Customer Number → Name/Address/Contact.

How to spot it: For each non-key attribute, ask: "Does this attribute describe the primary key directly, or does it describe another non-key attribute?" If Name describes Customer Number (not Order Number), you have a transitive dependency. The practical test: "If I change this value, would it be wrong in other rows?" If Amal moves from Dublin to Cork, every order she placed would show the wrong address — that means address belongs in its own table.
✗ Transitive Dependency Chain
PRIMARY KEY
Order No
NON-KEY
Customer No
DEPEND ON CUST NO ✗
Name, Address, Contact
⬇ Apply 3NF: move transitively dependent attributes
✓ ORDER (updated)
Order No (PK)Cust No (FK)Order Date
1001012305-Feb-2024
1002034510-Feb-2024
✓ CUSTOMER (new table)
Cust No (PK)NameAddressContact
0123AmalDublin39112200
0345AliMeath3321001
✓ Final schema — 4 tables in 3NF
ORDER
Order No (PK)Cust No (FK)Date
1001012305-Feb-2024
1002034510-Feb-2024
CUSTOMER
Cust No (PK)NameAddressContact
0123AmalDublin39112200
0345AliMeath3321001
ITEM
Item No (PK)DescriptionCost
X3412120Gb HD40
X2189Cisco NIC20
Y767417" Monitor50
B3456Wireless Mouse10
F675842GB Ram80
ORDER_ITEM
Order No (FK)Item No (FK)Qty
1001X34125
1001X218925
1002Y76746
1002B345610
1002F675847
Result: Four tables, each storing one type of fact. If Amal changes address, update one row in CUSTOMER. If an item's price changes, update one row in ITEM. No redundancy remains.

The complete 3NF result: ORDER, CUSTOMER, ITEM, and ORDER_ITEM — four tables with no redundancy, no partial dependencies, and no transitive dependencies.

08
DIAGRAM

Hardware Order — Table Relationships

How the four 3NF tables connect through foreign keys

1N1NN1CUSTOMERcustomer_numberPKnameaddresscontact_numberORDERorder_numberPKcustomer_numberFKorder_dateORDER_ITEMorder_numberFKitem_numberFKquantityITEMitem_numberPKdescriptionunit_cost
09
DIAGRAM

Hospital Visit System — Step by Step

Applying the three-step checklist to healthcare data

Second example — Hospital visits: A hospital records patient visits with treatment details. In the original table, one row stores all treatments for a visit (e.g. "X-Ray, Dr Smith, Radiology, €120; Blood Test, Dr John, Pathology, €50"). After applying 1NF (atomic values), 2NF (no partial dependencies) and 3NF (no transitive dependencies), the data splits into five clean tables. Each fact — patient names, doctor departments, treatment costs — is stored in exactly one place.
✗ UNF — Treatment details packed into one cell
VisitIDPatientDateTreatments
V101Ali Khan2026-03-10X-Ray (Dr Smith, Radiology, €120), Blood Test (Dr John, Pathology, €50)
V102Sara Noor2026-03-11MRI Scan (Dr Lee, Imaging, €300), ECG (Dr Patel, Cardiology, €90)
V103Emma Cole2026-03-12Consultation (Dr Patel, Cardiology, €80), X-Ray (Dr Smith, Radiology, €120)
Step 1 — 1NF: The Treatments column has multiple values in one cell. How to spot it: the commas and brackets tell us treatment name, doctor, department, and cost are all crammed together. Fix: give each treatment its own row. Composite key = (VisitID, TreatmentName).
⬇ 1NF result
VisitIDPatientIDPatientNameDateTreatmentDoctorDeptCost
V101P01Ali Khan2026-03-10X-RayDr SmithRadiology€120
V101P01Ali Khan2026-03-10Blood TestDr JohnPathology€50
V102P02Sara Noor2026-03-11MRI ScanDr LeeImaging€300
V102P02Sara Noor2026-03-11ECGDr PatelCardiology€90
V103P03Emma Cole2026-03-12ConsultationDr PatelCardiology€80
V103P03Emma Cole2026-03-12X-RayDr SmithRadiology€120
Step 2 — 2NF: Composite key is (VisitID + TreatmentName). Ask for each column: does it need BOTH parts? PatientID, PatientName and Date only need VisitID — they are the same regardless of which treatment. That is a partial dependency. Fix: move visit/patient data to a VISITS table.
⬇ 2NF result
VISITS
VisitIDPatientIDNameDate
V101P01Ali Khan2026-03-10
V102P02Sara Noor2026-03-11
V103P03Emma Cole2026-03-12
VISIT_TREATMENTS
VisitID (FK)TreatmentDoctorDeptCost
V101X-RayDr SmithRadiology€120
V101Blood TestDr JohnPathology€50
V102MRI ScanDr LeeImaging€300
V102ECGDr PatelCardiology€90
V103ConsultationDr PatelCardiology€80
V103X-RayDr SmithRadiology€120
Step 3 — 3NF: In VISITS, PatientName depends on PatientID (not VisitID) — transitive dependency. In VISIT_TREATMENTS, Doctor and Dept describe the doctor (not the visit), and Cost describes the treatment (not the visit). Ask: "If Dr Patel changes department, how many rows need updating?" Multiple — so extract. Fix: create PATIENTS, DOCTORS, and TREATMENTS tables.
⬇ 3NF result — five tables
Patients
PatientIDName
P01Ali Khan
P02Sara Noor
P03Emma Cole
Visits
VisitIDPatientID (FK)Date
V101P012026-03-10
V102P022026-03-11
V103P032026-03-12
Doctors
DoctorIDNameDept
D01Dr SmithRadiology
D02Dr JohnPathology
D03Dr LeeImaging
D04Dr PatelCardiology
Treatments
TreatmentIDNameCost
T01X-Ray€120
T02Blood Test€50
T03MRI Scan€300
T04ECG€90
T05Consultation€80
VisitTreatments
VisitID (FK)TreatmentID (FK)DoctorID (FK)
V101T01D01
V101T02D02
V102T03D03
V102T04D04
V103T05D04
V103T01D01
Key insight: The same three rules apply regardless of the domain. Ask yourself: Does this attribute depend on the whole key, just part of it, or another non-key attribute? The answer tells you which normal form to apply.

The hospital example produces five tables in 3NF. Each fact is stored once — updating a doctor's department or a treatment's cost requires changing only one row.

10
DIAGRAM

Hospital Visit System — Table Relationships

How the five 3NF tables connect through foreign keys

1N1N1NN1PATIENTSPatientIDPKPatientNameVISITSVisitIDPKPatientIDFKVisitDateDOCTORSDoctorIDPKDoctorNameDepartmentVISIT_TREATMENTSVisitIDFKTreatmentIDFKDoctorIDFKTREATMENTSTreatmentIDPKTreatmentNameCost
11
DIAGRAM

How to Identify Where to Split Tables

A three-step decision checklist for each normal form

The key question at each stage: Normalisation is about asking one question per stage. If the answer is "yes", you split. If "no", you move to the next stage. Here is the checklist you can follow for any table in any domain.
1
Check for 1NF — Are there repeating groups?
Ask: Does any cell contain more than one value?
Look for: Slashes (X3412/X2189), comma-separated lists, multiple rows of data packed into one cell.
If YES: First, expand every multi-valued cell into its own row — you get one big flat table where every cell is atomic. Then notice the redundancy (data repeating across rows) and split the repeating attributes into a new table. Use the original PK as a foreign key in the new table and create a composite key (original PK + new attribute PK).
If NO: The table is already in 1NF — proceed to step 2.
2
Check for 2NF — Any partial dependencies?
Ask: Is there a composite key? Does any non-key attribute depend on only part of it?
Test: For each non-key column, ask: "Do I need BOTH parts of the composite key to look up this value?"
If YES (partial dep exists): Move the partially dependent attributes to a new table, keyed by the part they depend on.
If NO (or no composite key): The table is already in 2NF — proceed to step 3.
3
Check for 3NF — Any transitive dependencies?
Ask: Does any non-key attribute depend on another non-key attribute rather than the primary key?
Test: For each non-key column, ask: "Does this describe the PK directly, or does it describe a different column?"
Practical test: "If I update this value, would other rows become inconsistent?"
If YES (transitive dep exists): Move the transitively dependent attributes to a new table. Keep the intermediary column as a FK.
If NO: Congratulations — your table is in 3NF!

Apply these three checks in order to any table. Each step builds on the previous one — you must achieve 1NF before checking for 2NF, and 2NF before checking for 3NF.

12
DIAGRAM

Worked Example — Lecturer Course System

Normalising a college timetable from UNF to 3NF

Third example: A college stores lecturer and course data in one table. Each lecturer teaches multiple courses, so course details repeat. Let us apply the same three-step checklist.
✗ UNF — Repeating course groups
Lect NoNameFacultyOfficeCourse CodeCourse NameStart DateWeekly Hrs
7146JamesBusinessRoom 22BM01Buss Manage12/9/20234
A01Account11/9/20233
E01Economics30/9/20237
1463DenisITRoom 21CS01Computer Systems01/9/20236
P01Programming02/9/20235
6455PhilBusinessRoom 22BM01Buss Manage04/10/20234
M01Marketing10/10/20236
⬇ Step 1: Expand every value into its own row
1NF — All values atomic (one per cell)
Lect NoNameFacultyOfficeCourse CodeCourse NameStartHrs
7146JamesBusinessRoom 22BM01Buss Manage12/9/234
7146JamesBusinessRoom 22A01Account11/9/233
7146JamesBusinessRoom 22E01Economics30/9/237
1463DenisITRoom 21CS01Comp Systems01/9/236
1463DenisITRoom 21P01Programming02/9/235
6455PhilBusinessRoom 22BM01Buss Manage04/10/234
6455PhilBusinessRoom 22M01Marketing10/10/236
Notice the redundancy: James's details (name, faculty, office) repeat for each of his 3 courses. Denis repeats twice, Phil repeats twice. This wastes space and creates update problems — if James changes office, three rows need updating. Next: split into LECTURER (lecturer data) and LECTURER_COURSE (course assignments), linked by Lect No as a foreign key.
⬇ Split into two tables to reduce redundancy
✓ LECTURER
Lect No (PK)NameFacultyOffice
7146JamesBusinessRoom 22
1463DenisITRoom 21
6455PhilBusinessRoom 22
✓ LECTURER_COURSE (composite key)
Lect No (FK)Course CodeCourse NameStartHrs
7146BM01Buss Manage12/9/234
7146A01Account11/9/233
7146E01Economics30/9/237
1463CS01Comp Systems01/9/236
1463P01Programming02/9/235
6455BM01Buss Manage04/10/234
6455M01Marketing10/10/236
⬇ Step 2: Apply 2NF
Partial dependency found: Course Name depends only on Course Code (not on the full composite key Lect No + Course Code). The same course always has the same name regardless of who teaches it. Fix: extract COURSE table.
✓ COURSE (new)
Code (PK)Name
BM01Buss Manage
A01Account
E01Economics
CS01Comp Systems
P01Programming
M01Marketing
✓ LECTURER_COURSE (updated)
Lect No (FK)Code (FK)Start DateWeekly Hrs
7146BM0112/9/234
7146A0111/9/233
7146E0130/9/237
1463CS0101/9/236
1463P0102/9/235
6455BM0104/10/234
6455M0110/10/236
⬇ Step 3: Check 3NF
Check LECTURER: Do Faculty or Office depend on another non-key attribute? Faculty describes the lecturer directly, and Office is assigned to the lecturer — no transitive dependency. Result: LECTURER is already in 3NF.
✓ Final schema — 3 tables in 3NF
LECTURER
Lect No (PK)NameFacultyOffice
7146JamesBusinessRoom 22
1463DenisITRoom 21
6455PhilBusinessRoom 22
COURSE
Code (PK)Name
BM01Buss Manage
A01Account
E01Economics
CS01Comp Systems
P01Programming
M01Marketing
LECTURER_COURSE
Lect No (FK)Code (FK)StartHrs
7146BM0112/9/234
7146A0111/9/233
7146E0130/9/237
1463CS0101/9/236
1463P0102/9/235
6455BM0104/10/234
6455M0110/10/236

The lecturer example uses the same three-step checklist. Course Name had a partial dependency on Course Code, but no transitive dependency was found — 3NF was reached in step 2.

13
DIAGRAM

Lecturer Course System — Table Relationships

How the three 3NF tables connect through foreign keys

1NN1LECTURERLect_NoPKNameFacultyOfficeLECTURER_COURSELect_NoFKCourse_CodeFKStart_DateWeekly_HrsCOURSECourse_CodePKCourse_Name
14
CODE

SQL — Step 1: UNF to 1NF

Split the single ORDER table into ORDER + ORDER_ITEM

step1_1nf.sql
-- STEP 1: UNF → 1NF
-- Remove repeating groups (item details) into a new table

CREATE TABLE "Order" (
    order_number     INT           PRIMARY KEY,
    order_date       DATE          NOT NULL,
    customer_number  VARCHAR(4)    NOT NULL,
    name             VARCHAR(50)   NOT NULL,
    address          VARCHAR(100)  NOT NULL,
    contact_number   VARCHAR(20)   NOT NULL
);

-- New table for the repeating item group
-- Composite key: order_number + item_number
CREATE TABLE Order_Item (
    order_number  INT          REFERENCES "Order",
    item_number   VARCHAR(10)  NOT NULL,
    description   VARCHAR(100) NOT NULL,
    unit_cost     DECIMAL(10,2) NOT NULL,
    quantity      INT          NOT NULL,
    PRIMARY KEY (order_number, item_number)
);
15
CODE

SQL — Step 2: 1NF to 2NF

Extract ITEM table to remove partial dependency

step2_2nf.sql
-- STEP 2: 1NF → 2NF
-- Description and unit_cost depend only on item_number
-- (partial dependency on the composite key)
-- Move them to a new ITEM table

CREATE TABLE Item (
    item_number  VARCHAR(10)   PRIMARY KEY,
    description  VARCHAR(100)  NOT NULL,
    unit_cost    DECIMAL(10,2) NOT NULL
);

-- Order_Item now only keeps quantity
-- (depends on the FULL composite key)
CREATE TABLE Order_Item (
    order_number  INT          REFERENCES "Order",
    item_number   VARCHAR(10)  REFERENCES Item,
    quantity      INT          NOT NULL,
    PRIMARY KEY (order_number, item_number)
);
16
CODE

SQL — Step 3: 2NF to 3NF

Extract CUSTOMER table to remove transitive dependency

step3_3nf.sql
-- STEP 3: 2NF → 3NF
-- Name, address and contact depend on customer_number
-- (transitive dependency — not on the PK)
-- Move them to a new CUSTOMER table

CREATE TABLE Customer (
    customer_number  VARCHAR(4)    PRIMARY KEY,
    name             VARCHAR(50)   NOT NULL,
    address          VARCHAR(100)  NOT NULL,
    contact_number   VARCHAR(20)   NOT NULL
);

-- Order now only keeps customer_number as FK
CREATE TABLE "Order" (
    order_number     INT           PRIMARY KEY,
    customer_number  VARCHAR(4)    REFERENCES Customer,
    order_date       DATE          NOT NULL
);

-- Final schema: 4 tables in 3NF
-- Customer, Order, Item, Order_Item
-- Each fact stored exactly once ✓
17
SUMMARY

Quick Reference

ConceptRuleWhat to Do
UNFRaw data, may have repeating groupsIdentify entity, list all attributes, find the key
1NFNo multi-valued attributesRemove repeating groups into a new table; add a foreign key to link them
2NFNo partial dependenciesIf a composite key exists, move attributes that depend on only part of it
3NFNo transitive dependenciesMove non-key attributes that depend on other non-key attributes
Primary Key (PK)Uniquely identifies each rowCannot be NULL; underlined in notation
Foreign Key (FK)References a PK in another tableCreates relationships between tables
Composite KeyTwo or more attributes forming a PKUsed when no single attribute is unique (e.g. Order No + Item No)
18
EXERCISES

Practice Exercises

Test your understanding of normalisation

01
A lecturer table has columns: Lecturer_no, Name, Faculty, Office_loc, Course_code, Course_name, Start_date, Weekly_time. One lecturer teaches multiple courses. What are the repeating (multi-valued) attributes?
Which attributes have multiple values for a single lecturer?
02
After removing the repeating group into a LECTURER_COURSE table, what would be the composite key of that new table?
Which two attributes together uniquely identify each row?
03
In the hardware order example, the ORDER table contains Name, Address and Contact Number alongside Customer Number. What type of dependency do Name, Address and Contact Number have, and which normal form does it violate?
Do these attributes depend on the primary key (Order Number) or on another non-key attribute?
04
In the hospital visit example, after reaching 1NF, the composite key is (VisitID, TreatmentName). PatientName and VisitDate depend only on VisitID. What normal form does this violate and why?
Does PatientName depend on the full composite key or just part of it?
05
What is the difference between a composite key and a foreign key?
06
Given: BOOK(BookID, BookDesc, CategoryID, CategoryDesc). Is this table in 3NF? If not, explain why and show the fix.
Does every non-key attribute depend directly on BookID?
07
Easy — Normalise this library loan table to 3NF.
LoanIDMemberNameMemberEmailBookTitleAuthorLoanDateReturnDate
L01Jane Doejane@mail.comSQL BasicsT. Brown01/03/2615/03/26
L02Jane Doejane@mail.comData DesignR. Singh05/03/2619/03/26
L03Mark Leemark@mail.comSQL BasicsT. Brown10/03/2624/03/26
Which attributes describe the member? Which describe the book? Which describe the loan itself?
08
Medium — Normalise this gym booking table to 3NF.
BookingIDClientPhoneClassInstructorInst PhoneRoomDate
B01Anna555-1234YogaCoach Kim555-9999R110/03/26
B02Tom555-5678YogaCoach Kim555-9999R110/03/26
B03Anna555-1234PilatesCoach Lee555-8888R211/03/26
Does Phone depend on BookingID or on the client? Does Instructor Phone depend on BookingID or on the instructor?
09
Medium-Hard — Normalise this online shop table. Identify all dependency chains.
PurchIDCustEmailCustNameProductCategoryCatDiscountQtyDate
P01jo@mail.comJoKeyboardPeripherals10%201/03/26
P02jo@mail.comJoMonitorDisplays5%102/03/26
P03sam@mail.comSamMousePeripherals10%303/03/26
Look for chains: does CatDiscount depend on Category, which depends on Product?
10
Hard — This hotel table has a repeating group. Normalise it through all three steps to 3NF.
ReservIDGuestGuestEmailCheckInRoomNoRoomTypeRateServices
R01Liamliam@mail.com01/03101Single€80Breakfast €15, Spa €40
R02Miamia@mail.com02/03205Double€120Breakfast €15, Gym €10, Laundry €25
R03Liamliam@mail.com10/03101Single€80Gym €10
Start with the Services column — it has multiple values. Then look for what describes the guest vs. what describes the room.
11
Challenge — A university stores student enrolments. This table has repeating groups AND transitive dependencies. Normalise to 3NF showing every step.
StudentIDNameAddressDeptCodeDeptNameModules
S001AvaDublinCSComp SciM101 Databases (Dr Ryan, B+), M102 Networks (Dr Kelly, A)
S002NoahCorkCSComp SciM101 Databases (Dr Ryan, A-), M103 Web Dev (Dr Murphy, B)
S003EllaGalwayBABusinessM201 Marketing (Dr Walsh, A), M101 Databases (Dr Ryan, B+)
The Modules column has module code, name, lecturer, and grade all packed together. After splitting, ask: does the module name depend on the student, or on the module code? Does the grade depend on one key or two?
19
ANSWERS

Answers

01
Course_code, Course_name, Start_date, Weekly_time
These four attributes repeat for each course a lecturer teaches — they form the repeating group that violates 1NF.
02
(Lecturer_no, Course_code)
Neither attribute alone can uniquely identify a row — Lecturer_no repeats for each course, and Course_code repeats if multiple lecturers teach the same course. Together they are unique.
03
Transitive dependency — violates 3NF
Name, Address and Contact Number depend on Customer Number, which is itself a non-key attribute. The fix is to extract a CUSTOMER table with Customer Number as PK, and keep only Customer Number as FK in ORDER.
04
2NF — partial dependency
PatientName depends only on VisitID, not on the full composite key (VisitID + TreatmentName). The fix is to move patient data to a separate Visits table.
05
A composite key is two or more attributes that together form a primary key (e.g. Order Number + Item Number in ORDER_ITEM). A foreign key is an attribute that references another table's primary key to create a relationship (e.g. Customer Number in ORDER references CUSTOMER).
A composite key identifies rows within its own table; a foreign key links to rows in another table. They serve different purposes — identification vs. relationship.
06
Not in 3NF. CategoryDesc depends on CategoryID (transitive dependency), not directly on BookID. Fix: split into BOOK(BookID, BookDesc, CategoryID FK) and CATEGORY(CategoryID, CategoryDesc).
BookID → CategoryID → CategoryDesc is a chain of dependencies. In 3NF, every non-key attribute must depend only on the primary key, with no intermediary.
07
Original Table
LoanIDMemberNameMemberEmailBookTitleAuthorLoanDateReturnDate
L01Jane Doejane@mail.comSQL BasicsT. Brown01/03/2615/03/26
L02Jane Doejane@mail.comData DesignR. Singh05/03/2619/03/26
L03Mark Leemark@mail.comSQL BasicsT. Brown10/03/2624/03/26
Step 1 — 1NF: Check each cell — does any contain multiple values? No, every cell has exactly one value. ✓ Already in 1NF.
⬇ Table stays the same
Step 1 check — table unchanged
LoanIDMemberNameMemberEmailBookTitleAuthorLoanDateReturnDate
L01Jane Doejane@mail.comSQL BasicsT. Brown01/03/2615/03/26
L02Jane Doejane@mail.comData DesignR. Singh05/03/2619/03/26
L03Mark Leemark@mail.comSQL BasicsT. Brown10/03/2624/03/26
Step 2 — 2NF: Is there a composite key? No — the primary key is LoanID (single column). Partial dependencies only occur with composite keys. ✓ Already in 2NF.
⬇ Table stays the same
Step 2 check — table unchanged
LoanIDMemberNameMemberEmailBookTitleAuthorLoanDateReturnDate
L01Jane Doejane@mail.comSQL BasicsT. Brown01/03/2615/03/26
L02Jane Doejane@mail.comData DesignR. Singh05/03/2619/03/26
L03Mark Leemark@mail.comSQL BasicsT. Brown10/03/2624/03/26
Step 3 — 3NF: For each non-key column, ask: "does this describe the PK or another column?"
• MemberName & MemberEmail depend on the member, not on LoanID — transitive dependency.
• BookTitle & Author depend on the book, not on LoanID — transitive dependency.
Fix: extract MEMBER and BOOK tables.
⬇ 3NF result — three tables
MEMBER
MemberIDMemberNameMemberEmail
M01Jane Doejane@mail.com
M02Mark Leemark@mail.com
BOOK
BookIDBookTitleAuthor
B01SQL BasicsT. Brown
B02Data DesignR. Singh
LOAN
LoanIDMemberID (FK)BookID (FK)LoanDateReturnDate
L01M01B0101/03/2615/03/26
L02M01B0205/03/2619/03/26
L03M02B0110/03/2624/03/26
Result: 3 tables in 3NF. Each fact stored once — changing Jane's email updates exactly one row in MEMBER.
Table Relationships
1NN1MEMBERMemberIDPKMemberNameMemberEmailLOANLoanIDPKMemberIDFKBookIDFKLoanDateReturnDateBOOKBookIDPKBookTitleAuthor
08
Original Table
BookingIDClientPhoneClassInstructorInst PhoneRoomDate
B01Anna555-1234YogaCoach Kim555-9999R110/03/26
B02Tom555-5678YogaCoach Kim555-9999R110/03/26
B03Anna555-1234PilatesCoach Lee555-8888R211/03/26
Step 1 — 1NF: Check each cell — does any contain multiple values? No, every cell has exactly one value. ✓ Already in 1NF.
⬇ Table stays the same
Step 1 check — table unchanged
BookingIDClientPhoneClassInstructorInst PhoneRoomDate
B01Anna555-1234YogaCoach Kim555-9999R110/03/26
B02Tom555-5678YogaCoach Kim555-9999R110/03/26
B03Anna555-1234PilatesCoach Lee555-8888R211/03/26
Step 2 — 2NF: Is there a composite key? No — the primary key is BookingID (single column). Partial dependencies only occur with composite keys. ✓ Already in 2NF.
⬇ Table stays the same
Step 2 check — table unchanged
BookingIDClientPhoneClassInstructorInst PhoneRoomDate
B01Anna555-1234YogaCoach Kim555-9999R110/03/26
B02Tom555-5678YogaCoach Kim555-9999R110/03/26
B03Anna555-1234PilatesCoach Lee555-8888R211/03/26
Step 3 — 3NF: For each non-key column, ask: "does this describe the PK or another column?"
• Phone depends on Client, not on BookingID — transitive dependency (Anna's phone repeats in B01 & B03).
• Instructor, Inst Phone & Room depend on Class, not on BookingID — transitive dependency (Yoga is always Coach Kim in R1).
• Inst Phone depends on Instructor, not on Class — another transitive dependency (Coach Kim's phone describes the instructor).
Fix: extract CLIENT, INSTRUCTOR, and CLASS tables.
⬇ 3NF result — four tables
CLIENT
ClientIDClientNamePhone
C01Anna555-1234
C02Tom555-5678
INSTRUCTOR
InstructorIDNamePhone
I01Coach Kim555-9999
I02Coach Lee555-8888
CLASS
ClassIDClassNameInstructorID (FK)Room
CL01YogaI01R1
CL02PilatesI02R2
BOOKING
BookingIDClientID (FK)ClassID (FK)Date
B01C01CL0110/03/26
B02C02CL0110/03/26
B03C01CL0211/03/26
Result: 4 tables in 3NF. Each fact stored once — changing Coach Kim's phone updates one row in INSTRUCTOR.
Table Relationships
1NN1N1CLIENTClientIDPKClientNamePhoneBOOKINGBookingIDPKClientIDFKClassIDFKDateCLASSClassIDPKClassNameInstructorIDFKRoomINSTRUCTORInstructorIDPKNamePhone
09
Original Table
PurchIDCustEmailCustNameProductCategoryCatDiscountQtyDate
P01jo@mail.comJoKeyboardPeripherals10%201/03/26
P02jo@mail.comJoMonitorDisplays5%102/03/26
P03sam@mail.comSamMousePeripherals10%303/03/26
Step 1 — 1NF: Check each cell — does any contain multiple values? No, every cell has exactly one value. ✓ Already in 1NF.
⬇ Table stays the same
Step 1 check — table unchanged
PurchIDCustEmailCustNameProductCategoryCatDiscountQtyDate
P01jo@mail.comJoKeyboardPeripherals10%201/03/26
P02jo@mail.comJoMonitorDisplays5%102/03/26
P03sam@mail.comSamMousePeripherals10%303/03/26
Step 2 — 2NF: Is there a composite key? No — the primary key is PurchID (single column). Partial dependencies only occur with composite keys. ✓ Already in 2NF.
⬇ Table stays the same
Step 2 check — table unchanged
PurchIDCustEmailCustNameProductCategoryCatDiscountQtyDate
P01jo@mail.comJoKeyboardPeripherals10%201/03/26
P02jo@mail.comJoMonitorDisplays5%102/03/26
P03sam@mail.comSamMousePeripherals10%303/03/26
Step 3 — 3NF: For each non-key column, ask: "does this describe the PK or another column?"
• CustName depends on CustEmail, not on PurchID — transitive dependency (Jo's name repeats whenever jo@mail.com appears).
• Category depends on Product, not on PurchID — transitive dependency (Keyboard is always Peripherals).
• CatDiscount depends on Category, not on Product — this is a double transitive chain: PurchID → Product → Category → CatDiscount (10% belongs to all Peripherals, not per product).
Fix: extract CUSTOMER, CATEGORY, and PRODUCT tables.
⬇ 3NF result — four tables
CUSTOMER
CustIDCustEmailCustName
C01jo@mail.comJo
C02sam@mail.comSam
CATEGORY
CatIDCatNameCatDiscount
CAT01Peripherals10%
CAT02Displays5%
PRODUCT
ProdIDProdNameCatID (FK)
PR01KeyboardCAT01
PR02MonitorCAT02
PR03MouseCAT01
PURCHASE
PurchIDCustID (FK)ProdID (FK)QtyDate
P01C01PR01201/03/26
P02C01PR02102/03/26
P03C02PR03303/03/26
Result: 4 tables in 3NF. Each fact stored once — changing Peripherals discount from 10% to 15% updates one row in CATEGORY.
Table Relationships
1NN1N1CUSTOMERCustIDPKCustEmailCustNamePURCHASEPurchIDPKCustIDFKProdIDFKQtyDatePRODUCTProdIDPKProdNameCatIDFKCATEGORYCatIDPKCatNameCatDiscount
10
Original Table
ReservIDGuestGuestEmailCheckInRoomNoRoomTypeRateServices
R01Liamliam@mail.com01/03101Single€80Breakfast €15, Spa €40
R02Miamia@mail.com02/03205Double€120Breakfast €15, Gym €10, Laundry €25
R03Liamliam@mail.com10/03101Single€80Gym €10
Step 1 — 1NF: Services column has multiple values (Breakfast €15, Spa €40). This is a repeating group. Fix: move services to a new RESERVATION_SERVICE table with composite key (ReservID + ServiceName).
⬇ 1NF result
1NF — All values atomic (one per cell)
ReservIDGuestGuestEmailCheckInRoomNoRoomTypeRateServiceNameServicePrice
R01Liamliam@mail.com01/03101Single€80Breakfast€15
R01Liamliam@mail.com01/03101Single€80Spa€40
R02Miamia@mail.com02/03205Double€120Breakfast€15
R02Miamia@mail.com02/03205Double€120Gym€10
R02Miamia@mail.com02/03205Double€120Laundry€25
R03Liamliam@mail.com10/03101Single€80Gym€10
Notice the redundancy: Liam's details repeat for each service. Room 101's type and rate repeat. Breakfast's price (€15) appears twice. Next: split into RESERVATION + RESERVATION_SERVICE, linked by ReservID.
⬇ Split into separate tables
RESERVATION (after 1NF)
ReservIDGuestGuestEmailCheckInRoomNoRoomTypeRate
R01Liamliam@mail.com01/03101Single€80
R02Miamia@mail.com02/03205Double€120
R03Liamliam@mail.com10/03101Single€80
RESERVATION_SERVICE (after 1NF)
ReservIDServiceNamePrice
R01Breakfast€15
R01Spa€40
R02Breakfast€15
R02Gym€10
R02Laundry€25
R03Gym€10
Step 2 — 2NF: RESERVATION_SERVICE has composite key (ReservID, ServiceName). Price depends only on ServiceName (Breakfast is always €15) — partial dependency. Fix: extract SERVICE table.
⬇ 2NF result
SERVICE
ServiceIDServiceNamePrice
SV01Breakfast€15
SV02Spa€40
SV03Gym€10
SV04Laundry€25
RESERVATION_SERVICE (after 2NF)
ReservID (FK)ServiceID (FK)
R01SV01
R01SV02
R02SV01
R02SV03
R02SV04
R03SV03
Step 3 — 3NF: In RESERVATION: Guest & GuestEmail depend on the guest (not ReservID) — transitive. RoomType & Rate depend on RoomNo (not ReservID) — transitive. Fix: extract GUEST and ROOM tables.
⬇ 3NF result — five tables
GUEST
GuestIDGuestNameGuestEmail
G01Liamliam@mail.com
G02Miamia@mail.com
ROOM
RoomNoRoomTypeRate
101Single€80
205Double€120
RESERVATION
ReservIDGuestID (FK)RoomNo (FK)CheckIn
R01G0110101/03
R02G0220502/03
R03G0110110/03
RESERVATION_SERVICE
ReservID (FK)ServiceID (FK)
R01SV01
R01SV02
R02SV01
R02SV03
R02SV04
R03SV03
Result: 5 tables in 3NF. All three steps used: 1NF (repeating group), 2NF (partial dependency), 3NF (two transitive chains). Each fact stored once.
Table Relationships
1NN11NN1GUESTGuestIDPKGuestNameGuestEmailRESERVATIONReservIDPKGuestIDFKRoomNoFKCheckInROOMRoomNoPKRoomTypeRateRESERV_SERVICEReservIDFKServiceIDFKSERVICEServiceIDPKServiceNamePrice
11
Original Table
StudentIDNameAddressDeptCodeDeptNameModules
S001AvaDublinCSComp SciM101 Databases (Dr Ryan, B+), M102 Networks (Dr Kelly, A)
S002NoahCorkCSComp SciM101 Databases (Dr Ryan, A-), M103 Web Dev (Dr Murphy, B)
S003EllaGalwayBABusinessM201 Marketing (Dr Walsh, A), M101 Databases (Dr Ryan, B+)
Step 1 — 1NF: Modules column packs multiple values (code, name, lecturer, grade per module). Repeating group found. Fix: create ENROLMENT table. Composite key = (StudentID + ModCode).
⬇ 1NF result
1NF — All values atomic (one per cell)
StudentIDNameAddressDeptCodeDeptNameModCodeModNameLecturerGrade
S001AvaDublinCSComp SciM101DatabasesDr RyanB+
S001AvaDublinCSComp SciM102NetworksDr KellyA
S002NoahCorkCSComp SciM101DatabasesDr RyanA-
S002NoahCorkCSComp SciM103Web DevDr MurphyB
S003EllaGalwayBABusinessM201MarketingDr WalshA
S003EllaGalwayBABusinessM101DatabasesDr RyanB+
Notice the redundancy: Ava's details repeat for each module. DeptName 'Comp Sci' repeats for every CS student. Module M101 'Databases' and 'Dr Ryan' repeat for each student enrolled. Next: split into STUDENT + ENROLMENT, linked by StudentID.
⬇ Split into separate tables
STUDENT (after 1NF)
StudentIDNameAddressDeptCodeDeptName
S001AvaDublinCSComp Sci
S002NoahCorkCSComp Sci
S003EllaGalwayBABusiness
ENROLMENT (after 1NF)
StudentIDModCodeModNameLecturerGrade
S001M101DatabasesDr RyanB+
S001M102NetworksDr KellyA
S002M101DatabasesDr RyanA-
S002M103Web DevDr MurphyB
S003M201MarketingDr WalshA
S003M101DatabasesDr RyanB+
Step 2 — 2NF: Composite key (StudentID + ModCode). Ask: does ModName need both keys? No — M101 is always "Databases" regardless of which student. Same for Lecturer. Partial dependency. Fix: extract MODULE table. ENROLMENT keeps only Grade (depends on both keys).
⬇ 2NF result
MODULE
ModCodeModNameLecturer
M101DatabasesDr Ryan
M102NetworksDr Kelly
M103Web DevDr Murphy
M201MarketingDr Walsh
ENROLMENT (after 2NF)
StudentID (FK)ModCode (FK)Grade
S001M101B+
S001M102A
S002M101A-
S002M103B
S003M201A
S003M101B+
Step 3 — 3NF: In STUDENT: DeptName depends on DeptCode (not StudentID) — transitive dependency. CS is always "Comp Sci". Fix: extract DEPARTMENT table.
⬇ 3NF result — four tables
DEPARTMENT
DeptCodeDeptName
CSComp Sci
BABusiness
STUDENT
StudentIDNameAddressDeptCode (FK)
S001AvaDublinCS
S002NoahCorkCS
S003EllaGalwayBA
MODULE
ModCodeModNameLecturer
M101DatabasesDr Ryan
M102NetworksDr Kelly
M103Web DevDr Murphy
M201MarketingDr Walsh
ENROLMENT
StudentID (FK)ModCode (FK)Grade
S001M101B+
S001M102A
S002M101A-
S002M103B
S003M201A
S003M101B+
Result: 4 tables in 3NF. All three steps used: 1NF (repeating modules), 2NF (partial dep on ModCode), 3NF (transitive dep on DeptCode). Grade is the only attribute that genuinely needs both parts of the composite key.
Table Relationships
1N1NN1DEPARTMENTDeptCodePKDeptNameSTUDENTStudentIDPKNameAddressDeptCodeFKENROLMENTStudentIDFKModCodeFKGradeMODULEModCodePKModNameLecturer
20
REFERENCES

References

Sources used to build this material

01
Database Normalization: 1NF, 2NF, 3NF & BCNF Examples
Comprehensive tutorial with visual examples for each normal form
Accessed: 22 March 2026
02
Database Normalization – Normal Forms 1nf 2nf 3nf Table Examples
Step-by-step normalisation walkthrough with practical table examples
Accessed: 22 March 2026
03
Normal Forms in DBMS
Reference guide covering all normal forms with definitions and examples
Accessed: 22 March 2026
04
DBMS Normalization: 1NF, 2NF, 3NF Database Example
Beginner-friendly normalisation tutorial with diagrams and SQL examples
Accessed: 22 March 2026