Filtering & predicates
Concept  ·  SQL & Querying

x NOT IN (list-with-a-NULL) can never be TRUE — one NULL empties the whole result, silently; NOT EXISTS is NULL-safe.

NOT IN expands to a chain of not-equal comparisons joined by AND. Any comparison with NULL is UNKNOWN, and AND with UNKNOWN is never TRUE, so the WHERE keeps no row.

See it break
customers
idname
1Ada
2Bo
3Cy
orders
customer_id
1
NULL
The trap
SELECT
  name
FROM
  customers WHERE
  id NOT IN (
    SELECT
      customer_id
    FROM
      orders )
ORDER BY
  name;
The fix
SELECT
  name
FROM
  customers c
WHERE
  NOT EXISTS (
    SELECT
      1
    FROM
      orders o
    WHERE
      o.customer_id = c.id
  )
ORDER BY
  name;

0 rows, no error — the 'customers we've never converted' report says there are none.

The rule

Use NOT EXISTS (NULL-safe), or filter the NULLs out of the subquery with WHERE key IS NOT NULL.

WHERE NOT EXISTS (SELECT 1 FROM t WHERE t.key = base.key)
Shows up elsewhere
Anti-join three ways
Try one →Prove it in practice →