Нормальные формы и осознанная денормализация
«Расскажите про нормальные формы» — вопрос-фильтр: заученное определение слышно сразу, а понимание проверяется просьбой привести пример.
Нормализация — приведение структуры таблиц к виду, где каждый факт хранится ровно в одном месте. Цель — не красота, а отсутствие аномалий при вставке, изменении и удалении данных.
Вопрос 1. Объясните первые три нормальные формы простыми словами
Что проверяют. Можете ли вы объяснить их без учебника — например, будущему стажёру. Формальные определения через функциональные зависимости знать полезно, но интервьюер обычно ждёт понятного объяснения плюс пример нарушения.
- 1НФ. В ячейке — одно атомарное значение, нет повторяющихся групп столбцов. Нарушение: колонка
phonesсо значением «+7999…, +7495…» или столбцыphone1,phone2,phone3. - 2НФ. Выполнена 1НФ, и каждый неключевой атрибут зависит от всего составного ключа, а не от его части. Нарушение: в таблице «позиция заказа» с ключом (order_id, product_id) лежит
product_name— он зависит только от product_id. - 3НФ. Выполнена 2НФ, и неключевые атрибуты не зависят друг от друга. Нарушение: в таблице «сотрудник» хранятся
department_idиdepartment_name— название зависит от отдела, а не от сотрудника.
Бытовая формулировка, которую любят: «каждый атрибут зависит от ключа, от всего ключа и ни от чего, кроме ключа». Это ровно 1–3НФ одной фразой.
Что происходит без нормализации: три аномалии
Возьмём ненормализованную таблицу и посмотрим на проблемы. Запустите пример — он самодостаточен:
-- НЕНОРМАЛИЗОВАННО: данные об отделе продублированы в каждой строке
CREATE TABLE employee_flat (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
department_id INTEGER NOT NULL,
department_name TEXT NOT NULL,
department_head TEXT NOT NULL
);
INSERT INTO employee_flat (name, department_id, department_name, department_head) VALUES
('Анна', 10, 'Продажи', 'Иванов'),
('Борис', 10, 'Продажи', 'Иванов'),
('Вера', 20, 'Разработка', 'Сидорова');
-- аномалия ИЗМЕНЕНИЯ: переименовали отдел только в одной строке
UPDATE employee_flat SET department_name = 'Сбыт' WHERE id = 1;
-- теперь у отдела 10 ДВА разных названия
SELECT department_id, department_name, COUNT(*) AS people
FROM employee_flat
GROUP BY department_id, department_name;
Запустите блок: у отдела с department_id = 10 окажется два разных названия — «Сбыт» и «Продажи». Данные противоречат сами себе, и никакой запрос уже не скажет, какое название верное.
Три классические аномалии:
- Аномалия изменения: факт хранится в N строках, обновили не все — данные разошлись (то, что мы только что воспроизвели).
- Аномалия вставки: нельзя завести отдел, пока в него не принят хотя бы один сотрудник, — некуда положить строку.
- Аномалия удаления: уволили последнего сотрудника отдела — вместе с ним исчезли сведения о самом отделе.
Нормализованный вариант убирает все три:
CREATE TABLE department (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
head TEXT NOT NULL
);
CREATE TABLE employee (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
department_id INTEGER NOT NULL REFERENCES department(id)
);
INSERT INTO department (title, head) VALUES ('Продажи','Иванов'), ('Разработка','Сидорова');
INSERT INTO employee (name, department_id) VALUES ('Анна',1), ('Борис',1), ('Вера',2);
-- название отдела теперь ровно в одном месте
UPDATE department SET title = 'Сбыт' WHERE id = 1;
SELECT e.name, d.title AS department, d.head
FROM employee e JOIN department d ON d.id = e.department_id
ORDER BY e.name;
Теперь переименование — одна строка, и разойтись данные не могут физически.
Вопрос 2. Когда вы намеренно денормализуете?
Что проверяют. Понимаете ли, что нормализация — не догма, и можете ли назвать цену решения. Ответ «денормализация — это плохо» так же слаб, как «нормализация всегда».
Обоснованные случаи:
- Историчность. Цена в позиции заказа, адрес доставки, реквизиты в счёте, тариф на момент подключения. Это не дубль, а снимок факта на момент события: справочник изменится, а документ обязан остаться прежним. Самый частый и самый правильный случай.
- Витрины и отчётность. Аналитические таблицы специально «плоские», чтобы не соединять десяток таблиц на каждый отчёт.
- Тяжёлые агрегаты. Счётчик комментариев или сумма заказа, посчитанные заранее, когда пересчёт на лету стоит дорого.
- Кэш чужих данных. Копия названия контрагента из внешней системы, чтобы не ходить в неё на каждый экран.
Цена денормализации всегда одна: согласованность становится вашей ответственностью. Поэтому в требованиях обязательно пишут, кто и когда обновляет производное значение и что считается источником истины.
Требование к денормализованному полю
R-330. Поле orders.items_count — производное, равно количеству
строк order_item для заказа.
Обновляется в той же транзакции, что и изменение состава.
Источник истины — order_item.
Ежесуточно в 03:00 выполняется сверка; расхождения
фиксируются в журнале и исправляются пересчётом.
Вопрос 3. Как хранить исторические данные, если справочник меняется?
Быстрый и сильный ответ — назвать варианты и выбрать. Первый: копировать значение в документ (цена, адрес, наименование на момент операции) — просто и надёжно для документов. Второй: версионировать сам справочник, добавив период действия (valid_from, valid_to) — нужен, когда важно уметь восстановить состояние справочника на любую дату, например для тарифов. Третий: журнал изменений отдельной таблицей — когда важен аудит «кто и когда поменял».
Выбор объясняется вопросом к бизнесу: «Если завтра поменяется название товара, что должно быть в чеке за прошлый месяц — старое или новое?» Ответ на этот вопрос и определяет модель.
Типичные ошибки кандидатов
- Пересказывают определения НФ, но не могут привести пример нарушения — сразу видно заучивание.
- Считают денормализацию ошибкой в любом виде и предлагают вычислять цену заказа из текущего прайса.
- Денормализуют без указания источника истины и правил сверки.
- Путают 2НФ и 3НФ: 2НФ имеет смысл только при составном ключе.
- Считают, что 1НФ нарушается любым JSON-полем. Хранить документ в JSON осознанно — нормально, если по его содержимому не строится реляционная логика.
Как ответить кратко
Нормализация — про то, чтобы каждый факт хранился в одном месте: «каждый атрибут зависит от ключа, от всего ключа и ни от чего, кроме ключа» — это 1–3НФ одной фразой. Без неё получаем три аномалии: изменения, вставки и удаления, — например, отдел переименовали в одной строке из трёх, и данные разошлись. Денормализую осознанно в четырёх случаях: историчность документа, витрины отчётности, дорогие агрегаты, кэш внешних данных. Причём историчность — это не дубль, а снимок факта на момент операции: цена в заказе обязана остаться прежней при изменении прайса. Плата за денормализацию — ручная поддержка согласованности, поэтому в требовании фиксирую источник истины, момент обновления и регламент сверки.