Latticework

Command Palette

Search for a command to run...

Database Design

Normalization

20 min

Explanation

Normalization is the process of structuring tables to eliminate redundancy and prevent certain classes of update bugs. First Normal Form (1NF) is the starting requirement: every column must hold a single, atomic value — no repeating groups (like a "phone numbers" column holding a whole list).

def has_repeating_group(row):
    return any(isinstance(v, list) for v in row.values())

bad_row = {"id": 1, "name": "Alice", "phones": ["555-1234", "555-5678"]}
print(has_repeating_group(bad_row))   # True -- phones should be its own table

The fix: a separate phone_numbers table with a foreign key back to id, one row per phone number — exactly the kind of table-splitting normalization is all about.

Try it

Splitting phones into its own table with a foreign key lets Alice have ANY number of phone numbers (0, 1, or 50) without changing the schema -- a list column caps you at whatever your application code expects to find there.

Loading editor…
Explanation

Second Normal Form (2NF) goes further, but only matters for tables with a COMPOSITE primary key (more than one column). It requires every non-key column to depend on the WHOLE key, not just part of it:

# order_items table, primary key = (order_id, product_id)
dependencies = [
    (("order_id", "product_id"), "quantity"),      # depends on the FULL key -- fine
    (("product_id",), "product_name"),                # depends on ONLY product_id -- 2NF violation!
]

product_name doesn't actually depend on WHICH ORDER it's in — it only depends on the product. Storing it in order_items means it's needlessly repeated for every order line, AND creates an update anomaly: change a product's name, and now you have to update every single order row that references it, or the data goes inconsistent. The fix: move product_name into its own products table, keyed by product_id alone.

Exercise

Write `has_repeating_group(row)`: given a row represented as a dict, return True if any value is a `list` — a repeating group, the classic First Normal Form (1NF) violation.

Exercise

Write `violates_2nf(functional_dependencies, primary_key)`: each dependency is `(determinant_tuple, dependent_column)`. Return True if any dependency's determinant is a PROPER SUBSET of `primary_key` — a partial dependency, the Second Normal Form (2NF) violation.

Quiz

What is a 'partial dependency' — the specific problem Second Normal Form (2NF) eliminates?

Checkpoint

You can detect a 1NF violation (repeating groups) and a 2NF violation (partial dependencies on a composite key), and understand why each causes real update anomalies.