Schema Design· 20 min

Normalization (1NF to BCNF)

Reduce redundancy and anomalies

NormalizationIntermediate20 min read

Process of organizing tables to reduce redundancy and dependency anomalies. Normal forms progress from 1NF to BCNF.

Normalization (1NF to BCNF) — diagram
Unnormalizedid | cust_name | cust_email | items[]⚠ Redundancy · Update anomalynormalizecustomerscustomer_id PKnameemailordersorder_id PKcustomer_id FKtotalorder_itemsitem_id PKorder_id FKproduct_nameqty, priceFKFK3NF: "Nothing but the key, the whole key, and nothing but the key"1NF: atomic · 2NF: no partial dep · 3NF: no transitive dep

Explanation

1NF: each column holds atomic values. 2NF: 1NF + no partial dependency. 3NF: 2NF + no transitive dependency. BCNF: 3NF + every determinant is a candidate key. Higher normal forms reduce redundancy but require more joins.

Example

CREATE TABLE customers (id INT PRIMARY KEY, name VARCHAR(100), email VARCHAR(100) UNIQUE);
CREATE TABLE orders (id INT PRIMARY KEY, customer_id INT REFERENCES customers(id), total DECIMAL(10,2));
CREATE TABLE order_items (id INT PRIMARY KEY, order_id INT REFERENCES orders(id), product_name VARCHAR(100), quantity INT, price DECIMAL(10,2));

Key points

  • →1NF: no repeating groups
  • →2NF: no partial dependencies
  • →3NF: no transitive dependencies
  • →BCNF: every determinant is a candidate key
  • →Denormalize for read-heavy workloads

Quick interview answers

When to denormalize?
When reads vastly outnumber writes and joins are expensive. Data warehouses are typically denormalized.
3NF vs BCNF?
BCNF catches a rare 3NF edge case. Most 3NF tables are also BCNF.

DBMS deep dive

Normalization (1NF to BCNF): deep dive

Normalization breaks one wide table into smaller ones, so each fact is stored exactly once. The easiest way to learn it is to take one messy table and fix it one normal form at a time, naming the dependency that each step removes.

The starting point: one messy table

A college tracks enrollments in a single spreadsheet-style table. Here are three rows:

student_idstudent_namecoursesinstructorinstructor_office
S1AshaDB101, OS201Rao, IyerB-12, C-04
S2RaviDB101RaoB-12
S3MeenaOS201IyerC-04

This table has all three classic anomalies. Update anomaly: if Rao moves office, you must edit every row that mentions Rao. Insert anomaly: you cannot record a new course until a student enrolls in it. Delete anomaly: if Meena drops OS201, the fact that Iyer sits in C-04 may vanish with her.

1NF: one value per cell

The courses column holds a list, so the table is not in 1NF. Split it so each row is one student in one course, and add a grade. The key becomes (student_id, course_id). The functional dependencies are now:

  • student_id → student_name
  • course_id → course_title, instructor
  • instructor → instructor_office
  • (student_id, course_id) → grade

2NF: remove partial dependencies

2NF only matters when the key is composite. student_name depends on just student_id, which is part of the key, and course_title depends on just course_id. These are partial dependencies. Move each group to a table keyed by the attribute it actually depends on:

  • students(student_id, student_name)
  • courses(course_id, course_title, instructor, instructor_office)
  • enrollments(student_id, course_id, grade)

3NF: remove transitive dependencies

In courses, course_id → instructor → instructor_office. The office describes the instructor, not the course, so it is a transitive dependency on the key. Split it out:

Schema after 3NF
CREATE TABLE students    (student_id TEXT PRIMARY KEY, student_name TEXT NOT NULL);
CREATE TABLE instructors (instructor TEXT PRIMARY KEY, office TEXT NOT NULL);
CREATE TABLE courses (
  course_id    TEXT PRIMARY KEY,
  course_title TEXT NOT NULL,
  instructor   TEXT NOT NULL REFERENCES instructors(instructor)
);
CREATE TABLE enrollments (
  student_id TEXT REFERENCES students(student_id),
  course_id  TEXT REFERENCES courses(course_id),
  grade      CHAR(2),
  PRIMARY KEY (student_id, course_id)
);

All three anomalies are gone. Rao's office lives in one row, a course can exist with no students, and deleting an enrollment deletes nothing else. A handy test for 3NF: every non-key attribute must depend on the key, the whole key, and nothing but the key.

BCNF: when 3NF is not enough

Now add a new rule: each tutor teaches exactly one subject, and each student has one tutor per subject. Table tutoring(student, subject, tutor) has the dependencies (student, subject) → tutor and tutor → subject. Both (student, subject) and (student, tutor) are candidate keys, so every attribute is part of some key. That makes the table 3NF, because 3NF allows a dependency whose right side is a prime attribute.

BCNF does not allow it. tutor → subject has a determinant, tutor, that is not a superkey. The redundancy is real: 'Rao teaches DB' is repeated for every student Rao tutors. Decompose into tutor_subject(tutor, subject) and student_tutor(student, tutor). The decomposition is lossless, but (student, subject) → tutor can no longer be checked inside one table. This is the trade-off interviewers want you to state: 3NF can always preserve dependencies, but BCNF sometimes cannot.

Normal formRuleWhat it removed here
1NFAtomic values, no repeating groupsComma-separated course list
2NFNo non-key attribute depends on part of a composite keystudent_name, course_title in the enrollment row
3NFNo non-key attribute depends on another non-key attributeinstructor_office via instructor
BCNFEvery determinant is a superkeytutor → subject redundancy

Common mistakes

  • Calling a table 2NF-violating when its key is a single column. Partial dependencies need a composite key.
  • Reading normal forms from sample data instead of from the business rules. Dependencies are constraints, not coincidences in today's rows.
  • Normalizing derived values away and then recomputing them with expensive aggregates on every read. Sometimes a cached total is the right call.
  • Forgetting that a normalized schema needs indexes on its foreign keys. Without them the extra joins get slow. See database indexes.

Interview questions

›What is a functional dependency?

X → Y means that any two rows with the same X value must have the same Y value. It is a rule about every valid state of the table, not just the data currently in it.

›What is a lossless-join decomposition?

A split of R into R1 and R2 is lossless if joining them back gives exactly R, with no spurious rows. That holds when the shared attributes form a key of at least one of the two tables.

›How do you find candidate keys from a set of dependencies?

Compute attribute closures. Attributes that never appear on the right side of any dependency must be in every key. Start from them and add attributes until the closure covers the whole relation, then keep only the minimal sets.

›Can a table with a single-column key violate 3NF?

Yes. It cannot violate 2NF, but it can still have a transitive dependency, such as employee_id → dept_id → dept_name in one table.

›What are 4NF and multivalued dependencies?

A multivalued dependency means one attribute determines a set of values independent of another set, such as a person's phone numbers and languages stored in one table. 4NF removes these by splitting the independent sets into separate tables.

More Database Management Systems

Compare this lesson with the other 8 concepts in the course. Expand any card for its design, trade-offs and diagram.

Category:
Difficulty:
Showing 8 of 8