Часто требуется формировать запросы, где критерии фильтрации определяются динамически — список значений задаётся пользователем интерфейса, а пропущенные значения (NULL) также должны уметь попадать или не попадать в результат. Такой запрос должен быстро работать на больших объёмах и быть корректным с точки зрения семантики SQL.
Некорректное комбинирование условий IN, NOT IN и проверки на NULL приводит к неожиданным результатам из-за того, что в SQL выражение "NULL IN (...)" всегда возвращает UNKNOWN, а "NOT IN" может привести к пустому результату, если хотя бы один из элементов списка — NULL. Производительность на больших данных при плохо написанном условии может резко упасть.
Правильное комбинирование выглядит так:
SELECT * FROM sales WHERE region IN ('Moscow','Samara') OR region IS NULL;
SELECT * FROM sales WHERE (region NOT IN ('Moscow','Samara') OR region IS NULL);
SELECT * FROM sales WHERE region NOT IN ('Moscow','Samara') AND region IS NOT NULL;
Ключевые особенности:
Что произойдёт, если проверить "WHERE field NOT IN ('a', NULL)"?
Результат всегда пустой, поскольку любая строка сравнивается с NULL в списке, что даёт UNKNOWN и фильтрует всё.
Пример кода:
SELECT * FROM test WHERE name NOT IN ('Ivan', NULL);
Даст ли "IN (NULL,...)" значение для строк с NULL?
Нет. Выражение NULL IN (...) всегда возвращает UNKNOWN, а не TRUE.
Как ускорить фильтрацию по большому списку значений?
Использовать индексы и предпочтительно применять JOIN с временной таблицей, если список большой (сотни/тысячи значений) — это ускоряет фильтрацию по сравнению с длинным IN.
В аналитическом отчёте применили "WHERE city NOT IN ('London', field_2, NULL)". Итог — полностью пустой результат, никто долго не мог понять причину потерянных строк.
Плюсы:
Минусы:
В CRM выбрали список клиентских регионов и добавили дополнительную кнопку "Включить незаполненные значения". Логика фильтра:
... WHERE city IN ('SPB','NNov') OR city IS NULL
Плюсы:
Минусы: