Joins I — semantics
Concept  ·  SQL & Querying

LEFT...IS NULL, NOT EXISTS, and NOT IN all express 'no match' — but NOT IN carries the P01 NULL trap; NOT EXISTS is the safe habit.

All three encode the same intent. LEFT-IS-NULL builds the full join then keeps the partnerless rows — readable, and the joined columns are there if you want them; its risk is someone later filtering a right column in WHERE (the P05 flagship). NOT EXISTS asks per-row 'any partner?' — NULL-safe by construction, the planner's favorite, and it can't fan out. NOT IN reads best and behaves worst: one NULL in the list and the whole result is empty (the P01 flagship).

See it break
students
sid
1
2
3
assign
student_id
1
NULL
The trap
SELECT
  sid
FROM
  students WHERE
  sid NOT IN (
    SELECT
      student_id
    FROM
      assign )
ORDER BY
  sid;
The fix
SELECT
  sid
FROM
  students s
WHERE
  NOT EXISTS (
    SELECT
      1
    FROM
      assign a
    WHERE
      a.student_id = s.sid
  )
ORDER BY
  sid;

The NOT IN spelling returned empty — one NULL student_id in the assignment list makes NOT IN never TRUE, where LEFT...IS NULL and NOT EXISTS both correctly list the unassigned students.

The rule

Default to NOT EXISTS; LEFT...IS NULL when you also need right-side columns; NOT IN only for provably-non-null lists, with a comment.

Shows up elsewhere
NOT IN meets NULLJoin keys with NULLs never match
Try one →Prove it in practice →