Normalization: 1NF to 3NF Explained Practically
AI generated
SELECT
JOIN
SQL · Data Modeling · Normalization · Database Design
Normalization: 1NF to 3NF Explained Practically
from a before and after example to an anomaly-free table

Normalization is the systematic process of splitting a table into smaller, linked tables so that each piece of information is stored exactly once. This post walks through a single, continuous orders table step by step to show what first, second and third normal form each require, which concrete anomaly they eliminate, and what the result looks like as an actual SQL table structure.

18 min read 1NF · 2NF · 3NF · functional dependency Database agnostic: MySQL · PostgreSQL · SQL Server

1. Why normalization is necessary at all

Normalization is not an academic exercise, it is a direct answer to a problem that shows up in almost every table that has grown organically: the same piece of information gets stored multiple times, and those copies eventually drift apart. A table that mixes customer data, order data and product data into a single wide row looks convenient at first glance, because a single SELECT seems to return everything at once. In practice this design means that a single address change has to be propagated to twenty places at once, and one forgotten row leaves the database in an inconsistent state.

The core idea behind normalization is functional dependency: attribute B is functionally dependent on attribute A if each value of A corresponds to exactly one value of B. A customer id uniquely determines the customer name, an order id uniquely determines the order date. Normalization sorts columns into tables so that every column truly depends on the primary key of that table, and on nothing else. This post walks through the three most important normal forms, 1NF, 2NF and 3NF, using a single continuous example, so the effect of each stage is concrete rather than abstract.

2. First normal form (1NF): atomic values instead of repeating groups

First normal form demands two things: every column holds only atomic, non-decomposable values, and there are no repeating groups, meaning no repeated groups of columns for multiple values of the same kind in a single row. A table that stores the products of an order as a comma-separated list in a single column violates normalization at the very first stage, because SQL has no efficient way to search for, count or aggregate a single product inside that list. Columns named product_1, product_2, product_3 are just as problematic: they arbitrarily cap the number of products per order and leave empty columns whenever there are fewer products.

The fix for 1NF is to turn every repeating group into its own row. An order with three products becomes three rows, one per order line, tied together by the shared order id. This initially produces more redundancy in header-level data such as the customer name or the order date, but that redundancy gets resolved systematically in the following steps of normalization. It is important not to confuse 1NF with "no value may repeat": it is purely about structure, not about whether the same customer name is allowed to appear in multiple rows.

-- BEFORE 1NF: repeating group in a single column, not atomic
CREATE TABLE orders_raw (
    order_id     INT PRIMARY KEY,
    customer_name VARCHAR(100),
    customer_city VARCHAR(100),
    products      VARCHAR(500)  -- e.g. "Keyboard:2, Monitor:1, Mouse:3"
);

-- AFTER 1NF: one row per order line, atomic values, no repeating group
CREATE TABLE order_lines_1nf (
    order_id      INT,
    customer_name VARCHAR(100),
    customer_city VARCHAR(100),
    product_name  VARCHAR(100),
    quantity      INT,
    PRIMARY KEY (order_id, product_name)
);

INSERT INTO order_lines_1nf VALUES
    (1001, 'Anna Berger', 'Leipzig', 'Keyboard', 2),
    (1001, 'Anna Berger', 'Leipzig', 'Monitor',  1),
    (1001, 'Anna Berger', 'Leipzig', 'Mouse',    3);

3. Second normal form (2NF): full functional dependency

Second normal form builds on the first and additionally requires that every non-key column depends on the whole primary key, not just part of it. This problem only occurs with composite primary keys. In the table from the previous section, the key consists of order_id and product_name. But customer_name and customer_city only depend on order_id, not on the product name: whatever product appears in the row, the customer stays the same. This partial dependency is exactly what 2NF forbids.

The resolution is to move the partially dependent columns into their own table that depends only on the part of the key they actually relate to. Customer data moves into an orders table with order_id as its sole key, while order_lines only keeps line-specific data such as quantity and product name. After this step of normalization, every column depends on the entire key of its table, no partial key is enough to determine it uniquely anymore.

-- AFTER 2NF: split into orders (depends on order_id only)
-- and order_lines (depends on the full composite key)
CREATE TABLE orders_2nf (
    order_id      INT PRIMARY KEY,
    customer_name VARCHAR(100),
    customer_city VARCHAR(100),
    order_date    DATE
);

CREATE TABLE order_lines_2nf (
    order_id     INT,
    product_name VARCHAR(100),
    quantity     INT,
    PRIMARY KEY (order_id, product_name),
    FOREIGN KEY (order_id) REFERENCES orders_2nf(order_id)
);

-- customer_name and customer_city no longer repeat per product line
INSERT INTO orders_2nf VALUES (1001, 'Anna Berger', 'Leipzig', '2026-07-01');
INSERT INTO order_lines_2nf VALUES
    (1001, 'Keyboard', 2),
    (1001, 'Monitor',  1);

4. Third normal form (3NF): removing transitive dependencies

Third normal form requires that every non-key column depends directly on the primary key only, and not transitively through another non-key column. In our orders_2nf table, customer_city does not actually depend directly on order_id, it depends indirectly through the customer: order_id determines customer_name, and customer_name determines customer_city. This chain, order_id to customer_name to customer_city, is a transitive dependency and exactly what the third normal form of normalization eliminates.

The fix is again a split: customer data moves into its own customers table with customer_id as the key, and orders only references customer_id through a foreign key. Now customer_city depends directly on the key of its own table, customers.customer_id, instead of transitively through an order. From 3NF onward the rule of thumb applies: "every non-key column depends on the key, the whole key, and nothing but the key." This sentence summarizes 1NF, 2NF and 3NF in one line and is in practice the fastest test for whether a table design is properly normalized.

-- AFTER 3NF: customer attributes moved out of orders entirely
CREATE TABLE customers_3nf (
    customer_id   INT PRIMARY KEY AUTO_INCREMENT,
    customer_name VARCHAR(100) NOT NULL,
    customer_city VARCHAR(100) NOT NULL
);

CREATE TABLE orders_3nf (
    order_id    INT PRIMARY KEY,
    customer_id INT NOT NULL,
    order_date  DATE NOT NULL,
    FOREIGN KEY (customer_id) REFERENCES customers_3nf(customer_id)
);

-- customer_city now depends only on customer_id, no transitive chain
INSERT INTO customers_3nf (customer_name, customer_city)
    VALUES ('Anna Berger', 'Leipzig');
INSERT INTO orders_3nf (order_id, customer_id, order_date)
    VALUES (1001, 1, '2026-07-01');

5. The complete example: from raw table to target schema

Putting all three steps of normalization together turns a single wide orders_raw table into a schema of four cleanly separated tables: customers, orders, order_lines and, once product attributes such as price or description are added, also products. Each of these tables has its own primary key, and every non-key column in every table depends fully and exclusively on that key. The path from raw table to target schema is always the same: first atomic values with no repeating groups (1NF), then full dependency on the whole key (2NF), then no more transitive dependencies (3NF).

The practical benefit shows up immediately in realistic queries. A question like "how many orders came from Leipzig" only needs a JOIN between orders and customers with a WHERE condition on customer_city in the normalized schema, while the answer in the unnormalized raw format would have required a text search inside a nested column or error-prone string parsing logic. Normalization does not just make data more consistent, it also makes it reliably queryable with standard SQL.

-- Complete target schema after full normalization to 3NF
CREATE TABLE customers (
    customer_id   INT PRIMARY KEY AUTO_INCREMENT,
    customer_name VARCHAR(100) NOT NULL,
    customer_city VARCHAR(100) NOT NULL
);

CREATE TABLE products (
    product_id   INT PRIMARY KEY AUTO_INCREMENT,
    product_name VARCHAR(100) NOT NULL,
    unit_price   DECIMAL(10,2) NOT NULL
);

CREATE TABLE orders (
    order_id    INT PRIMARY KEY,
    customer_id INT NOT NULL,
    order_date  DATE NOT NULL,
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);

CREATE TABLE order_lines (
    order_id   INT NOT NULL,
    product_id INT NOT NULL,
    quantity   INT NOT NULL,
    PRIMARY KEY (order_id, product_id),
    FOREIGN KEY (order_id) REFERENCES orders(order_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);

With this schema, the sample question from earlier becomes a single, clear query. The optimizer can use the foreign key indexes instead of scanning an entire table for substrings. That exact difference, an indexed JOIN versus a string search inside an unstructured column, is the measurable performance effect of consistent normalization in reporting queries.

-- Query enabled by the normalized schema: no string parsing needed
SELECT c.customer_name, COUNT(*) AS order_count
FROM orders AS o
JOIN customers AS c ON c.customer_id = o.customer_id
WHERE c.customer_city = 'Leipzig'
GROUP BY c.customer_name
ORDER BY order_count DESC;
Normal form Rule Anomaly it removes Typical symptom before
1NF Atomic values, no repeating groups Searching and aggregating inside list columns is impossible product_1, product_2, product_3 or a CSV field
2NF Full dependency on the whole key Partial dependency with a composite key Customer name repeats per order line
3NF No transitive dependency through a non-key column Transitive dependency through another attribute City depends on customer name instead of the key
BCNF Every determinant is a candidate key Rare anomalies with multiple overlapping keys Only relevant in practice for complex multi-key tables

6. Update anomalies in detail

An update anomaly occurs when the same piece of information is stored redundantly in multiple places, so a single change has to be propagated to multiple rows at once. In the unnormalized orders_raw table, where customer_city sits right next to every order line, a customer moving to Munich would in theory need updating in every single row that customer appears in. Forgetting even one row leaves the database with two contradictory answers to the same question: which city does this customer actually live in.

After normalization to 3NF, customer_city exists exactly once, in the customers table, referenced through customer_id. An update becomes a single UPDATE statement on exactly one row, and every query that joins to that row immediately sees the new value. Update anomalies are therefore not just rarer, they are structurally impossible, because there is no second copy left that could become stale.

7. Insert anomalies in detail

An insert anomaly happens when a piece of information cannot be stored without also supplying another, actually unrelated, piece of information. If customer data only exists through order lines, as is often the case in an unnormalized structure, a new prospect who has not placed an order yet cannot be entered into the database at all. Their name and city simply have no place to exist without an order id attached.

Once customers exists as its own table with its own primary key, a customer can be created independently of any order. This is one of the underrated benefits of normalization: it decouples entities from each other, so each can exist on its own. A newsletter subscriber, a product with no stock, or a customer who has not ordered yet are all trivially representable in a normalized schema, without forcing placeholder rows or NULL values.

8. Delete anomalies in detail

A delete anomaly describes the case where deleting a piece of information unintentionally drags along another, actually unrelated, piece of information. If a customer's last order is cancelled in the unnormalized table and the associated row is deleted, the customer name and city disappear along with that one row, even though the customer as a person still exists and might order again next week. The information "this customer exists" was incorrectly tied to the existence of an order line.

After normalization, customers is fully independent of orders. Deleting an order leaves the customer in the customers table untouched, because the foreign key relationship only points one direction: orders references customers, not the other way around. Delete anomalies can be deliberately avoided by consistently placing entities that should exist independently of each other into separate tables, and modeling the relationship exclusively through foreign keys.

9. When to deliberately deviate from 3NF

3NF is the practical target for most transactional schemas in practice, because it offers a good balance between consistency protection and query complexity. Higher normal forms such as BCNF, 4NF or 5NF theoretically resolve even finer anomalies, but are rarely relevant in day to day application development, because the cases they address tend to be contrived or extremely rare. What matters more than reaching the highest possible normal form is that any deviation from 3NF is a deliberate, documented decision, not an accident born from missing data modeling knowledge.

In read-heavy reporting scenarios, deliberate denormalization sometimes happens, for example by keeping a computed sum column redundantly to avoid expensive aggregations. This is not a contradiction of normalization, it is a downstream, deliberate tradeoff built on top of an already normalized schema: normalize correctly first, then denormalize selectively where measurements prove a real performance gain, not out of convenience during the initial table design.

Mironsoft

Database design, schema reviews and data modeling

Database schema with anomalies or an unclear structure?

We review existing schemas for normalization gaps, uncover update, insert and delete anomalies, and deliver a properly normalized target schema with complete DDL scripts.

Schema review

Check existing tables against 1NF, 2NF and 3NF

Migration

Plan a step by step migration without data loss

Documentation

Document ER diagrams and dependencies cleanly

10. Summary

The three practically important normal forms of normalization each solve a concrete problem: 1NF requires atomic values with no repeating groups, 2NF requires full dependency on the whole primary key with composite keys, 3NF removes transitive dependencies through other non-key columns. Together they prevent update, insert and delete anomalies, because after normalization every piece of information exists exactly once and only needs to change in a single place.

The fastest practical test remains the rule of thumb "the key, the whole key, and nothing but the key." Checking a table against this question uncovers most normalization gaps within minutes, without needing to work through the formal definition of functional dependency in detail. Deliberate exceptions to 3NF, for example for reporting purposes, remain possible, but should always build on an already correctly normalized schema and be documented.

Normalization from 1NF to 3NF: the essentials at a glance

1NF: atomic values

No lists inside columns, no repeating groups. Every order line becomes its own row.

2NF: full dependency

With composite keys, every column must depend on the whole key, not just part of it.

3NF: no transitive chains

Attributes may only depend directly on the key, not through another non-key attribute.

Result

Update, insert and delete anomalies structurally excluded, because each piece of information exists exactly once.

11. FAQ: Normalization 1NF to 3NF

1What is normalization in one sentence?
Splitting tables so that each piece of information is stored exactly once and anomalies are structurally excluded.
2What does 1NF actually require?
Atomic values per column, no repeating groups. Every repetition becomes its own row.
3Difference between 2NF and 3NF?
2NF: full dependency on the whole key. 3NF: additionally no transitive dependencies through other columns.
4What is a functional dependency?
Each value of A corresponds to exactly one value of B. A customer id uniquely determines the customer name.
5What is an update anomaly?
Redundant copies of information must all be updated simultaneously on a change, otherwise a contradiction results.
6What is an insert anomaly?
A piece of information cannot be stored without necessarily supplying another, actually unrelated piece of information.
7What is a delete anomaly?
Deleting one row unintentionally loses another, actually unrelated, piece of information as well.
8Does every table need to reach 3NF?
3NF is the sensible default. Deliberate, documented deviations for reporting are possible.
9Difference between 3NF and BCNF?
BCNF is stricter and requires every determinant to be a candidate key. Relevant with multiple overlapping keys.
10Quick test for normalization?
The key, the whole key, and nothing but the key. If that holds for every column, the table is in 3NF.