Перейти к содержанию
Innovialabs

Нормализация базы данных: 1НФ, 2НФ, 3НФ на примерах

Основы4 мин

Коротко

Нормализация — это разбиение данных на таблицы так, чтобы каждый факт хранился один раз: 1НФ убирает списки в ячейках, 2НФ — колонки, зависящие от части составного ключа, 3НФ — колонки, зависящие от других неключевых колонок; денормализуйте только осознанно, ради измеренной скорости чтения или отчётов.

Коротко о главном

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

  • Аномалия обновления: клиент сменил телефон, и его нужно исправить в десятках строк — одну обязательно пропустите.
  • Аномалия вставки: нельзя добавить новый товар, пока его кто-нибудь не закажет.
  • Аномалия удаления: удалили последний заказ клиента — и потеряли всё, что о нём знали.

Первые три нормальные формы (1НФ, 2НФ, 3НФ) покрывают почти всё, что нужно базе приложения.

Исходная точка: таблица из Excel

Так часто выглядит таблица заказов, перенесённая из таблиц:

order_iddatecustomerphonecityproducts
1012026-03-01Азиз+998 90 111ТашкентРучка x2, Тетрадь x1
1022026-03-02Малика+998 91 222СамаркандТетрадь x3
1032026-03-05Азиз+998 90 111ТашкентРучка x1

Выглядит компактно, но посчитать, сколько продано ручек, неудобно, а телефон Азиза хранится дважды.

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

Правило: в каждой ячейке одно атомарное значение, нет списков и повторяющихся групп, каждая строка определяется ключом.

Колонка products содержит список — даём каждому товару свою строку:

order_idproductqtypricedatecustomerphonecity
101Ручка252026-03-01Азиз+998 90 111Ташкент
101Тетрадь1122026-03-01Азиз+998 90 111Ташкент
102Тетрадь3122026-03-02Малика+998 91 222Самарканд
103Ручка152026-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 — корректный дизайн.

Правила безопасной денормализации: начинайте с нормализованной схемы, денормализуйте только после замеров и поддерживайте копии в согласованном состоянии — транзакциями, триггерами или понятным путём обновления.

Частые вопросы

Для большинства приложений 3НФ достаточно. Более высокие формы, например НФБК и 4НФ, решают редкие случаи с пересекающимися ключами или независимыми многозначными фактами; знать о них полезно, но проектировать под них явно приходится редко.

Поможем с этим

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

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.