Database Normalization
Introduction
Section titled “Introduction”Database normalization is the systematic process of structuring a relational schema to minimize data Redundancy and eliminate insertion, deletion, and update anomalies. The theory was introduced by E.F. Codd in 1970 and formalized through a series of normal forms, each defined in terms of Functional dependencies on the relation.
The core idea is simple: every piece of data should live in exactly one place. If the same fact Appears in multiple rows, updating that fact requires updating every row that contains it. If you Miss one, your data is inconsistent. Normalization gives you a principled, mathematical framework For avoiding this class of problems.
Why Normalization Matters
Section titled “Why Normalization Matters”Unnormalized schemas suffer from three categories of anomalies:
| Anomaly Type | Description | Concrete Example |
|---|---|---|
| Insertion | Cannot add a fact without adding unrelated facts | Cannot record a new department until at least one employee is assigned to it |
| Deletion | Deleting one fact unintentionally removes another | Deleting the last employee in a department also removes the department”s address |
| Update | Updating a single fact requires touching multiple rows | Renaming a department requires updating every employee row in that department |
These are not theoretical concerns. In production systems with millions of rows, an update anomaly Means a single UPDATE statement that touches 50,000 rows, requires a table lock, and risks partial Failure. Normalization eliminates these problems at the schema level rather than relying on Application logic to keep data consistent.
Normalization vs Denormalization
Section titled “Normalization vs Denormalization”Normalization and denormalization are not opposites in the sense that one is “right” and the other Is “wrong.” They are engineering tradeoffs:
Normalized schema: + Each fact stored once (single source of truth) + No update anomalies + Smaller individual tables + Schema is self-documenting through foreign keys - Queries require JOINs (higher read latency) - More complex queries for simple reports
Denormalized schema: + Fewer JOINs (lower read latency for common access patterns) + Simpler queries for reporting and dashboards - Data duplication (write amplification) - Update anomalies require application-level consistency logic - Higher storage costThe default starting point for any OLTP system is 3NF. Denormalize only after measuring a specific Performance bottleneck and understanding the consistency cost. Premature denormalization creates Maintenance debt that compounds over time.
Normal Form Hierarchy
Section titled “Normal Form Hierarchy”Each normal form is a strict subset of the one below it:
1NF \supset 2NF \supset 3NF \supset \mathrm{BCNF \supset 4NF \supset 5NF
A relation in BCNF is automatically in 3NF, 2NF, and 1NF. The higher the normal form, the less Redundancy, but the more relations you need (and the more JOINs you must perform).
Functional Dependencies
Section titled “Functional Dependencies”Functional dependencies are the mathematical foundation on which all normal forms are built. Before Discussing any normal form, you must understand FDs thoroughly.
Formal Definition
Section titled “Formal Definition”Definition. Given a relation schema and two attribute sets and A functional dependency holds on if and only if, for every Pair of tuples and in any legal instance of :
In plain language: if two tuples agree on all attributes in They must also agree on all Attributes in . is called the determinant and is called the dependent.
Trivial vs Non-trivial Dependencies
Section titled “Trivial vs Non-trivial Dependencies”Definition. A functional dependency is:
- Trivial if . It holds for every relation by definition and carries no informational content. Example: .
- Non-trivial if . It carries actual semantic information about the data.
- Completely non-trivial if .
Full vs Partial vs Transitive Dependencies
Section titled “Full vs Partial vs Transitive Dependencies”These three categories are critical for understanding the progression from 2NF to 3NF:
Definition. Let be a candidate key of relation .
- A full functional dependency means that depends on all of . Removing any attribute from destroys the dependency. Formally, for no proper subset does hold.
- A partial functional dependency means that depends on only a proper subset of . There exists some such that holds.
- A transitive dependency occurs when and both hold, and depends on only through the intermediate . Formally, holds because and But neither nor .
Relation: OrderItem(order_id, product_id, quantity, product_name, category_name)
Full dependency: {order_id, product_id} -> quantity (quantity depends on the ENTIRE composite key)
Partial dependency: product_id -> product_name, category_name (product_name depends on only product_id, a SUBSET of the key)
Transitive dependency: product_id -> category_id -> category_name (category_name depends on product_id only through category_id)Armstrong’s Axioms
Section titled “Armstrong’s Axioms”Armstrong’s axioms are a sound and complete set of inference rules for deriving all functional Dependencies that are logically implied by a given set .
Definition. Given a set of functional dependencies on a relation schema The three Axioms are:
- Reflexivity (A1): If Then .
- Augmentation (A2): If Then for any attribute set .
- Transitivity (A3): If and Then .
These three axioms alone are sound (every derived dependency is correct) and complete (every correct Dependency can be derived from them).
Derived Rules
Section titled “Derived Rules”The following rules are not axioms but can be proven from Armstrong’s three axioms. They are used Constantly in normalization proofs:
| Rule | Statement | Proof Strategy |
|---|---|---|
| Union | If and Then | Augmentation + Transitivity |
| Decomposition | If Then and | Reflexivity + Transitivity |
| Pseudotransitivity | If and Then | Augmentation + Transitivity |
| Composition | If and Then | Augmentation + Union |
Proof of the Union rule:
\begin{aligned} X \rightarrow Y \quad &\mathrm{(given) \\ X \rightarrow X \quad &\mathrm{(reflexivity, since X \subseteq X) \\ XX \rightarrow YX \quad &\mathrm{(augmentation, add X \mathrm{ to both sides) \\ X \rightarrow XY \quad &\mathrm{(since XX = X) \\ XY \rightarrow YZ \quad &\mathrm{(augmentation of X \rightarrow Z \mathrm{ with Y) \\ X \rightarrow YZ \quad &\mathrm{(transitivity) \end{aligned}Attribute Closure
Section titled “Attribute Closure”Definition. The closure of an attribute set under a set of functional dependencies Denoted Is the largest attribute set such that can be derived from Using Armstrong’s axioms.
Algorithm to compute :
function attributeClosure(X, F): X⁺ = X repeat until X⁺ stops changing: for each FD Y -> Z in F: if Y ⊆ X⁺: X⁺ = X⁺ ∪ Z return X⁺Key property: is logically implied by if and only if .
This algorithm is used for two critical tasks: determining whether an attribute set is a superkey (I.e., the closure equals the entire relation schema), and determining whether a specific FD is implied by (check whether ).
Relation: R(A, B, C, D, E)FDs: {A -> B, BC -> D, D -> E}
Compute A⁺: A⁺ = {A} A -> B: A ⊆ A⁺, so A⁺ = {A, B} BC -> D: {B,C} ⊈ A⁺, skip D -> E: D ⊈ A⁺, skip No more changes. A⁺ = {A, B} A is NOT a superkey.
Compute AC⁺: AC⁺ = {A, C} A -> B: A ⊆ AC⁺, so AC⁺ = {A, B, C} BC -> D: {B,C} ⊆ AC⁺, so AC⁺ = {A, B, C, D} D -> E: D ⊆ AC⁺, so AC⁺ = {A, B, C, D, E} AC⁺ = R. AC is a superkey.Closure of a Set of FDs
Section titled “Closure of a Set of FDs”Definition. The closure of a set of functional dependencies Denoted Is the set of All FDs that can be derived from using Armstrong’s axioms.
can be exponentially large (up to FDs for attributes), so you never compute it Explicitly. Instead, you use the attribute closure algorithm to answer specific questions about Whether a given FD is in .
Candidate Keys from FDs
Section titled “Candidate Keys from FDs”To find all candidate keys of a relation given a set of FDs :
- Compute the closure of each attribute and each combination of attributes.
- An attribute set is a superkey if .
- A superkey is a candidate key if it is minimal: removing any attribute from yields .
Relation: R(A, B, C, D)FDs: {A -> C, C -> B, D -> C}
Compute closures of single attributes: A⁺ = {A, C, B} (A -> C -> B) B⁺ = {B} C⁺ = {C, B} (C -> B) D⁺ = {D, C, B} (D -> C -> B)
None of A, B, C, D alone is a superkey (none has D in its closure except D⁺,but D⁺ = {D,C,B} which lacks A).
Check pairs: AD⁺ = {A, D, C, B} = R. AD is a superkey. Is AD minimal? A⁺ = {A, C, B} ≠ R (missing D). So A alone is not a superkey. D⁺ = {D, C, B} ≠ R (missing A). So D alone is not a superkey. AD is a candidate key.
BD⁺ = {B, D, C} ≠ R (missing A). Not a superkey. AB⁺ = {A, B, C} ≠ R (missing D). Not a superkey. CD⁺ = {C, D, B} ≠ R (missing A). Not a superkey. AC⁺ = {A, C, B} ≠ R (missing D). Not a superkey.
The only candidate key is {A, D}.Prime attributes: A, D.Non-prime attributes: B, C.Minimal Cover (Canonical Cover)
Section titled “Minimal Cover (Canonical Cover)”Definition. A minimal cover of a set of FDs satisfies three conditions:
- Right-side decomposition: Every FD in has exactly one attribute on the right side. (Replace with and .)
- No redundant FDs: Removing any FD from changes the closure. For each , .
- No redundant attributes on the left side: For each FD in and each attribute , does not contain . In other words, removing any attribute from the left side would destroy the dependency.
The minimal cover is not necessarily unique, but all minimal covers of are equivalent.
Algorithm to compute :
1. START with F.2. DECOMPOSE right sides: Replace each X -> {A₁, A₂, ..., Aₙ} with X -> A₁, X -> A₂, ..., X -> Aₙ.3. REMOVE redundant FDs: For each FD f in F: Temporarily remove f from F. Compute closure of f's left side using the remaining FDs. If the closure still contains f's right side, f is redundant -- remove it permanently. Otherwise, restore f.4. REMOVE redundant attributes from left sides: For each FD X -> A in F: For each attribute B in X: Temporarily remove B from X. Compute closure of (X - {B}) using the full set of FDs. If the closure contains A, B is redundant -- remove it permanently from X. Otherwise, restore B.5. RETURN the result.Example: Compute minimal cover of F = {AB -> C, C -> B, A -> B}
Step 1: Right sides already single-attribute.
Step 2: Remove redundant FDs: Remove AB -> C: Compute (AB)⁺ using {C -> B, A -> B}: AB⁺ = {A, B}. Does NOT contain C. Restore AB -> C. Remove C -> B: Compute C⁺ using {AB -> C, A -> B}: C⁺ = {C}. Does NOT contain B. Restore C -> B. Remove A -> B: Compute A⁺ using {AB -> C, C -> B}: A⁺ = {A}. Does NOT contain B. Restore A -> B. No redundant FDs.
Step 3: Remove redundant attributes: AB -> C: Remove A: compute B⁺ using {B -> ?, C -> B, A -> B}: B⁺ = {B}. Does NOT contain C. A is not redundant. Remove B: compute A⁺ using {A -> ?, C -> B, A -> B}: A⁺ = {A, B, C} (A -> B, AB -> C). Contains C. B IS redundant. Replace AB -> C with A -> C.
Result: F_min = {A -> C, C -> B, A -> B}
Verify: Is A -> B still redundant? Remove A -> B: Compute A⁺ using {A -> C, C -> B}: A⁺ = {A, C, B}. Contains B. A -> B IS redundant now.
Remove A -> B: F_min = {A -> C, C -> B}
Final minimal cover: {A -> C, C -> B}SQL databases that support array types (PostgreSQL INTEGER[]JSONB) technically allow violations Of 1NF. This is a pragmatic extension. Use these types when the array is opaque data that you never Need to query or join on individually. If you need to query individual elements or enforce Referential integrity, model them as separate rows.
Second Normal Form (2NF)
Section titled “Second Normal Form (2NF)”Definition. A relation is in second normal form (2NF) if and only if:
- is in 1NF, and
- No non-prime attribute is partially dependent on any candidate key.
A non-prime attribute is an attribute that does not belong to any candidate key. A partial Dependency exists when a non-prime attribute depends on only a proper subset of a candidate key (rather than the entire key).
4NF violations are rare in practice. They appear when modeling entity-attribute-value Patterns or when a single entity has multiple independent multi-valued attributes. If you see a Table where adding a row requires adding rows (for values of one attribute and Values of another), you likely have a 4NF violation.
Fifth Normal Form (5NF) / Project-Join Normal Form (PJNF)
Section titled “Fifth Normal Form (5NF) / Project-Join Normal Form (PJNF)”Definition. A relation is in fifth normal form (5NF) if and only if, for every non-trivial Join dependency that holds in Each is a superkey of .
A join dependency generalizes the concept of lossless-join decomposition to relations. A Relation satisfies a join dependency if and only if is equal to The natural join of its projections on :
5NF violations are extremely rare. They arise in ternary (or higher-arity) relationships where the Constraint is inherently multi-way and cannot be decomposed into binary relationships without losing Information.
Classic 5NF example: Supplier-Part-Project
Relation: Supply(supplier, part, project)
Business rule: if a supplier supplies a certain part AND works on a certain project,then that supplier supplies that part for that project.
This means: (S₁, P₁) ∈ Supply AND (S₁, J₁) ∈ Supply IMPLIES (S₁, P₁, J₁) ∈ Supply
Sample data satisfying the rule:| supplier | part | project ||----------|------|---------|| S1 | P1 | J1 || S1 | P1 | J2 || S1 | P2 | J1 |
Decompose into binary relations: SP(supplier, part): {(S1,P1), (S1,P2)} SJ(supplier, project): {(S1,J1), (S1,J2)} PJ(part, project): {(P1,J1), (P1,J2), (P2,J1)}
Join SP ⋈ SJ ⋈ PJ: (S1,P1) ⋈ (S1,J1) ⋈ (P1,J1) -> (S1,P1,J1) -- was in original (S1,P1) ⋈ (S1,J1) ⋈ (P2,J1) -> (S1,P1,J1) -- wait, this needs checking (S1,P1) ⋈ (S1,J2) ⋈ (P1,J2) -> (S1,P1,J2) -- was in original (S1,P2) ⋈ (S1,J1) ⋈ (P2,J1) -> (S1,P2,J1) -- was in original
The join SP ⋈ SJ gives: {(S1,P1,J1), (S1,P1,J2), (S1,P2,J1), (S1,P2,J2)} But (S1,P2,J2) was NOT in the original relation!
This spurious tuple means the decomposition is LOSSY. The relation is in 5NF because it cannot be decomposed without losing information.Normal Form Summary
Section titled “Normal Form Summary”| Normal Form | Eliminates | Condition | Practical Relevance |
|---|---|---|---|
| 1NF | Repeating groups, non-atomic values | Every attribute is atomic | Mandatory for any relational database |
| 2NF | Partial dependencies on composite keys | No non-prime attribute partially dependent on any candidate key | Only matters with composite keys |
| 3NF | Transitive dependencies | For : is a superkey OR is prime | Standard target for OLTP schemas |
| BCNF | Determinants that are not superkeys (prime attribute exception) | For every non-trivial : is a superkey | Apply when possible; may sacrifice dependency preservation |
| 4NF | Multi-valued dependencies | For every non-trivial : is a superkey | Rare; occurs with independent multi-valued attributes |
| 5NF | Join dependencies not implied by candidate keys | For every non-trivial JD: each component is a superkey | Extremely rare; mostly theoretical |
Normalization Examples
Section titled “Normalization Examples”Worked Example: University Course Registration
Section titled “Worked Example: University Course Registration”Walk through normalizing a real-world schema from unnormalized to BCNF.
Starting Point: Unnormalized Data
Section titled “Starting Point: Unnormalized Data”CourseRegistration(student_id, student_name, student_email, course_id, course_title, course_credits, instructor_id, instructor_name, instructor_office, semester, grade)
Sample row:(1001, "Alice", "alice@univ.edu", CS101, "Intro to CS", 3, I5, "Prof Smith", "Room 301", "Fall 2025", "A")Step 1: Identify Functional Dependencies
Section titled “Step 1: Identify Functional Dependencies”From the business rules:
student_id -> student_name, student_emailcourse_id -> course_title, course_creditsinstructor_id -> instructor_name, instructor_office{student_id, course_id, semester} -> gradecourse_id, semester -> instructor_idStep 2: Identify Candidate Keys
Section titled “Step 2: Identify Candidate Keys”Compute closure of {student_id, course_id, semester}: {student_id, course_id, semester}⁺ = {student_id, student_name, student_email, course_id, course_title, course_credits, instructor_id, instructor_name, instructor_office, semester, grade} = R
Is it minimal? Remove student_id: {course_id, semester}⁺ = {course_id, course_title, course_credits, instructor_id, instructor_name, instructor_office, semester} ≠ R.Remove course_id: {student_id, semester}⁺ = {student_id, student_name, student_email, semester} ≠ R.Remove semester: {student_id, course_id}⁺ = {student_id, student_name, student_email, course_id, course_title, course_credits} ≠ R.
Candidate key: {student_id, course_id, semester}Prime attributes: student_id, course_id, semesterNon-prime attributes: everything elseStep 3: Verify 1NF
Section titled “Step 3: Verify 1NF”All attributes are atomic. The relation is in 1NF.
Step 4: Check for 2NF Violations (Partial Dependencies)
Section titled “Step 4: Check for 2NF Violations (Partial Dependencies)”{student_id, course_id, semester} -> student_name But student_id -> student_name (partial -- depends on subset of key) VIOLATES 2NF.
{student_id, course_id, semester} -> course_title, course_credits But course_id -> course_title, course_credits (partial) VIOLATES 2NF.
{student_id, course_id, semester} -> instructor_id But {course_id, semester} -> instructor_id (partial) VIOLATES 2NF.
{student_id, course_id, semester} -> grade No subset of the key determines grade. FULL dependency. OK.Step 5: Decompose to 2NF
Section titled “Step 5: Decompose to 2NF”R1(student_id, course_id, semester, grade) FDs: {student_id, course_id, semester} -> grade Key: {student_id, course_id, semester} No partial dependencies. In 2NF.
R2(student_id, student_name, student_email) FDs: student_id -> student_name, student_email Key: {student_id} Single-attribute key, automatically in 2NF.
R3(course_id, course_title, course_credits) FDs: course_id -> course_title, course_credits Key: {course_id} Single-attribute key, automatically in 2NF.
R4(course_id, semester, instructor_id) FDs: {course_id, semester} -> instructor_id Key: {course_id, semester} Single non-key attribute. In 2NF.
R5(instructor_id, instructor_name, instructor_office) FDs: instructor_id -> instructor_name, instructor_office Key: {instructor_id} Single-attribute key, automatically in 2NF.Step 6: Check for 3NF Violations (Transitive Dependencies)
Section titled “Step 6: Check for 3NF Violations (Transitive Dependencies)”R1: {student_id, course_id, semester} -> grade. No transitive dependency. 3NF.R2: student_id -> student_name, student_email. No transitive dependency. 3NF.R3: course_id -> course_title, course_credits. No transitive dependency. 3NF.R4: {course_id, semester} -> instructor_id. No transitive dependency. 3NF.R5: instructor_id -> instructor_name, instructor_office. No transitive dependency. 3NF.
All relations are in 3NF.Step 7: Check for BCNF Violations
Section titled “Step 7: Check for BCNF Violations”R1: Only FD is {student_id, course_id, semester} -> grade. Determinant is the key. BCNF.R2: Only FD is student_id -> student_name, student_email. Determinant is the key. BCNF.R3: Only FD is course_id -> course_title, course_credits. Determinant is the key. BCNF.R4: Only FD is {course_id, semester} -> instructor_id. Determinant is the key. BCNF.R5: Only FD is instructor_id -> instructor_name, instructor_office. Determinant is the key. BCNF.All five relations are in BCNF. The decomposition is both lossless and dependency-preserving.
Worked Example: BCNF Violation
Section titled “Worked Example: BCNF Violation”Relation: Booking(room_number, date, guest_name, guest_passport)
Business rules: - A room can have at most one booking per date - A guest has exactly one passport number - A guest can book multiple rooms on different dates
FDs: {room_number, date} -> guest_name, guest_passport guest_name -> guest_passport
Candidate keys: {room_number, date} ({room_number, date}⁺ = R) {room_number, date, guest_name} (obviously a superkey, but not minimal)
Check: is {room_number, guest_name} a candidate key? {room_number, guest_name}⁺ = {room_number, guest_name, guest_passport} ≠ R (missing date) Not a superkey.
Only candidate key: {room_number, date}Prime attributes: room_number, dateNon-prime attributes: guest_name, guest_passport
3NF check: {room_number, date} -> guest_name: superkey. OK. {room_number, date} -> guest_passport: superkey. OK. guest_name -> guest_passport: guest_name is NOT a superkey. BUT guest_passport is NOT a prime attribute. VIOLATES 3NF.
Wait -- this also violates 3NF because guest_passport is non-prime.Let me reconsider: guest_passport depends transitively on the key through guest_name. {room_number, date} -> guest_name -> guest_passport. This is a transitive dependency. 3NF violation.
Decomposition: R1(room_number, date, guest_name) -- key: {room_number, date} R2(guest_name, guest_passport) -- key: {guest_name}
Both in BCNF. Lossless: R1 ∩ R2 = {guest_name}, guest_name -> guest_passport,so {guest_name} is a superkey for R2. Lossless.Dependency-preserving: both FDs are checkable on individual relations.Decomposition
Section titled “Decomposition”Decomposition is the mechanism by which normalization is achieved: you replace a single relation With two or more smaller relations. Not every decomposition is correct. A decomposition must satisfy Two properties to be valid.
Lossless-Join Decomposition
Section titled “Lossless-Join Decomposition”Definition. A decomposition of relation into and is a lossless-join Decomposition if and only if, for every legal instance of :
That is, joining the decomposed relations produces exactly the original relation, with no spurious Tuples and no missing tuples.
Theorem. A decomposition of into and is lossless if and only if at least one of The following holds:
In words: the common attributes must form a superkey for at least one of the decomposed relations.
R(A, B, C, D) with FDs: {A -> B, B -> C}
Decompose into R1(A, B) and R2(B, C, D): R1 ∩ R2 = {B} B -> C holds in F (and thus in R2). But is B a superkey for R2? B⁺ = {B, C}. B⁺ ≠ R2 = {B, C, D}. B is NOT a superkey for R2. Is B a superkey for R1? R1 = {A, B}. B⁺ = {B, C} ≠ {A, B}. B is NOT a superkey for R1. This decomposition is LOSSY.
Decompose into R1(A, B, C) and R2(B, C, D): R1 ∩ R2 = {B, C} B -> C holds. Is {B, C} a superkey for R1? {B, C}⁺ = {B, C}. {B, C}⁺ ≠ {A, B, C}. NOT a superkey for R1. Is {B, C} a superkey for R2? {B, C}⁺ = {B, C}. {B, C}⁺ ≠ {B, C, D}. NOT a superkey for R2. This decomposition is ALSO lossy.
Decompose into R1(A, B, C) and R2(A, D): R1 ∩ R2 = {A} A -> B holds. Is {A} a superkey for R1? A⁺ = {A, B, C} = R1. YES! Lossless. ✓
Decompose into R1(A, B) and R2(A, C, D): R1 ∩ R2 = {A} A -> B holds. Is {A} a superkey for R1? A⁺ = {A, B} = R1. YES! Lossless. ✓ But dependency B -> C is not checkable on R1 or R2 alone. Not dependency-preserving.For decompositions into more than two relations, the lossless-join property must be checked Pairwise. A decomposition is lossless if, when you join them one at a time, Each intermediate join is lossless.
Dependency Preservation
Section titled “Dependency Preservation”Definition. A decomposition of into is dependency-preserving if and Only if, for every functional dependency in the closure of (the original set Of FDs), for some . Equivalently, the union of the restrictions of To each is logically equivalent to :
In plain language: every functional dependency from the original relation can be verified by Examining a single decomposed relation. You do not need to join the relations back together to check The constraint.
BCNF vs 3NF Decomposition Tradeoff
Section titled “BCNF vs 3NF Decomposition Tradeoff”This is the central practical tension in normalization theory:
| Property | 3NF Decomposition | BCNF Decomposition |
|---|---|---|
| Lossless-join | Always achievable | Always achievable |
| Dependency-preserving | Always achievable | NOT always achievable |
| Redundancy | Possible (minor) | None |
Theorem. For every relation with a set of FDs There exists a decomposition of into 3NF that is both lossless and dependency-preserving.
Theorem. For every relation with a set of FDs There exists a decomposition of into BCNF that is lossless, but such a decomposition may NOT be dependency-preserving.
When BCNF loses dependency preservation:
Relation: R(A, B, C)FDs: {A -> B, B -> C}
3NF decomposition (dependency-preserving): R1(A, B) with FDs: {A -> B}. 3NF and BCNF. R2(B, C) with FDs: {B -> C}. 3NF and BCNF. Both dependencies checkable on individual relations. Lossless: R1 ∩ R2 = {B}, B -> C, so B is a superkey for R2. ✓
BCNF decomposition (also works here): Same as above. Both R1 and R2 are already in BCNF. In this case, BCNF and 3NF coincide.When BCNF and 3NF differ:
Relation: R(student, course, instructor)FDs: {{student, course} -> instructor, instructor -> course}
Candidate keys: {student, course} and {student, instructor}All attributes are prime.
3NF check: {student, course} -> instructor: superkey. OK. instructor -> course: instructor is NOT a superkey, but course IS prime. Allowed by 3NF. In 3NF. ✓
BCNF check: instructor -> course: instructor is NOT a superkey. VIOLATES BCNF.
BCNF decomposition: R1(instructor, course) -- FD: instructor -> course. BCNF. R2(student, instructor) -- no non-trivial FDs. BCNF.
Lost dependency: {student, course} -> instructor. This dependency requires attributes from both R1 and R2. It cannot be checked without joining.
3NF approach: keep the original relation. It is in 3NF. The dependency is preserved. The tradeoff: instructor -> course means that if Prof Smith teaches both CS101 and CS201, this information is duplicated across rows. But the duplication involves only prime attributes, so it is bounded.The practical rule: always decompose to 3NF. If a relation is not in BCNF, check whether the BCNF Decomposition loses dependency preservation. If it does, and the lost dependency is important for Data integrity, stay in 3NF. If the lost dependency is trivial or can be enforced through Application logic, proceed with BCNF.
Practical Normalization
Section titled “Practical Normalization”When to Stop
Section titled “When to Stop”In practice, nearly all OLTP schemas target 3NF. Here is the reasoning:
- 1NF is mandatory. Non-atomic values break relational algebra operations and SQL query semantics.
- 2NF is almost free. If your keys are single-column surrogate keys, 2NF violations are structurally impossible.
- 3NF is the sweet spot. It eliminates transitive dependencies (the most common source of real update anomalies) and always admits a dependency-preserving, lossless decomposition.
- BCNF is a bonus. Apply it when it does not sacrifice dependency preservation. When it does, the redundancy it eliminates involves only prime attributes and is manageable.
- 4NF and 5NF are almost never encountered in production schemas. When they do arise, the fix is obvious (split independent multi-valued attributes into separate tables).
Normalization Checklist
Section titled “Normalization Checklist”For each relation in your schema:
1. Does every column contain a single, atomic value? (1NF)2. Does every non-key column depend on the ENTIRE primary key? (2NF)3. Does every non-key column depend ONLY on the primary key? (3NF)4. Is every determinant a superkey? (BCNF)5. Are there independent multi-valued attributes? (4NF)Normalization and Surrogate Keys
Section titled “Normalization and Surrogate Keys”Surrogate keys (auto-increment integers, UUIDs) simplify normalization because they make composite Natural keys unnecessary in most cases. A single-column surrogate key means:
- 2NF violations are impossible (no composite key to have partial dependencies on)
- Candidate key computation is trivial (the surrogate alone is the primary key)
- Foreign key references are simpler and more efficient
Without surrogate key: OrderItem(order_id, product_id, quantity) Composite key: {order_id, product_id} 2NF violations are possible if product attributes are mixed in.
With surrogate key: OrderItem(item_id, order_id, product_id, quantity) Primary key: {item_id} 2NF violations are impossible. But you still need UNIQUE(order_id, product_id) to prevent duplicates.