Одна из самых коварных ловушек в SQL связана с оператором NOT IN и значением NULL. На первый взгляд поведение кажется очевидным, но на практике строки с NULL тихо исчезают из результата — без единой ошибки и без предупреждения.
NULL — это не «пусто», это «неизвестно»
Ключ к пониманию проблемы — семантика NULL в SQL. NULL не равен пустой строке, нулю или какому-либо другому значению. NULL означает «значение неизвестно». Из этого вытекает трёхзначная логика SQL: результат логического выражения может быть не только TRUE или FALSE, но и UNKNOWN.
Что происходит при сравнении с NULL
Рассмотрим, как SQL вычисляет выражение NOT IN для разных значений поля city:
- 'Москва' NOT IN ('Москва') → FALSE — значение найдено в списке, условие не выполняется
- 'Питер' NOT IN ('Москва') → TRUE — значение не найдено, условие выполняется
- NULL NOT IN ('Москва') → UNKNOWN — сравнение с неизвестным значением даёт неизвестный результат
В результирующую выборку попадают только строки, для которых условие WHERE вернуло TRUE. Строки с результатом FALSE и UNKNOWN одинаково отсекаются. Именно поэтому строки, где city IS NULL, молча исчезают из результата.
Как сохранить строки с NULL в выборке
Если нужно, чтобы строки с NULL также попадали в результат, необходимо явно обработать этот случай в условии WHERE:
- Добавьте OR city IS NULL к условию фильтрации
- Например: WHERE city NOT IN ('Москва') OR city IS NULL
- Альтернативно — используйте NOT EXISTS с подзапросом: он корректно обрабатывает NULL без дополнительных условий
Что сделать на практике
- Перед использованием NOT IN проверьте, может ли соответствующий столбец содержать NULL.
- Если NULL допустим — всегда добавляйте явную проверку OR column IS NULL.
- При работе с подзапросами в NOT IN убедитесь, что подзапрос не возвращает NULL — иначе вся выборка может оказаться пустой.
- Рассмотрите замену NOT IN на NOT EXISTS: последний более предсказуем при наличии NULL.
Вывод
Трёхзначная логика SQL (TRUE / FALSE / UNKNOWN) — не баг, а архитектурная особенность стандарта. Понимание того, как NULL влияет на результаты фильтрации, позволяет избежать трудноуловимых ошибок в аналитических запросах и дашбордах, которые строятся на их основе.