openlang

NOT IN и один NULL

SELECT * FROM orders o WHERE o.customer_id NOT IN (SELECT id FROM blocked) перестаёт возвращать что-либо, как только в blocked.id окажется хотя бы один NULL. Без ошибки. Ноль строк.

Причина механическая. x NOT IN (a, b, NULL) означает x <> a AND x <> b AND x <> NULL. Последнее сравнение даёт unknown и никогда не даёт true. AND с неизвестным операндом может оказаться false или unknown, но не true, а WHERE оставляет только строки, где условие истинно.

Исправлений три, и они не равноценны.

  1. Отфильтровать подзапрос через WHERE id IS NOT NULL. Правильно, но каждому следующему автору придётся об этом помнить.
  2. Переписать как NOT EXISTS (SELECT 1 FROM blocked b WHERE b.id = o.customer_id). NULL в blocked ни с чем не совпадает, поэтому никого не блокирует. Я обычно выбираю этот вариант.
  3. Объявить blocked.id как NOT NULL. Лучший из трёх, если столбец изначально не должен был хранить неизвестные значения, а для списка заблокированных id он и не должен был.

У IN та же проблема в более мягкой форме. Он может вернуть unknown там, где ждали false, и это бьёт, только когда результат кто-то отрицает.

Разговор продолжается 2

Следуя доводам
  1. Ссылка на ответ

    В Julia 1 in [2, missing] возвращает missing, ровно ваш случай с NOT IN. Те же правила трёхзначной логики действуют везде. missing == 1 даёт missing, true | missing даёт true, а false & missing даёт false, как OR и AND в SQL.

    Разница там, где WHERE в SQL молча выбросил бы строку. if missing выбрасывает TypeError, как и filter с предикатом, который возвращает missing. Ловушка не может сработать тихо. Приходится выбрать coalesce, skipmissing или isequal, и выбор остаётся в коде.

  2. Ссылка на ответ

    Что здесь означает отсутствие значения? NULL в этом списке может быть недозаполненной строкой или заблокированным аккаунтом, потерявшим id. NOT EXISTS молча решает, что он никого не блокирует. Третий вариант заставляет сказать это саму схему.

Другие стороны того же вопроса

  1. Нужен ли общему числу и странице один снимок?

    PostgreSQL 18, уровень Read Committed, два сеанса. Экран отчёта насчитывает 12 подходящих заказов. Другой сеанс вставляет под…

    Ответы 2sqlclojure