Объясните механизм: почему ограничение CHECK может пропустить строку с отсутствующим значением, хотя провер...

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

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

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

CHECK отклоняет строку только тогда, когда его условие принимает значение FALSE. Если результат — TRUE или UNKNOWN, строка проходит проверку. Отсутствующее значение NULL обычно превращает сравнение в UNKNOWN, поэтому для запрета NULL нужно отдельно использовать NOT NULL или явно проверять значение через IS NOT NULL.

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

SQL должен различать как минимум два состояния: известное значение и отсутствие известного значения. Для этого используется NULL, а логика SQL расширена до трёх значений: TRUE, FALSE и UNKNOWN.

Такой подход позволяет хранить неполные данные, не выдавая неизвестное значение за обычное ложное или нулевое. Побочный эффект — привычные сравнения и логические условия с NULL требуют специального рассуждения.

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

Разработчик может написать ограничение, которое проверяет диапазон, положительность или соответствие условию, но не запрещает NULL. В результате строка с отсутствующим значением будет принята, хотя бизнес-правило требует именно известного значения.

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

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

При вычислении выражения с NULL результат большинства сравнений становится UNKNOWN. Например, проверка «значение больше нуля» не является ни истинной, ни ложной, если само значение неизвестно.

Семантика CHECK такова: ограничение нарушено только при результате FALSE. Поэтому UNKNOWN считается допустимым результатом. Это не означает, что NULL равен нулю или удовлетворяет условию; это означает, что условие нельзя подтвердить как ложное.

CREATE TABLE products ( price DECIMAL(10, 2) CHECK (price > 0), stock INTEGER NOT NULL CHECK (stock >= 0), discount DECIMAL(5, 2) CHECK (discount IS NOT NULL AND discount >= 0) );

В примере price может быть NULL: выражение price > 0 даст UNKNOWN, и CHECK его пропустит. stock обязан иметь значение из-за NOT NULL, а для discount запрет отсутствующего значения включён непосредственно в условие.

Если столбец должен быть обязательным независимо от диапазона, предпочтительнее явно объявить NOT NULL. Если допустимость NULL зависит от более сложного правила, его нужно сформулировать в CHECK с использованием IS NULL или IS NOT NULL.

Важно отличать CHECK от предикатов в операторе WHERE: WHERE оставляет только строки с результатом TRUE, поэтому и FALSE, и UNKNOWN исключаются. В ограничении CHECK UNKNOWN не считается нарушением, что и создаёт различие.

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

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

Рассматривались два варианта. Можно было оставить NULL и обрабатывать его в каждом запросе, но это увеличивало риск разных трактовок. Можно было запретить NULL на уровне схемы, однако тогда потребовалось бы заранее выбрать значение по умолчанию и отличать реальную нулевую скидку от отсутствующих данных.

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

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

  1. Достаточно ли записать в CHECK условие, не допускающее отрицательные значения, чтобы запретить NULL?

Нет. Для NULL такое условие обычно возвращает UNKNOWN, а CHECK отклоняет только FALSE. Наличие CHECK (значение >= 0) контролирует отрицательные известные значения, но само по себе не делает столбец обязательным. Для этого нужен NOT NULL или явная часть условия значение IS NOT NULL.

  1. Чем поведение NULL в CHECK отличается от поведения NULL в WHERE?

В WHERE строка сохраняется только при результате TRUE; FALSE и UNKNOWN отбрасываются. В CHECK нарушение фиксируется только при FALSE, поэтому UNKNOWN допускается. Из-за этого одинаковое логическое выражение может отфильтровать строку в запросе, но не нарушить ограничение при вставке.

  1. Почему для проверки отсутствующего значения нельзя использовать обычное сравнение с NULL?

Сравнение с NULL также даёт UNKNOWN, поэтому оно не является проверкой на отсутствие значения. Для этого предназначены предикаты IS NULL и IS NOT NULL; они возвращают однозначные TRUE или FALSE и корректно участвуют в ограничениях и запросах.