First Normal Form (1NF)
Normalization is the process of organizing columns and tables to reduce redundancy and avoid data anomalies. First Normal Form (1NF) is the entry requirement — before a table can be judged against any higher normal form, it has to satisfy 1NF first. The rule itself is simple to state: every column must hold a single, atomic value, and the table must not contain repeating groups of columns.
What "atomic" means
A value is atomic if it cannot be meaningfully split into smaller pieces that the database would ever need to query independently. A single integer, a single date, a single name — these are atomic. A comma-separated list stuffed into one column is not, because the moment someone needs to filter, count, or join on one of the individual items inside that list, the column stops behaving like a single value and starts fighting the query language.
A table that violates 1NF
Imagine a table that tracks which books each customer has ordered, and someone decides to store all of a customer's books in one column, separated by commas:
Violates 1NF: multiple values crammed into one column
CREATE TABLE customer_orders ( customer_id INT PRIMARY KEY, customer_name VARCHAR(100), books_ordered VARCHAR(500) ); INSERT INTO customer_orders (customer_id, customer_name, books_ordered) VALUES (1, 'Alice Nguyen', 'Dune, Foundation, Neuromancer'), (2, 'Bob Smith', 'The Hobbit'), (3, 'Carol Diaz', 'Dune, 1984');
This looks compact, but it breaks down almost immediately in practice. There is no clean way to write a query that finds every customer who ordered "Dune" — you would be stuck writing fragile pattern matches like books_ordered LIKE '%Dune%', which is slow, cannot use an index effectively, and would also match a hypothetical book called "Dune Messiah" by accident. Counting how many books were ordered in total means parsing a string instead of running COUNT. Adding or removing a single book means rewriting the entire column value. None of this is a coincidence — it is the direct, predictable cost of storing a list where a single value is expected.
Fixing it: splitting into a related table
The 1NF fix is to give the repeating information its own table, with one row per book per customer, connected back to the customer by a foreign key. Each column in each table now holds exactly one atomic value.
1NF-compliant: one row per fact
CREATE TABLE customers ( customer_id INT PRIMARY KEY, customer_name VARCHAR(100) ); CREATE TABLE customer_orders ( order_id INT PRIMARY KEY, customer_id INT NOT NULL, book_title VARCHAR(200) NOT NULL, FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ); INSERT INTO customers (customer_id, customer_name) VALUES (1, 'Alice Nguyen'), (2, 'Bob Smith'), (3, 'Carol Diaz'); INSERT INTO customer_orders (order_id, customer_id, book_title) VALUES (101, 1, 'Dune'), (102, 1, 'Foundation'), (103, 1, 'Neuromancer'), (104, 2, 'The Hobbit'), (105, 3, 'Dune'), (106, 3, '1984');
Now finding every customer who ordered "Dune" is a plain WHERE clause, counting orders is a plain COUNT, and adding a new book for a customer is a single INSERT rather than a string edit:
Queries that only work once the data is atomic
-- Every customer who ordered 'Dune' SELECT c.customer_name FROM customers c JOIN customer_orders o ON o.customer_id = c.customer_id WHERE o.book_title = 'Dune'; -- How many books each customer has ordered SELECT customer_id, COUNT(*) AS books_ordered FROM customer_orders GROUP BY customer_id;
Repeating groups of columns
The comma-separated column is the most common 1NF violation, but the rule also rules out repeating groups spread across columns, such as book_1, book_2, book_3. This looks more "structured" than a comma-separated string, but it has the same underlying problem: the number of books is capped by however many columns were created, most rows waste space with unused columns, and a query still cannot ask "which customers ordered this book" without checking every book_N column individually.
Also violates 1NF: a repeating group of columns
CREATE TABLE customer_orders_bad ( customer_id INT PRIMARY KEY, book_1 VARCHAR(200), book_2 VARCHAR(200), book_3 VARCHAR(200) );
Symptom | Why it violates 1NF | Fix |
|---|---|---|
Comma-separated list in a column | Column value is not atomic — it holds many values at once | Move each value to its own row in a related table |
book_1, book_2, book_3 columns | Repeating group of columns instead of repeating rows | Move the repeating attribute to its own row-based table |
JSON blob storing a list that is queried by item | Individual items are not directly queryable columns | Extract the list into a child table if it is queried per item |
Every column must hold a single, atomic value — no lists or delimited strings.
A table must not have repeating groups of columns like book_1, book_2, book_3.
The fix is almost always to move the repeating data into its own related table connected by a foreign key.
Whether a JSON or array column violates 1NF in practice depends on whether individual items need to be queried directly.