Разработчик добавил ограничение, требующее, чтобы скидка была неотрицательной. Почему строка с отсутствующей скидкой может пройти такую проверку?
Ограничение CHECK обычно отклоняет строку только тогда, когда его условие вычисляется в FALSE. Для отсутствующего значения NULL сравнение со скидкой даёт UNKNOWN, а не FALSE, поэтому строка может пройти проверку. Если скидка обязательна, одного CHECK недостаточно: нужно дополнительно задать NOT NULL.
SQL поддерживает NULL для представления неизвестного, неприменимого или ещё не заданного значения. Из-за этого логика SQL использует три состояния: TRUE, FALSE и UNKNOWN, чтобы не превращать неизвестное значение в обычный факт.
Декларативные ограничения нужны для передачи правил целостности базе данных. Для CHECK это означает проверку логического предиката, но не автоматическое требование заполненности всех участвующих столбцов.
Пусть скидка не может быть отрицательной, но при создании товара её иногда забывают указать. Условие неотрицательности защищает от отрицательных чисел, однако не запрещает пропуск значения.
Если приложение считает NULL нулевой скидкой, отчёты и расчёты могут дать неверный результат. Дополнительно возникают неоднозначность бизнес-смысла и различия между операциями сравнения: NULL нельзя корректно обрабатывать как обычное число.
В SQL сравнение любого значения с NULL не даёт TRUE или FALSE: результатом становится UNKNOWN. Например, проверка скидки на неотрицательность для NULL не может установить, что условие нарушено.
Для CHECK важен принцип: запись нарушает ограничение, когда предикат равен FALSE; UNKNOWN обычно не считается нарушением. Поэтому обязательность и допустимый диапазон — разные свойства модели.
NOT NULL запрещает отсутствие значения, а CHECK контролирует допустимость уже заданного значения. Порядок записи ограничений в объявлении не меняет их смысл.
Если NULL имеет отдельный бизнес-смысл, например «скидка ещё не рассчитана», добавлять NOT NULL нельзя без изменения модели. Тогда нужно явно учитывать NULL в запросах, отчётах и вычислениях, а не считать его нулём.
В каталоге товаров скидка рассчитывалась отдельным процессом после загрузки товара. Команда добавила только проверку неотрицательности и обнаружила, что незавершённые товары попадали в выборки как товары без скидки; часть отчётов трактовала их как товары с нулевой скидкой.
Рассматривались два варианта. Запретить NULL через NOT NULL было просто и усиливало целостность, но требовало изменить порядок загрузки или временно использовать отдельный статус обработки. Сохранить NULL позволяло загружать данные поэтапно, но требовало явной фильтрации и усложняло аналитику.
Выбрали NULL только для незавершённых товаров, добавили отдельный статус обработки и запретили публикацию товара до расчёта скидки. Это сохранило двухфазную загрузку, но не позволило незаполненным значениям незаметно попадать в пользовательские отчёты.
Ответ: Нет. CHECK проверяет логическое условие, а NULL может дать UNKNOWN, которое не считается нарушением. Обязательность задаётся отдельным ограничением NOT NULL; обычно эти ограничения применяют вместе, если столбец должен быть и заполнен, и находиться в допустимом диапазоне.
Ответ: Условие, построенное так, чтобы для NULL результатом было FALSE, отклонит такую строку. Однако полагаться на сложную логику CHECK вместо NOT NULL обычно хуже: намерение модели становится менее очевидным, а отдельное NOT NULL проще проверять, сопровождать и переносить между СУБД.
Ответ: Из-за трёхзначной логики. Если исходная проверка для NULL дала UNKNOWN, её отрицание также даст UNKNOWN, а условие WHERE возвращает только строки с TRUE. Поэтому поиск нарушений через отрицание не обнаружит NULL; для них нужна отдельная проверка IS NULL либо корректная модель с NOT NULL.