1. Home
  2. Database Management Systems
  3. BCNF and Lossless Decomposition

BCNF and Lossless Decomposition

The strictest common normal form. Find the dependency that breaks BCNF, split the relation in two without losing data, repeat until every part is in BCNF, and see in 3D which dependencies survive.

Interactive 3DAdvanced14 min readDBMSUpdated

Drag to rotate · Right-drag to pan · Click, then scroll to zoom · Space play · ←→ step

What's happening

Pseudocode

    Try this in the 3D model

    • Decompose Student, Course, Teacher. Which FD breaks BCNF, and which FD is lost afterwards?
    • Choose the chain R(A,B,C,D,E). How many splits does it take, and is any FD lost?
    • Choose R(A,B,C,D). What are its three candidate keys?
    • Type your own relation: attributes ABCDEF and FDs A->BC, CD->E, B->D, E->A.

    Why go beyond 3NF?

    3NF removes most redundancy, but not all. Take R(Student, Course, Teacher): each student takes a course from one teacher (SC → T), and each teacher teaches only one course (T → C).

    Student Course Teacher
    Asha DBMS Rao
    Ravi DBMS Rao
    Meera DBMS Iyer
    Asha OS Sen

    “Rao teaches DBMS” is stored once for every student Rao has. If Rao switches to OS, many rows must change (an update anomaly). If Meera leaves, we lose the fact that Iyer teaches DBMS (a deletion anomaly). R is in 3NF, because C is part of the key SC. But the root problem is that T → C holds while T is not a key.

    The definition

    A relation is in BCNF (Boyce–Codd normal form) if for every non-trivial functional dependency X → Y that holds in it, X is a superkey.

    3NF BCNF
    X → A is fine if… X is a superkey or A is part of some key X is a superkey, with no exception
    Lossless decomposition always possible? yes yes
    Dependency-preserving decomposition always possible? yes no

    The decomposition algorithm

    1. Find a relation Ri and a set X ⊂ Ri such that X determines more of Ri but isn’t a superkey of Ri. Use attribute closure: X⁺ ∩ Ri ≠ X and X⁺ ∩ Ri ≠ Ri.
    2. Split Ri into R1 = X⁺ ∩ Ri (X and everything it determines) and R2 = Ri − (X⁺ − X) (the rest, keeping X so the parts can be joined).
    3. Repeat until no part breaks BCNF.

    Every split is lossless. R1 and R2 share exactly X, and X determines all of R1. By the binary test (R1 ∩ R2 → R1), joining them gives back exactly the original rows, with no extra “spurious” ones.

    Worked examples

    R(S, C, T), F = {SC → T, T → C}. Keys: SC and ST. T⁺ = TC, so T → C breaks BCNF.

    • Split on T: (C, T) and (S, T). Both have two attributes, so both are in BCNF.
    • But SC → T is lost. No single part contains S, C and T, so the database can’t check it without a join. This is the classic case where BCNF costs a dependency.

    R(A, B, C, D, E), F = {A → B, B → C, C → D, D → E}. Key: A.

    Step Relation Violation Split into
    1 (A, B, C, D, E) B⁺ = BCDE, but B is not a key (B, C, D, E) and (A, B)
    2 (B, C, D, E) C → DE (C, D, E) and (B, C)
    3 (C, D, E) D → E (D, E) and (C, D)

    Result: (A, B), (B, C), (C, D), (D, E), and every FD is preserved.

    Code

    from itertools import combinations
    
    def closure(X, F):
        c = set(X)
        changed = True
        while changed:
            changed = False
            for L, R in F:
                if L <= c and not R <= c:
                    c |= R
                    changed = True
        return c
    
    def violation(Ri, F):
        """Smallest X in Ri that determines more of Ri but is not a superkey of Ri."""
        for size in range(1, len(Ri)):
            for X in combinations(sorted(Ri), size):
                got = closure(X, F) & Ri
                if got != set(X) and got != Ri:
                    return set(X), got
        return None
    
    def bcnf(Ri, F):
        v = violation(Ri, F)
        if not v:
            return [Ri]
        X, got = v
        return bcnf(got, F) + bcnf((Ri - got) | X, F)   # lossless: the parts share X, and X -> got
    
    def preserved(L, R, F, parts):
        """Can L -> R be checked using only the parts (closure through projections)?"""
        got = set(L)
        while True:
            add = set().union(*(closure(got & P, F) & P for P in parts)) - got
            if not add:
                return R <= got
            got |= add
    
    F = [({'S', 'C'}, {'T'}), ({'T'}, {'C'})]
    parts = bcnf({'S', 'C', 'T'}, F)
    print([''.join(sorted(p)) for p in parts])          # ['CT', 'ST']
    print([preserved(L, R, F, parts) for L, R in F])    # [False, True]

    Choosing between 3NF and BCNF

    • If BCNF keeps every dependency, use BCNF.
    • If it would lose an important dependency (like SC → T above), many designers stop at 3NF, which always keeps every dependency, and accept a little redundancy.
    • Different choices of violating FD can give different BCNF decompositions. All of them are correct, and some preserve more dependencies than others.

    Common mistakes

    • Checking only the given FDs inside the parts. A part can break BCNF through a derived FD. Use closures.
    • Forgetting to keep X in both parts. Without the shared X the join is lossy and invents rows.
    • Thinking BCNF always keeps every dependency. It doesn’t, as the S, C, T example shows.
    • Mixing up superkey and candidate key. X only has to be a superkey (it may contain extra attributes).

    Complexity at a glance

    Case / operationTimeWhy
    Test one relation for BCNF (n attributes)up to 2ⁿ closuresFor the original relation it is enough to check the given FDs. For the parts, derived FDs count too.
    Lossless test of a binary splitone closureR1 ∩ R2 → R1 or R1 ∩ R2 → R2.
    Dependency-preservation testpolynomialA closure that is only allowed to move through the parts.

    Quick check

    Test yourself — pick an answer to see if you got it.

    1. When is a relation in BCNF?

    2. R(S, C, T) has SC → T and T → C. Which FD violates BCNF?

    3. Why is splitting R on X into R1 = X⁺ ∩ R and R2 = R − (X⁺ − X) lossless?

    4. R(S, C, T) is decomposed into (C, T) and (S, T). Which dependency is lost?

    5. Which statement is true?

    Saved in this browser.
    Spotted a mistake or a bug in the 3D model?

    Report a mistake

    in BCNF and Lossless Decomposition. Thank you — every report makes the lesson better for the next reader.

    We'll also include a link to the step of the 3D model you're on and your browser type, so we can reproduce it.