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