Аномалии и нормальные формы 1NF, 2NF, 3NF

Плохо спроектированная таблица наказывает аномалиями при каждом изменении данных. Нормальные формы — это лекарство. Разберём 1NF, 2NF и 3NF строго.

Нормализация — это процесс декомпозиции отношений на основе функциональных зависимостей так, чтобы устранить избыточность и аномалии, сохранив возможность восстановить исходные данные соединением.

Зачем нормализовать: аномалии

Вернёмся к «плоской» таблице, где в каждой строке заказа продублированы данные клиента и товара: (order_id, product_id, client_name, client_city, product_name, cena, qty). Хранить всё в одной таблице кажется удобным, но избыточность мстит тремя видами аномалий.

  • Аномалия обновления. Клиент сменил город. Его имя и город продублированы в каждой строке его заказов — нужно обновить десятки строк. Пропустили одну — данные противоречивы.
  • Аномалия вставки. Хотим завести новый товар, который ещё никто не заказал. Но строка существует только в составе заказа — товар «без заказа» вставить некуда (или придётся ставить NULL в ключевые поля).
  • Аномалия удаления. Удалили единственный заказ с этим товаром — и вместе с ним потеряли название и цену товара, хотя сам товар никуда не делся.

Все три аномалии — следствие того, что в одной таблице смешаны факты о разных сущностях. Нормализация разносит их по отдельным таблицам.

Что такое нормализация и зачем она пошагова

Нормализация устроена как лестница: 1NF, затем 2NF, затем 3NF, затем BCNF — каждая форма строже предыдущей и устраняет свой класс проблем. Почему не сразу к самой строгой? Потому что пошаговость делает процесс понятным и управляемым: на каждой ступени мы убираем один конкретный вид зависимости и точно знаем, что именно исправили. К тому же не всегда нужна самая строгая форма — иногда 3NF достаточна (об этом в уроке про BCNF). Важно и то, что нормализация — это не «разрезать таблицу как попало», а декомпозиция без потерь: разбивая отношение, мы обязаны иметь возможность соединением восстановить ровно исходные данные. Разрезать так, чтобы соединение давало лишние или, наоборот, терять строки, — недопустимо. Поэтому за каждой нормальной формой стоит не только «что устранить», но и «как разбить корректно».

Первая нормальная форма (1NF)

Отношение в 1NF, если все его атрибуты атомарны: в каждой ячейке ровно одно неделимое значение, нет повторяющихся групп и вложенных таблиц. По сути, 1NF — это требование быть настоящим отношением. Нарушение — хранить список телефонов через запятую в одном поле или повторять колонки product1, product2, product3. Лечение: вынести многозначные данные в отдельные строки/таблицу.

Аномалии — это симптом, избыточность — болезнь

Полезно зафиксировать причинно-следственную связь, потому что она объясняет всю нормализацию. Корень зла — избыточность: один и тот же факт (имя клиента, цена товара) физически хранится в нескольких строках. Аномалии — лишь её симптомы. Аномалия обновления возникает, потому что копий много и их легко рассогласовать. Аномалия удаления — потому что факт о товаре «прицеплен» к строке заказа и гибнет вместе с ней. Аномалия вставки — потому что факт нельзя записать отдельно от строки, в которую он вшит. Уберите избыточность (храните каждый факт один раз) — и все три симптома исчезнут разом, без отдельной борьбы с каждым. Именно это делает нормализация: она не «лечит аномалии» по одной, а устраняет их общую причину. Поэтому, увидев в схеме дублирующийся факт, можно заранее предсказать все три аномалии — они придут неизбежно.

Вторая нормальная форма (2NF)

2NF имеет смысл только когда ключ составной. Отношение в 2NF, если оно в 1NF и каждый непервичный атрибут функционально зависит от всего потенциального ключа целиком, а не от его части. Нарушение называется частичной зависимостью.

В нашей таблице ключ — {order_id, product_id}. Но client_name зависит только от order_id (часть ключа), а product_name и cena — только от product_id (другая часть ключа). Это частичные зависимости — 2NF нарушена. Именно они порождают аномалии вставки/удаления товара. Лечение: вынести «частично зависящие» атрибуты в отдельные таблицы по их детерминантам.

ТаблицаАтрибутыКлюч
order_itemsorder_id, product_id, qty(order_id, product_id)
ordersorder_id, client_name, client_cityorder_id
productsproduct_id, product_name, cenaproduct_id

Теперь qty зависит от всего ключа связующей таблицы, а данные товара и заказа живут отдельно. Аномалии вставки и удаления товара исчезли.

Третья нормальная форма (3NF)

2NF убрала зависимости от части ключа, но осталась другая беда — зависимости неключевых атрибутов друг от друга. В таблице orders(order_id, client_id, client_city): order_id → client_id и client_id → client_city, откуда транзитивно order_id → client_city. Город зависит от заказа не напрямую, а через клиента — это транзитивная зависимость, и она снова даёт аномалии (город клиента продублирован во всех его заказах).

Строгое определение: отношение в 3NF, если оно в 2NF и для каждой нетривиальной ФЗ X → A выполнено хотя бы одно из условий: X — суперключ, или A — первичный (prime) атрибут (входит в какой-либо потенциальный ключ). Эквивалентно: ни один непервичный атрибут не зависит транзитивно от ключа.

В orders ФЗ client_id → client_city нарушает 3NF: client_id — не суперключ, а client_city — не первичный атрибут. Лечение: вынести клиента в свою таблицу.

-- после нормализации до 3NF: три чистые таблицы
CREATE TABLE clients (id INTEGER PRIMARY KEY, name TEXT, city TEXT);
CREATE TABLE orders  (id INTEGER PRIMARY KEY, client_id INTEGER REFERENCES clients(id));
CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, cena INTEGER);
CREATE TABLE order_items (
  order_id INTEGER REFERENCES orders(id),
  product_id INTEGER REFERENCES products(id),
  qty INTEGER,
  PRIMARY KEY (order_id, product_id)
);
INSERT INTO clients VALUES (1,'Анна','Москва');
INSERT INTO orders VALUES (10,1),(11,1);
INSERT INTO products VALUES (1,'Книга',500);
INSERT INTO order_items VALUES (10,1,2),(11,1,1);

-- город клиента хранится ОДИН раз; меняем в одном месте
UPDATE clients SET city = 'Казань' WHERE id = 1;
SELECT o.id AS zakaz, c.name, c.city FROM orders o JOIN clients c ON c.id = o.client_id;

Вывод: «10, Анна, Казань» и «11, Анна, Казань». Мы поменяли город одной строкой в clients — и оба заказа сразу видят новое значение. Аномалия обновления побеждена.

Общая идея всех нормальных форм

За частными определениями 2NF и 3NF стоит одна объединяющая мысль, которую полезно увидеть: каждый факт должен храниться ровно в одном месте, и определяться он должен ключом, всем ключом и ничем, кроме ключа. Эта знаменитая формула («the key, the whole key, and nothing but the key») кратко выражает 2NF и 3NF вместе. «Всем ключом» — это 2NF: атрибут не должен зависеть лишь от части составного ключа (иначе он относится не ко всей строке, а к её части). «Ничем, кроме ключа» — это 3NF: атрибут не должен зависеть от другого неключевого атрибута (иначе он на самом деле описывает не эту сущность, а ту, через которую зависит). Когда каждый неключевой атрибут зависит точно от ключа целиком и больше ни от чего, избыточности неоткуда взяться — а значит, неоткуда взяться и аномалиям. Все нормальные формы — лишь разные грани этого единого принципа.

Порядок и смысл нормальных форм

ФормаЧто устраняет
1NFНеатомарные значения, повторяющиеся группы
2NFЧастичные зависимости (от части составного ключа)
3NFТранзитивные зависимости (неключевой через неключевой)

Формы вложены: каждая следующая включает предыдущую. Достичь 3NF, минуя 1NF, нельзя.

Типичные ошибки

  • Список значений в одной ячейке. Нарушение 1NF; «теги через запятую» убивают поиск и целостность.
  • Атрибут зависит от части ключа. Нарушение 2NF; данные товара в строке заказа — классика.
  • Неверная формулировка 3NF. Условие «A — первичный атрибут» забывают; без него определение слишком строгое и совпало бы с BCNF.
  • Нормализация без ФЗ. Без анализа зависимостей нормализация превращается в гадание.

Итог

  • Избыточность порождает аномалии вставки, обновления и удаления — их и убирает нормализация.
  • 1NF требует атомарности значений; 2NF убирает частичные зависимости от составного ключа.
  • 3NF убирает транзитивные зависимости: для каждой нетривиальной ФЗ X → A детерминант X — суперключ или A — первичный атрибут.
  • Нормальные формы вложены и опираются на корректно выявленные функциональные зависимости.
Проверьте себя
1. Какую аномалию иллюстрирует ситуация: удалили единственный заказ с товаром и потеряли название и цену товара?
AАномалию вставки
BАномалию обновления
CАномалию удаления
DЭто не аномалия
2. Что устраняет переход во вторую нормальную форму (2NF)?
AНеатомарные значения
BЧастичные зависимости неключевых атрибутов от части составного ключа
CТранзитивные зависимости
DДубликаты строк
3. Каково строгое условие 3NF для нетривиальной ФЗ X → A?
AX должен быть первичным ключом
BX — суперключ ИЛИ A — первичный (prime) атрибут
CA не должен быть NULL
DX и A должны быть в разных таблицах
Поддержать проект