1.Normalization: 1NF, 2NF, 3NF and When Denormalization Is Justified
1NF: every column holds one atomic value, no repeating groups. 2NF: every non-key column depends on the whole primary key, not part of it. 3NF: every non-key column depends only on the key, not on another non-key column. Denormalization deliberately breaks one of these for a specific, measured performance or simplicity reason.
Key features
- 1NF: atomic values, no arrays/repeating groups masquerading as one column (contrast with genuinely using a PostgreSQL array type deliberately)
- 2NF: applies to composite keys — every non-key column must depend on the entire key, not just part of it
- 3NF: no non-key column may depend on another non-key column (a "transitive dependency")
- A 3NF violation shows up as the same fact stored in multiple rows, which can go out of sync on update
- Denormalization is a deliberate, measured trade — usually applied to a specific hot read path, not the whole schema
What each normal form eliminates
- Denormalized (3NF violation)
- orders stores both zip and city directly
- city is really determined by zip, not by the order itself
- updating a zip code's city means updating every order row that used it
- Normalized (3NF)
- orders stores only zip
- a separate zip_codes table maps zip -> city, once
- updating a city means changing exactly one row
Command
-- normalized: city lives in one place, keyed by zip
CREATE TABLE zip_codes (zip TEXT PRIMARY KEY, city TEXT NOT NULL);
CREATE TABLE orders (id SERIAL PRIMARY KEY, zip TEXT REFERENCES zip_codes(zip));1NF/2NF/3NF each eliminate one specific kind of redundancy; a 3NF violation shows up as the same fact stored in multiple rows that can silently disagree. Denormalize deliberately, for a specific measured reason — not by default.