Skip to content
Foundations

NULL, and the third truth value

NULL is not a value. It is the absence of one, and every comparison it touches returns neither true nor false. Half of all real SQL bugs live here.

14 min · runs against the shop dataset

SQL has three truth values: true, false, and unknown. Any comparison involving NULL produces unknown, and a WHERE clause keeps a row only when its predicate is true. Unknown is not a reason to keep a row, and it is not a reason to drop it either — it is simply not true, and that is enough for the row to disappear.

Two nulls are not equal. They are not unequal either.

null_equals_nullbooleannull_differsbooleanactually_unknownboolean
nullnullt

1 row · 0 examined · 0 pages read

A predicate and its opposite do not cover the table

This is the consequence people are surprised by. If a column is nullable, WHERE total > 100 and WHERE total <= 100 do not partition the table between them — the rows where total is null fall out of both.

The two halves do not add up to the whole.

bigbigintsmallbigintall_rowsbigint
443890

1 row · 90 examined · 1 page read

NOT IN is the trap worth memorising

x IN (1, 2, NULL) is true for 1, and unknown — not false — for 3, because SQL cannot rule out that the null was a 3. NOT IN is therefore never true the moment the list contains a null, and the query returns nothing at all however obviously the value is absent.

run it

in_is_unknownbooleannot_in_is_fineboolean
tt

1 row · 0 examined · 0 pages read

The fix is not to remember this at the moment you write it, because you will not. The fix is NOT EXISTS, which asks a question that has an answer whatever the nulls are doing.

The operator that will compare a null

IS DISTINCT FROM is the null-safe comparison: two nulls are not distinct, and a null and a value are. It is what = refuses to say, and it is why it exists.

run it

samebooleandifferentboolean
tt

1 row · 0 examined · 0 pages read

Where grouping disagrees with equality

GROUP BY, DISTINCT and the set operations all treat two nulls as the same value, which = flatly refuses to do. Grouping and equality are different questions, and this is the clearest place that shows.

Many null rows, one group.

totalnumeric(10,2)
null

1 row · 90 examined · 1 page read

Did it land?