Core CS · DBMS
Where 3NF stops and BCNF keeps going
The two forms ask every dependency the same question and disagree about exactly one answer. You will build the relation where they disagree, watch each definition rule on it, and see what the stricter form charges you for the privilege.
Run one relation past both definitions →01 The idea
One clause apart
Every normal form after 1NF is a test you run on arrows, not on rows. An arrow, or functional dependency, written X → Y, says any two rows agreeing on X must agree on Y. The left-hand side of a non-trivial arrow is called its determinant. Once the arrows are written down, 3NF and BCNF are two one-line tests over the same list, and neither of them ever looks at your data again.
BCNF is the short one. For every non-trivial X → Y, X must be a superkey. That is the entire rule and nothing is exempt. 3NF is that same rule with one escape clause bolted on: X is allowed to fail the superkey test, provided every attribute it determines that it does not already contain is prime. So every relation in BCNF is automatically in 3NF, and the relations that sit between the two forms are exactly the ones holding an arrow that takes the escape.
The classical wording of 3NF sounds like a different rule and is not. “No non-prime attribute is transitively dependent on any candidate key” bans precisely the arrows the escape clause fails to save, because an arrow with a non-superkey determinant pointing at a non-prime attribute is a non-prime attribute reaching the key through something that is not the key. Learn both sentences. Interviewers ask for the second and then push on the first, and the first is the one you can actually run down a list.
02 Worked example
One allotment table, and the arrow 3NF lets through
An exam board allots five candidates to four centres and records the centre’s address on the same row. Four business rules govern it, and they are rules about every legal row, not observations about these five. Two arrows that people expect to hold do not, and they do different jobs. One is why the third rule needs both of its columns. The other is why PIN can never identify a row on its own, and that is the fact the whole second half of this lesson runs on.
-- the four rules, as the board states themRoll -> Centre -- a candidate sits at exactly one centreCentre -> Street, City -- a centre has one addressStreet, City -> PIN -- within a city, a street lies in one postal zonePIN -> City -- a postal zone lies in exactly one city -- two arrows that do NOT hold, and each one does a different jobStreet -> PIN -- MG Road is 560001 in Bengaluru, 600002 in Chennai: line 4 needs CityPIN -> Street -- 560001 covers MG Road and Church St: PIN alone is never a key -- Roll sits on no right-hand side, and Roll+ reaches every columncandidate key {Roll}, and only that. prime: Roll. non-prime: Centre, Street, City, PIN.
Here is the table itself. Amber marks a cell that restates a fact an earlier row already stated, working down the arrows one at a time.
| Roll | Centre | Street | City | PIN |
|---|---|---|---|---|
| R1 | VH | MG Road | Bengaluru | 560001 |
| R2 | VH | MG Road | Bengaluru | 560001 |
| R3 | SH | Park St | Bengaluru | 560038 |
| R4 | KH | MG Road | Chennai | 600002 |
| R5 | TH | Church St | Bengaluru | 560001 |
Four amber cells out of twenty-five, which looks like almost nothing, and that is the trap this table is set for. Five candidates is a teaching size. A real allotment puts three hundred candidates in VH, and then MG Road, Bengaluru is written three hundred times and any two of those rows are free to disagree. The redundancy is not measured by the amber count. It is measured by how many rows repeat a determinant, and that number is set by the data, not by the schema.
Follow one fact. 560001 is in Bengaluru is recorded on rows 1, 2 and 5, three times in a five-row table, and nothing in the schema knows those three cells are one fact.
Node 4 is the node the whole lesson turns on, and it is the one most revision notes skip straight past. After the 3NF split the schema is genuinely in 3NF, every arrow is checkable without a join, and one of the three tables still writes the same fact on many rows. 3NF is not failing to notice. 3NF has looked at that arrow and passed it, on purpose, using the escape clause. Node 5 is what removing it costs.
03 Mechanics
Every arrow, tested against both definitions
Two tables. The first is the pair of definitions side by side, because most of what gets recited about 3NF and BCNF is a garbled memory of one row of it. The second runs every non-trivial arrow in the story past both definitions and marks each one, which is the only way to say a relation is in a form and mean it.
| 3NF | BCNF | |
|---|---|---|
| The test, for every non-trivial X → Y | X is a superkey, OR every attribute of Y not in X is prime | X is a superkey |
| The escape clause | a wholly prime right-hand side | none |
| Classical wording | 2NF, and no non-prime attribute transitively dependent on any candidate key | every determinant is a candidate key, though superkey is the accurate word |
| A decomposition always exists that is | lossless and dependency preserving | lossless only |
| What it can still leave behind | one fact rewritten on every row that repeats a non-superkey determinant | nothing that a functional dependency can explain |
One shortcut before the second table, because otherwise the job looks infinite. You are told a handful of arrows, but the relation obeys every arrow those imply, and there are a lot of those. You do not have to test them. If a derived arrow X → A has a non-superkey left-hand side, then somewhere in the closure computation that put A into X⁺ there was a given arrow with a non-superkey left-hand side of its own, and that given arrow already fails whatever X → A fails. Deriving new arrows cannot manufacture a violation the given ones do not already show, for either form. Test the list you were handed and stop. That holds for the relation as it was handed to you. After a split you work out afresh which arrows live inside each piece, which is why one row of the table below is marked derived: Street, PIN → City is not one of the four stated rules, and it is listed because it is where ZONE’s second candidate key comes from.
| Relation | Non-trivial arrow | Left side a superkey? | Right side all prime? | 3NF | BCNF |
|---|---|---|---|---|---|
| CENTRE_ALLOT key {Roll} | Roll → Centre | yes, Roll is the key | not consulted | pass | pass |
| Centre → Street, City | no, closure misses Roll | no, neither is prime | fail | fail | |
| Street, City → PIN | no, closure misses Roll and Centre | no, PIN is not prime | fail | fail | |
| PIN → City | no, closure is {PIN, City} | no, City is not prime | fail | fail | |
| ALLOT key {Roll} | Roll → Centre | yes | not consulted | pass | pass |
| CENTRE key {Centre} | Centre → Street, City | yes | not consulted | pass | pass |
| ZONE keys {Street, City} and {Street, PIN} | Street, City → PIN | yes, it is a candidate key | not consulted | pass | pass |
| Street, PIN → City (derived) | yes, the other candidate key | not consulted | pass | pass | |
| PIN → City | no, closure is {PIN, City} | yes, City is in {Street, City} | pass | fail | |
| ZONE_CITY key {PIN} | PIN → City | yes | not consulted | pass | pass |
| ZONE_ST key {Street, PIN} | none exists | — | — | pass | pass |
The ninth row is the whole lesson. PIN closes to {PIN, City}, which is two of ZONE’s three columns, so it is not a superkey and the first clause fails for both forms. Then 3NF asks its second question, finds City sitting inside the candidate key {Street, City}, and passes the arrow. BCNF has no second question. Same relation, same arrow, same closure, two verdicts.
Notice how ZONE got two candidate keys. {Street, City} is a key by rule three. {Street, PIN} is a key because PIN hands you City by rule four, and Street with City then hands you everything. Neither can lose a column: Street alone determines nothing, City alone determines nothing, and PIN alone stops at City. Two keys, and they share Street.
-- after the 3NF decomposition: every arrow lives inside one relationALLOT (Roll*, Centre) -- Roll -> CentreCENTRE(Centre*, Street, City) -- Centre -> Street, CityZONE (Street, City, PIN) -- Street,City -> PIN and PIN -> City keys {Street, City} and {Street, PIN} -- after splitting ZONE on the one arrow BCNF rejectsZONE_CITY(PIN*, City) -- PIN -> City, and PIN is the keyZONE_ST (Street*, PIN*) -- no non-trivial arrow at all lost: Street,City -> PIN now spans two tables and lives in neither 25 cells -> 34 after 3NF -> 36 after BCNF -- this was never about space
The 3NF decomposition on lines 2 to 4 is not guesswork. Take a canonical cover, make one relation from each group of arrows that share a determinant, drop any relation wholly contained in another, and add one holding a candidate key if none of them already does. Here that yields {Roll, Centre}, {Centre, Street, City}, {Street, City, PIN} and {PIN, City}, and the last is contained in the third, so it goes. ALLOT holds the candidate key, so nothing extra is needed. That last check is not book-keeping. It is what makes the synthesis lossless: once a candidate key sits whole inside one of the pieces the algorithm builds, joining the pieces gives back the original rows and nothing else. Every arrow ends up inside one relation, which is what dependency preserving means, and this construction works for any relation you will ever be handed. That is the guarantee 3NF has and BCNF does not.
Line 10 is the price of line 8. Split ZONE and no table holds Street, City and PIN together any more, so there is nowhere to write the rule down. Project the arrows onto the pieces and you get PIN → City in ZONE_CITY and, in ZONE_ST, nothing at all. Close {Street, City} under what survives and you reach {Street, City}. It does not reach PIN. The rule is gone, not relocated.
One form further, in a paragraph, because it is asked as a follow-up. A multivalued dependency X ↠ Y says the set of Y values attached to an X value does not depend on the rest of the row, and 4NF demands that every non-trivial one has a superkey on the left. It matters because a relation can pass BCNF and still repeat itself. Give a centre a set of invigilators and, independently, a set of subjects, and CENTRE_STAFF(Centre, Invigilator, Subject) has no non-trivial functional dependency anywhere in it, so its only key is all three columns and BCNF passes it without comment. Three invigilators and two subjects still force it to store all six pairings. Split it into (Centre, Invigilator) and (Centre, Subject) and the same information sits in five rows, and a fourth invigilator costs one row instead of two. Functional dependencies never saw that redundancy, because no column was determining another.
05 Cheat sheet
Four forms, four tests, and what each one still allows
Read the right-hand column, not the middle one. Anyone can recite a test. The follow-up is always what the form does not protect you from, and that is where a recited answer runs out.
| Form | The test to apply | What it still allows |
|---|---|---|
| 2NF | no non-prime attribute depends on only part of a candidate key There is nothing to test unless some candidate key has more than one column. | a non-key column determining another non-key column |
| 3NF | every non-trivial X → Y has X a superkey, or Y minus X all prime | one fact rewritten on every row that repeats a non-superkey determinant |
| BCNF | every non-trivial X → Y has X a superkey | repetition that no functional dependency explains, and a rule you can no longer check in one table |
| 4NF | every non-trivial X ↠ Y has X a superkey | read cost. It says nothing about how many tables a query now joins |
06 Where & why
Where the lost dependency goes to live
The trade-off is not a diagram. Before the split, Street, City → PIN is one line of DDL, because {Street, City} is a candidate key of ZONE and UNIQUE (street, city) enforces it on every write. After the split no table holds those three columns, so that line has nowhere to go. Every mainstream engine has an answer, all four answers are different, and none of them is one line.
A CHECK constraint may not contain a subquery and may not look at another table, so the rule becomes a trigger on both tables, or a materialised view over the join with a unique index on it. PostgreSQL refreshes a materialised view only when you run REFRESH MATERIALIZED VIEW, so that route detects the violation later rather than rejecting it.
Oracle can define a materialised view with REFRESH ON COMMIT and put a unique constraint on it, so the check runs as the transaction commits rather than at the next manual refresh. That is the closest any mainstream product gets to the standard’s CREATE ASSERTION, which none of them implement. You pay for it on every write to the base tables.
A SCHEMABINDING view over the two tables with a UNIQUE CLUSTERED INDEX is updated in the same transaction as the base-table write, so the offending insert fails immediately with a duplicate-key error. It is the cleanest reconstruction of the lost rule and the most restricted: indexed views carry a long list of rules about what the view is allowed to contain.
MySQL has no materialised views at all, and CHECK constraints were only parsed and ignored before 8.0.16 and still may not contain a subquery. A rule spanning two tables becomes a BEFORE INSERT and BEFORE UPDATE trigger on each of them, or it slides into application code where a batch import walks past it. That gap is a large part of why teams here stop at 3NF.
07 Interview questions
What they actually ask
Expect to be handed a small relation and a list of arrows and asked for the key, then the normal form, then a decomposition, in that order. The questions below escalate the same way, and the ones near the end are where prepared answers usually stop.
What is a transitive dependency?
State 3NF the way you would write it on a whiteboard.
What is a prime attribute?
What is BCNF, and how exactly does it differ from 3NF?
Give me a relation that is in 3NF but not in BCNF.
How do you decompose a relation into BCNF?
Which decomposition is guaranteed to preserve every dependency?
You lost a dependency. What actually goes wrong?
What is 4NF?
Does going past 3NF ever make things worse?
Two people give different normal forms for the same table. What went wrong?
08 Practice problems
Six to work through
For every one: write the candidate keys first, then the prime attributes, then run each arrow past both clauses. Declaring a normal form before the key list is complete is how a correct-sounding answer ends up wrong.