Почему одно и то же условие с неизвестным результатом может пройти ограничение CHECK, но исключить строку и...

Почему одно и то же условие с неизвестным результатом может пройти ограничение CHECK, но исключить строку из результата запроса?

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

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

В 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 отбрасываются. Это объясняет различие между контролем целостности при записи и фильтрацией уже существующих данных.

CREATE TABLE payments ( amount DECIMAL(10, 2), CHECK (amount > 0) ); INSERT INTO payments (amount) VALUES (NULL); -- обычно успешно SELECT amount FROM payments WHERE amount > 0; -- NULL не возвращается

Если NULL недопустим, одного CHECK недостаточно: требуется отдельное ограничение NOT NULL. Если NULL допустим как «сумма пока неизвестна», запросы должны явно учитывать это состояние, например отдельной веткой с проверкой на NULL.

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

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

В таблице платежей поле суммы стало необязательным из-за отложенной сверки с внешней системой. Разработчик добавил CHECK на положительность суммы и решил, что отрицательные и неизвестные суммы будут исключены. В результате строки с NULL успешно сохранялись, но отчёт с фильтром по положительной сумме их не показывал.

Рассматривались два варианта. Запретить NULL через NOT NULL было бы проще для отчётов, но невозможно до завершения сверки; оставить только CHECK сохраняло рабочий процесс, но создавало риск неполных финансовых показателей.

Выбрали явное разрешение NULL и отдельный статус сверки, а отчёты разделили на подтверждённые платежи и платежи с неизвестной суммой. Это сохранило возможность отложенной загрузки и устранило смешение «нулевой», «неизвестной» и подтверждённой суммы.

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

  1. Вопрос: Гарантирует ли CHECK с условием положительности, что значение столбца не равно NULL?

    Ответ: Нет. При NULL сравнение обычно даёт UNKNOWN, а CHECK отклоняет только FALSE. Для гарантии наличия значения нужен NOT NULL либо условие, которое явно запрещает NULL.

  2. Вопрос: Что произойдёт с условием, объединённым через AND, если одна его часть UNKNOWN?

    Ответ: Результат зависит от второй части. UNKNOWN AND TRUE даёт UNKNOWN, поэтому строка будет исключена WHERE и обычно пройдёт CHECK; UNKNOWN AND FALSE даёт FALSE, поэтому строка будет исключена WHERE и нарушит CHECK. Нельзя считать UNKNOWN безусловным аналогом TRUE или FALSE.

  3. Вопрос: Почему добавление NOT NULL может изменить не только вставку данных, но и смысл запросов?

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