Объясните механизм: почему ограничение CHECK может пропустить строку с отсутствующим значением, хотя проверяемое условие для него не выполняется?
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 равен нулю или удовлетворяет условию; это означает, что условие нельзя подтвердить как ложное.
В примере 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.
CHECK условие, не допускающее отрицательные значения, чтобы запретить NULL?Нет. Для NULL такое условие обычно возвращает UNKNOWN, а CHECK отклоняет только FALSE. Наличие CHECK (значение >= 0) контролирует отрицательные известные значения, но само по себе не делает столбец обязательным. Для этого нужен NOT NULL или явная часть условия значение IS NOT NULL.
NULL в CHECK отличается от поведения NULL в WHERE?В WHERE строка сохраняется только при результате TRUE; FALSE и UNKNOWN отбрасываются. В CHECK нарушение фиксируется только при FALSE, поэтому UNKNOWN допускается. Из-за этого одинаковое логическое выражение может отфильтровать строку в запросе, но не нарушить ограничение при вставке.
NULL?Сравнение с NULL также даёт UNKNOWN, поэтому оно не является проверкой на отсутствие значения. Для этого предназначены предикаты IS NULL и IS NOT NULL; они возвращают однозначные TRUE или FALSE и корректно участвуют в ограничениях и запросах.