Нормализация данных
Нормализация - убрать дубли и аномалии в таблицах
Повтор одних и тех же фактов в каждой строке заказа усложняет правки. Нормализация хранит каждый факт в одном месте; связи — через ключи.
Цель — согласованность при вставке и удалении. Отчёты ускоряют денормализацией с явной синхронизацией копий.
Перед главой: Реляционная модель, ER. Расширенный разбор и чек-лист проектировщика — Проектирование баз данных. После: Ограничения, JOIN.
Функциональные зависимости
Определение: атрибут B функционально зависит от A (A → B), если каждому значению A соответствует ровно одно значение B.
| Тип | Пример |
|---|---|
| Тривиальная | {сотрудник, отдел} → сотрудник |
| Полная | B зависит от всего составного ключа, не от его части |
| Частичная | B зависит только от части составного ключа |
| Транзитивная | A → B, B → C, при этом B не является ключом |
Отношение с простым (не составным) первичным ключом автоматически находится во 2НФ: частичных зависимостей от части ключа быть не может.
Нормальные формы
Нормализация идёт ступенями: каждая следующая форма опирается на предыдущую и убирает свой класс избыточности. На практике проектирования SQL-схем для OLTP обычно достаточно 3НФ или НФБК; 4НФ полезна знать, когда в одной таблице смешаны независимые списки значений.
| Форма | Что устраняет | Типичный приём |
|---|---|---|
| 1НФ | Повторяющиеся группы, неатомарные ячейки | Одно значение в ячейке; "широкие" столбцы product1, product2 → отдельные строки |
| 2НФ | Частичные зависимости от части составного ключа | Разделить заголовок сущности и строки состава |
| 3НФ | Транзитивные зависимости между неключевыми полями | Вынести справочник, на который ссылается FK |
| НФБК | Зависимости, где левая часть — не суперключ | Дополнительная декомпозиция при нескольких кандидатных ключах |
| 4НФ | Независимые многозначные зависимости | Две таблицы "один ко многим" вместо декартова произведения |
Ниже — один сквозной домен (заказы интернет-магазина), на котором видно переход от "сырой" таблицы к 3НФ. Примеры НФБК и 4НФ — отдельные типовые случаи.
1НФ — первая нормальная форма
Критерий: каждый атрибут атомарен — в ячейке одно значение домена; нет повторяющихся групп столбцов (телефон1, телефон2, product1, product2) и списков "через запятую" вместо отдельных строк.
До (нарушение 1НФ) — в одной строке заказа несколько товаров в разных столбцах:
| order_id | customer_name | customer_email | product1 | product2 | product3 |
|---|---|---|---|---|---|
| 5001 | Иванов | ivan@mail.ru | Кроссовки | Кепка | Часы |
После (1НФ) — каждый товар заказа в своей строке; данные клиента повторяются, пока не пройдём 2НФ и 3НФ:
| order_id | customer_name | customer_email | product | order_date |
|---|---|---|---|---|
| 5001 | Иванов | ivan@mail.ru | Кроссовки | 2025-03-01 |
| 5001 | Иванов | ivan@mail.ru | Кепка | 2025-03-01 |
| 5001 | Иванов | ivan@mail.ru | Часы | 2025-03-01 |
2НФ — вторая нормальная форма
Критерий: выполнена 1НФ, и каждый неключевой атрибут полностью зависит от всего первичного ключа. Актуально при составном ключе — поля, зависящие только от order_id, нельзя держать в таблице с ключом (order_id, product).
До (нарушение 2НФ) — ключ (order_id, product), а customer_name, customer_email, order_date определяются только order_id:
| order_id | product | customer_name | customer_email | order_date |
|---|---|---|---|---|
| 5001 | Кроссовки | Иванов | ivan@mail.ru | 2025-03-01 |
| 5001 | Кепка | Иванов | ivan@mail.ru | 2025-03-01 |
После (2НФ) — заголовок заказа и позиции разделены:
orders — всё, что зависит только от order_id:
| order_id | customer_name | customer_email | order_date |
|---|---|---|---|
| 5001 | Иванов | ivan@mail.ru | 2025-03-01 |
order_items — связь заказа с товаром:
| order_id | product |
|---|---|
| 5001 | Кроссовки |
| 5001 | Кепка |
Отношение с простым (не составным) первичным ключом автоматически во 2НФ: частичных зависимостей от "части ключа" быть не может.
3НФ — третья нормальная форма
Критерий: выполнена 2НФ, и неключевые атрибуты нетранзитивно зависят от ключа — то есть не зависят от других неключевых полей.
До (нарушение 3НФ) — в orders поле region определяется через customer_email (или имя клиента), а не напрямую через order_id:
| order_id | customer_email | order_date | region |
|---|---|---|---|
| 5001 | ivan@mail.ru | 2025-03-01 | Центр |
| 5002 | ivan@mail.ru | 2025-03-05 | Центр |
Цепочка: order_id → customer_email → region. Смена региона клиента потребует правки всех его заказов.
После (3НФ):
customers — факты о клиенте хранятся один раз:
| customer_email | customer_name | region |
|---|---|---|
| ivan@mail.ru | Иванов | Центр |
orders — ссылка на клиента и дата заказа:
| order_id | customer_email | order_date |
|---|---|---|
| 5001 | ivan@mail.ru | 2025-03-01 |
| 5002 | ivan@mail.ru | 2025-03-05 |
Другой частый случай: employee_id → department_id → department_head в одной таблице сотрудников → отдельная таблица departments.
НФБК — нормальная форма Бойса–Кодда
Критерий: усиление 3НФ — каждая нетривиальная функциональная зависимость X → Y имеет X в роли суперключа (кандидата в ключ). 3НФ допускает редкие случаи, когда зависимость идёт от неключевого атрибута, который входит в другой кандидатный ключ.
До (нарушение НФБК) — расписание: преподаватель ведёт курс только в одной аудитории; при этом instructor → room_number, хотя instructor сам по себе не является ключом:
| course_id | room_number | instructor |
|---|---|---|
| CS101 | A-201 | Петров |
| CS102 | B-105 | Сидоров |
Кандидатные ключи могут быть (course_id, room_number) и (course_id, instructor), но зависимость instructor → room_number создаёт избыточность и аномалии при смене аудитории преподавателя.
После (НФБК) — разнести "кто ведёт" и "где и когда идёт занятие":
| course_id | instructor |
|---|---|
| CS101 | Петров |
| course_id | room_number | time_slot |
|---|---|---|
| CS101 | A-201 | Пн 10:00 |
Для большинства бизнес-приложений целевая схема — 3НФ или НФБК; дальше идут формы для узких теоретических случаев.
4НФ — четвёртая нормальная форма
Критерий: выполнена НФБК, и в таблице нет независимых многозначных зависимостей для одной сущности. Если у сотрудника несколько навыков и несколько проектов, и эти списки не связаны друг с другом, хранить их в одной таблице (employee_id, skill, project) приводит к декартову произведению строк.
До (нарушение 4НФ) — у E001 два навыка и два проекта → четыре строки вместо двух+двух:
| employee_id | skill | project |
|---|---|---|
| E001 | Java | ProjectX |
| E001 | Java | ProjectY |
| E001 | Python | ProjectX |
| E001 | Python | ProjectY |
Строки Java + ProjectY и Python + ProjectX могут не соответствовать реальности — это артефакт смешения независимых списков.
После (4НФ):
employee_skills:
| employee_id | skill |
|---|---|
| E001 | Java |
| E001 | Python |
employee_projects:
| employee_id | project |
|---|---|
| E001 | ProjectX |
| E001 | ProjectY |
5НФ (форма соединения) на практике встречается ещё реже — при декомпозиции по зависимостям соединения; для типового OLTP достаточно знать, что она существует.
Аномалии обновления
| Аномалия | Проявление |
|---|---|
| Вставки | Нельзя добавить отдел без сотрудника, если отдел хранится только в строке сотрудника |
| Удаления | Удаление последнего сотрудника стирает сведения об отделе |
| Модификации | Смена названия отдела требует обновления многих строк |
Нормализация разбивает данные так, чтобы каждый факт хранился в одном месте.
Практика — декомпозиция "Заказы"
Исходная денормализованная таблица (ключ — (order_id, product_code)):
Заказы (номер_заказа, дата, id_клиента, фио_клиента, телефон, город,
код_товара, наименование_товара, категория, цена, количество)
Зависимости:
order_id→date,client_idclient_id→full_name,phone,cityproduct_code→name,category,price(order_id, product_code)→quantity
Итоговая схема в 3НФ (PostgreSQL: SERIAL, CURRENT_DATE; в MySQL — AUTO_INCREMENT):
Код ITЗагрузка примера кода…
Денормализация
Определение: сознательное нарушение нормальных форм ради скорости отчётов, меньшего числа JOIN, витрин или кэша в документе. В семи стратегиях масштабирования БД и в девяти рычагах производительности это приём на уровне схемы — рядом с индексами и материализованными представлениями.
Нужен явный источник правды и синхронизация копий — триггеры, job пересборки, ETL, события (CQRS). Дублирование полей в одной "плоской" таблице (клиент + товар + заказ в одной строке) убирает соединения при чтении, но усложняет обновление — правки должны затрагивать все копии согласованно.
Типичные сценарии:
- аналитические запросы с десятками соединений;
- высоконагруженное чтение в OLTP;
- исторические срезы в хранилище данных.
Чек-лист моделирования данных
Перед CREATE TABLE в проде пройдите схему по пунктам ниже. Полный цикл "концепция → DDL" — в Проектировании баз данных; ER-термины — Entity Relationship.
Имена и соглашения
- Имена таблиц и столбцов короткие и единообразные (лимит длины в целевой СУБД и в ORM).
- Таблицы — множественное число или единый стиль команды (
orders, не смешивать сOrder). - Внешние ключи читаются без схемы:
customer_id→customers.id.
Сущности
- Каждая сущность — существительное предметной области, не отчёт и не экран UI.
- Слабые сущности (строка заказа, оценка на курсе) имеют ключ через родителя или составной PK.
- Связь M:N разбита на таблицу-связку с собственным PK.
Атрибуты
- Ячейки атомарны (1НФ): нет списков через запятую в одном столбце.
-
NULLтолько там, где "неизвестно" или "не применимо", а не "забыли заполнить". - Бизнес-коды, которые должны быть уникальны, помечены
UNIQUE, даже при суррогатном PK.
Связи
- У каждой связи ясны обе стороны и кардинальность (1:1, 1:N, M:N).
- Для FK заданы политики
ON DELETE/ON UPDATE(не дефолт "как получится"). - Нет лишней дублирующей связи между теми же сущностями без разной семантики.
ER-модель и процессы
- Модель проверена на типовые операции — вставка, обновление, удаление без обхода ограничений.
- Решено, где хранить историю (отдельные таблицы версий,
valid_from/valid_to, архив). - Избыточность данных осознанна: либо 3НФ, либо денормализация с планом синхронизации — ниже.
- Выполнено правило минимальных данных: всё нужное есть, лишнего нет.
Прогоните нормальные формы на ключевых таблицах (1НФ–3НФ) и сверьте индексы под ожидаемые WHERE / JOIN — см. внутреннее устройство БД и рычаги производительности.
Контрольные вопросы
- Почему таблица с простым первичным ключом автоматически во 2НФ?
- Приведите пример транзитивной зависимости вне иерархии "сущность — подчинённая сущность".
- Чем нарушение 4НФ отличается от нарушения 3НФ? (подсказка: независимые списки значений.)
- Когда денормализация допустима без роста аномалий модификации?
- Почему 3НФ не устраняет все избыточности? (подсказка: НФ Бойса–Кодда.)
См. также
- Entity Relationship — нотации и ключи
- Реляционная модель данных
- Ограничения целостности в SQL
- Семь стратегий масштабирования БД
- Девять рычагов производительности БД
- Чек-лист самопроверки — пункты 15–16, 48