TGViewer
SQL Ready | Базы Данных SQL Ready | Базы Данных @sql_ready · 17.5K subscribers
Post #1957 2.39K
Почему NOT IN может вернуть пустой результат из-за NULL!

Одна из самых неприятных ловушек SQL — поведение NOT IN при наличии NULL. Запрос выглядит абсолютно корректно, но внезапно перестаёт возвращать строки.

Таблицы:
users(id)
bans(user_id)


Допустим, нужно получить пользователей, которых нет в bans. Часто пишут так:
SELECT *
FROM users
WHERE id NOT IN (
SELECT user_id
FROM bans
);


Пока в bans.user_id нет NULL — всё работает нормально. Но представим данные:
bans
-------
1
2
NULL


Теперь запрос внезапно вернет:
0 rows


Хотя пользователи без бана есть. Почему так происходит — SQL сравнивает условие примерно как:
id <> 1
AND id <> 2
AND id <> NULL


Но:
id <> NULL


не даёт TRUE или FALSE. Результат: UNKNOWN. А в WHERE проходят только TRUE. Из-за этого всё условие целиком перестаёт выполняться.

Это особенность трёхзначной логики SQL: TRUE, FALSE, UNKNOWN

Любое сравнение с NULL даёт UNKNOWN:
NULL = 1
NULL <> 1
NULL = NULL


Поэтому NOT IN с NULL внутри подзапроса становится опасным.

Безопасный вариант — NOT EXISTS:
SELECT *
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM bans b
WHERE b.user_id = u.id
);


Почему EXISTS работает нормально: сравнение идёт построчно; NULL не ломает всю проверку; оптимизатор обычно хорошо превращает это в anti-join.

Ещё вариант — явно убрать NULL:
SELECT *
FROM users
WHERE id NOT IN (
SELECT user_id
FROM bans
WHERE user_id IS NOT NULL
);


Но на практике: NOT EXISTS обычно считается более безопасным и читаемым решением.

Отдельный момент: NOT IN () и NOT EXISTS могут давать разные планы выполнения в зависимости от СУБД. Но логически для nullable данных: NOT EXISTS почти всегда предпочтительнее.

Быстрая проверка проблемы:
SELECT COUNT(*)
FROM bans
WHERE user_id IS NULL;


Если такие строки есть — NOT IN уже потенциально опасен.

🔥 NOT IN и NULL плохо сочетаются. Если в подзапросе появляется хотя бы один NULL, условие может перестать возвращать строки вообще. Для таких проверок надёжнее использовать NOT EXISTS.

➡️ SQL Ready | #практика
  • 👍 22
  • ❤ 10
  • 🔥 5
  • 🤝 1
More from @sql_ready
  1. Oct 6, 2026Проверяйте связи с учётом периода! В PostgreSQL 18 связь может учитывать ещё и период его…
  2. Oct 6, 2026⚡️5 фундаментальных курсов по ИБ по цене одного Это предложение для тех, кто готов войти в…
  3. Oct 6, 2026👍 Устройство PostgreSQL — подробная документация на русском языке! Материалы посвящены не…
  4. Oct 5, 2026COUNT(*) в PostgreSQL: MVCC, visibility map и стоимость выполнения! В PostgreSQL точный CO…
  5. Oct 5, 2026Как оплачивать зарубежные сервисы в 2026 году? Можно бегать между посредниками и бояться б…
  6. Oct 5, 2026Проверяйте JSON без преобразования! Если JSON приходит как text, необязательно делать ::js…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →