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

В заказах дата отмены должна быть заполнена только для отменённых заказов. Какое ограничение схемы надёжно выразит это правило?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Используйте проверочное ограничение CHECK, выражающее согласованность статуса и даты отмены. Оно должно требовать одновременно: для отменённого заказа дата присутствует, для любого другого статуса дата отсутствует.

Исторический контекст

Ограничения целостности появились как способ хранить правила данных рядом с самими данными, а не полагаться только на код приложений. Это особенно важно, когда одну базу используют несколько сервисов, фоновые задания, административные инструменты или прямые SQL-операции.

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

Постановка проблемы

Если проверку выполнять только в приложении, разные пути записи могут реализовать её по-разному. В результате у заказа появится статус, не соответствующий наличию даты отмены: например, активный заказ с заполненной датой или отменённый заказ без неё.

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

Подробное решение

Условие CHECK должно описывать обе допустимые комбинации. Минимальный пример:

CREATE TABLE orders ( order_id BIGINT PRIMARY KEY, status VARCHAR(20) NOT NULL CHECK (status IN ('new', 'paid', 'cancelled')), cancelled_at TIMESTAMP, CHECK ( (status = 'cancelled' AND cancelled_at IS NOT NULL) OR (status <> 'cancelled' AND cancelled_at IS NULL) ) );

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

Важно учитывать семантику NULL: результат CHECK, равный UNKNOWN, во многих СУБД не считается нарушением ограничения. Поэтому status объявлен как NOT NULL, а условие явно использует IS NULL и IS NOT NULL. Для сложных правил стоит проверить конкретную СУБД и её трактовку выражений.

Такое ограничение проверяет только значения одной строки. Оно не подходит для правил о других строках, например о запрете пересекающихся интервалов или необходимости существования связанной записи; для них могут потребоваться уникальность, внешние ключи, специальные ограничения или триггеры.

Ситуация из практики

В интернет-магазине сначала проверку выполнял только API. После добавления фонового процесса, автоматически переводившего заказы в статус cancelled, появились записи без даты отмены. Рассматривались три варианта: оставить проверку в приложении, использовать триггер или добавить CHECK.

Проверка только в приложении была простой, но не защищала от других каналов записи. Триггер позволял автоматически подставлять дату, однако добавлял скрытую логику и усложнял сопровождение. Выбрали CHECK, потому что правило является простым утверждением о текущей строке; дату отмены приложение должно передавать явно. После этого некорректные записи блокировались независимо от источника изменения.

Что кандидаты часто упускают

1. Достаточно ли написать только CHECK (status <> 'cancelled' OR cancelled_at IS NOT NULL)?

Нет. Такое условие требует дату у отменённых заказов, но разрешает дату у заказов с другими статусами. Если правило требует взаимного соответствия, нужно отдельно запретить дату для неотменённых заказов, как в двустороннем условии из примера.

2. Почему нельзя заменить это правило одним NOT NULL для даты отмены?

Потому что дата отмены обязательна не для всех строк, а только для подмножества со статусом cancelled. NOT NULL не умеет учитывать значение другого столбца; он выражает безусловное требование наличия значения. Условную обязательность задают комбинацией CHECK и, при необходимости, NOT NULL для самого управляющего столбца.

3. Когда вместо CHECK нужен триггер?

Триггер нужен, если правило зависит от других строк, таблиц, агрегатов или требует вычисляемого побочного действия. Например, проверка, что сумма платежей не превышает лимит клиента, обычно выходит за пределы простого CHECK. Для связи значений внутри одной строки CHECK предпочтительнее: он декларативнее, проще для анализа и не содержит скрытой процедуры изменения данных.