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 оставляет только строки, где условие истинно.
Исправлений три, и они не равноценны.
- Отфильтровать подзапрос через
WHERE id IS NOT NULL. Правильно, но каждому следующему автору придётся об этом помнить. - Переписать как
NOT EXISTS (SELECT 1 FROM blocked b WHERE b.id = o.customer_id). NULL вblockedни с чем не совпадает, поэтому никого не блокирует. Я обычно выбираю этот вариант. - Объявить
blocked.idкак NOT NULL. Лучший из трёх, если столбец изначально не должен был хранить неизвестные значения, а для списка заблокированных id он и не должен был.
У IN та же проблема в более мягкой форме. Он может вернуть unknown там, где ждали false, и это бьёт, только когда результат кто-то отрицает.