SQLFirst Normal Form (1NF)

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

SQL
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

SQL
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

SQL
-- 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

SQL
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

JSON columns are not automatically an exception
Modern databases support JSON columns, and a single JSON value can be perfectly atomic from the database's point of view. But if the application regularly needs to filter, join, or aggregate on individual items inside that JSON, the data is functionally violating 1NF again, just wearing a different syntax. Whether to normalize a JSON blob depends on how it will actually be queried, not on which data type stores it.
1NF is the floor, not the ceiling
Satisfying 1NF only guarantees atomic values and no repeating groups — it says nothing about whether non-key columns depend on the whole primary key or on each other. Those concerns are addressed by 2NF and 3NF, covered next.
  • 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.