Нормализация базы данных: 1НФ, 2НФ, 3НФ на примерах
Основы4 минРедакция Innovialabs
Коротко
Нормализация — это разбиение данных на таблицы так, чтобы каждый факт хранился один раз: 1НФ убирает списки в ячейках, 2НФ — колонки, зависящие от части составного ключа, 3НФ — колонки, зависящие от других неключевых колонок; денормализуйте только осознанно, ради измеренной скорости чтения или отчётов.
Коротко о главном
Нормализация — способ проектировать таблицы так, чтобы каждый факт хранился ровно в одном месте. Она защищает от трёх классических проблем — аномалий:
- Аномалия обновления: клиент сменил телефон, и его нужно исправить в десятках строк — одну обязательно пропустите.
- Аномалия вставки: нельзя добавить новый товар, пока его кто-нибудь не закажет.
- Аномалия удаления: удалили последний заказ клиента — и потеряли всё, что о нём знали.
Первые три нормальные формы (1НФ, 2НФ, 3НФ) покрывают почти всё, что нужно базе приложения.
Исходная точка: таблица из Excel
Так часто выглядит таблица заказов, перенесённая из таблиц:
| order_id | date | customer | phone | city | products |
|---|---|---|---|---|---|
| 101 | 2026-03-01 | Азиз | +998 90 111 | Ташкент | Ручка x2, Тетрадь x1 |
| 102 | 2026-03-02 | Малика | +998 91 222 | Самарканд | Тетрадь x3 |
| 103 | 2026-03-05 | Азиз | +998 90 111 | Ташкент | Ручка x1 |
Выглядит компактно, но посчитать, сколько продано ручек, неудобно, а телефон Азиза хранится дважды.
Первая нормальная форма (1НФ)
Правило: в каждой ячейке одно атомарное значение, нет списков и повторяющихся групп, каждая строка определяется ключом.
Колонка products содержит список — даём каждому товару свою строку:
| order_id | product | qty | price | date | customer | phone | city |
|---|---|---|---|---|---|---|---|
| 101 | Ручка | 2 | 5 | 2026-03-01 | Азиз | +998 90 111 | Ташкент |
| 101 | Тетрадь | 1 | 12 | 2026-03-01 | Азиз | +998 90 111 | Ташкент |
| 102 | Тетрадь | 3 | 12 | 2026-03-02 | Малика | +998 91 222 | Самарканд |
| 103 | Ручка | 1 | 5 | 2026-03-05 | Азиз | +998 90 111 | Ташкент |
Ключ теперь — пара (order_id, product). Запросы вроде «сколько продано ручек» стали простыми, но дублирования стало больше.
Вторая нормальная форма (2НФ)
Правило: таблица в 1НФ, и каждая неключевая колонка зависит от всего ключа, а не от его части. Это важно только для составных ключей.
Проверим колонки относительно (order_id, product):
qtyзависит от обоих: сколько этого товара в этом заказе. Остаётся.date,customer,phone,cityзависят только отorder_id. Выносим.price(цена из каталога) зависит только отproduct. Выносим.
Получаем:
- orders (order_id, date, customer, phone, city)
- products (product_id, name, price)
- order_items (order_id, product_id, qty)
Третья нормальная форма (3НФ)
Правило: таблица во 2НФ, и ни одна неключевая колонка не зависит от другой неключевой (нет транзитивной зависимости).
В orders ключ — order_id, но phone и city описывают клиента, а не заказ. От order_id они зависят только через customer. Выносим клиента в отдельную таблицу:
- customers (customer_id, name, phone, city)
- orders (order_id, customer_id, date)
- products (product_id, name, price)
- order_items (order_id, product_id, qty)
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
phone TEXT,
city TEXT
);
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INT NOT NULL REFERENCES customers,
created_at DATE NOT NULL
);
Теперь телефон Азиза хранится один раз, товары существуют до первого заказа, а удаление заказа не удаляет клиента.
Удобная формула: каждая неключевая колонка зависит от ключа, от всего ключа и ни от чего, кроме ключа.
Частые ошибки
- Хранить ID или теги через запятую в одной текстовой колонке вместо таблицы связей.
- Копировать имя клиента в каждый заказ «для удобства».
- Заводить колонки
phone1,phone2,phone3— это та же повторяющаяся группа. - Перенормализация: дробить стабильные и всегда используемые вместе данные на множество крошечных таблиц без пользы.
Когда денормализация оправдана
Денормализация — осознанное хранение части данных с избыточностью. Это инструмент, а не ошибка, когда:
- Отчёты и аналитика работают в отдельном хранилище, где широкие плоские таблицы или схема «звезда» упрощают и ускоряют запросы.
- Измеренно медленный запрос делает тяжёлый JOIN или подсчёт на каждом просмотре страницы; поможет закэшированный счётчик вроде
comments_count. - Нужен исторический снимок: цена и адрес доставки на момент заказа не должны меняться при правке каталога или профиля. Строго говоря, это другой факт («цена покупки»), поэтому
order_items.price— корректный дизайн.
Правила безопасной денормализации: начинайте с нормализованной схемы, денормализуйте только после замеров и поддерживайте копии в согласованном состоянии — транзакциями, триггерами или понятным путём обновления.