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