Rows with no match
Both find rows in TableA with no TableB counterpart:
| |
NOT EXISTS is the better default:
Correctness with NULLs. LEFT JOIN breaks when the join condition is not exactly equivalent, with nullable columns, or when a joined-table filter lands in WHERE instead of ON — silently an inner join. Keep every joined-table filter inside ON:
| |
Optimizer intent. SQL Server recognizes NOT EXISTS as an anti-semi join, stopping at the first match and using indexes better; modern versions optimize both, but the subquery states intent more clearly and large-table plans follow.
NOT IN is to be avoided unless the subquery provably cannot return NULLs:
| |
One NULL there and the query returns no rows at all — looks like data loss, not a bug.
| Pattern | Correct with NULLs | Optimizer-friendly | Verdict |
|---|---|---|---|
NOT EXISTS | yes | yes | best default |
LEFT JOIN … IS NULL | sometimes | usually | with care |
NOT IN | no | sometimes | avoid |