В таблице заказов хранятся идентификатор заказа, идентификатор клиента и кредитный лимит клиента; кредитный лимит определяется клиентом, а не заказом. Какую зависимость следует устранить разбиением таблицы и к чему приводит её сохранение?
Следует устранить транзитивную функциональную зависимость: идентификатор заказа определяет клиента, а клиент, в свою очередь, определяет кредитный лимит. Кредитный лимит нужно хранить в сущности клиента, а в заказе оставить только ссылку на клиента.
Сохранение такого атрибута в заказах создаёт дублирование и аномалии обновления: после изменения лимита клиента разные заказы могут содержать противоречивые значения.
Нормализация появилась как способ уменьшить избыточность данных и сделать правила предметной области явными в реляционной модели. Исходная проблема состояла в том, что один факт часто хранился во множестве строк, из-за чего изменения приводили к несогласованности.
Разбиение по функциональным зависимостям помогает отделить факт от сущности, которой он принадлежит. Третья нормальная форма в частности направлена на устранение зависимостей неключевых атрибутов от других неключевых атрибутов через ключ.
Пусть строка заказа содержит номер заказа, клиента и его кредитный лимит. Лимит зависит не от заказа, а от клиента: один и тот же клиент может иметь множество заказов, для которых лимит повторяется.
Если лимит изменится, нужно найти и изменить все заказы клиента. Пропущенная строка создаст противоречие: запросы по заказу и по клиенту начнут возвращать разные значения одного бизнес-факта.
Возникают также аномалии вставки и удаления. Нельзя сохранить лимит клиента без заказа, а удаление последнего заказа может случайно уничтожить сведения о клиенте и его лимите.
Зависимость имеет вид: заказ → клиент → кредитный лимит. Поскольку кредитный лимит определяется идентификатором клиента, он должен находиться в таблице клиентов, а таблица заказов должна ссылаться на клиента внешним ключом.
Минимальная схема выглядит так:
В такой схеме изменение лимита выполняется в одном месте, а внешний ключ гарантирует наличие соответствующего клиента. Это уменьшает риск рассинхронизации, но не означает, что любое повторение значения запрещено: иногда значение действительно является историческим снимком или результатом расчёта на момент заказа.
Если при оформлении заказа нужно зафиксировать применённый лимит, это уже другой факт. Его следует хранить в заказе под именем вроде «лимит, использованный при оформлении», явно отличая его от текущего лимита клиента. Нельзя механически удалять все повторяющиеся атрибуты без анализа их семантики.
В кредитной системе лимит клиента находился в каждой строке заказа. После изменения лимита часть заказов обновлялась фоновым заданием, а часть оставалась со старым значением. Отчёт по клиентам показывал текущий лимит, а аудит конкретного заказа — случайное значение из его строки.
Рассматривались три варианта:
Выбрали третий вариант: текущий лимит перенесли в таблицу клиента, а применённый при оформлении лимит оставили в заказе как отдельный неизменяемый исторический факт. В результате обновление текущего лимита стало однозначным, а аудит сохранил корректную историю.
1. Всегда ли повторение атрибута в дочерней таблице нарушает нормализацию?
Нет. Нужно определить смысл атрибута и его функциональные зависимости. Если значение является снимком состояния на момент события, например фактически применённой ставкой или ценой позиции, оно зависит от конкретного заказа, даже если исходно было получено из таблицы клиента или товара. Такое хранение может быть обязательным для воспроизводимости истории.
2. Достаточно ли перенести кредитный лимит в таблицу клиента, чтобы исключить противоречия?
Не всегда. Если бизнес-правило разрешает несколько версия лимита с периодами действия, одного столбца недостаточно. Нужна отдельная модель версий или периодов, а ограничения должны предотвращать пересекающиеся интервалы для одного клиента, если это требуется предметной областью.
3. Что изменится, если кредитный лимит зависит от пары «клиент и валюта»?
Тогда зависимость будет определяться составным ключом: лимит принадлежит не просто клиенту, а сочетанию клиента и валюты. Его следует хранить в отдельной таблице с ключом из идентификатора клиента и валюты; перенос только в таблицу клиента всё ещё оставит неоднозначность и не устранит дублирование.