datarekha

NULL and three-valued logic

Why col = NULL is silently wrong, how NULL behaves in aggregates and groups, and the handful of functions that tame it.

6 min read Intermediate SQL Lesson 6 of 27

What you'll learn

  • The TRUE / FALSE / NULL truth table that breaks naive filters
  • How aggregates and GROUP BY treat NULLs
  • COALESCE, NULLIF, IFNULL — what each one is for
  • Telling 'missing' apart from 'empty string'

Before you start

NULL means unknown. Not zero, not empty string — we simply do not know. SQL takes that literally: almost any expression that touches a NULL returns NULL rather than true or false. That one rule is behind more “why are my numbers off?” tickets than anything else in the language.

Three values, not two

Ordinary languages have TRUE and FALSE. SQL has a third, UNKNOWN, written as NULL:

ExpressionResult
1 = 1TRUE
1 = 2FALSE
1 = NULLNULL
NULL = NULLNULL
NULL <> NULLNULL

The catch is that WHERE keeps a row only when the predicate is TRUE — and NULL is not TRUE. So WHERE col = NULL returns zero rows, even for the rows where col really is NULL. And note that NULL = NULL is not TRUE either: two unknowns cannot be confirmed equal. The only correct test is the dedicated IS NULL, which always returns a real true or false:

labelvaluevalue = NULLvalue IS NULL
has_value5NULLfalse
is_zero0NULLfalse
is_nullNULLNULLtrue

See how value = NULL is itself NULL on every row — useless as a filter — while IS NULL actually tells the truth.

NULL in aggregates — quietly skipped

Every aggregate except COUNT(*) ignores NULLs. Given the column x holding 10, 20, NULL, 30:

SELECT COUNT(*) AS rows_total,
       COUNT(x) AS non_null,
       SUM(x)   AS sum_x,
       AVG(x)   AS avg_x
FROM   demo;
rows_totalnon_nullsum_xavg_x
436020

AVG(x) is (10 + 20 + 30) / 3 = 20, not divided by 4 — the NULL is dropped from both the sum and the count. Whether that is what you want depends on the question; if missing should count as zero, you must say so with COALESCE. (GROUP BY, by contrast, is one of the few places SQL relaxes the rule and gathers all NULLs into a single group, for usability.)

COALESCE and NULLIF

COALESCE(a, b, c, …) returns the first non-NULL argument — the everyday way to supply a default:

namenicknameemailCOALESCE(nickname, name)COALESCE(email, ‘(none)‘)
AvaAava@x.comAava@x.com
BoNULLbo@x.comBobo@x.com
CyCNULLC(none)

Its mirror image, NULLIF(a, b), returns NULL when a = b and a otherwise — whose classic use is treating an empty string as genuinely missing:

nameemailemail IS NULLNULLIF(email, ”) IS NULL
Avaava@x.comfalsefalse
Bo(empty string)falsetrue
CyNULLtruetrue

email IS NULL catches only Cy. NULLIF(email, '') IS NULL catches both Bo (empty) and Cy (NULL) — usually exactly what you mean by “do we actually have an email for this person?”

Practice

Quick check

0/3
Q1How many rows does SELECT * FROM users WHERE email <> 'a@b.com' return, if some users have NULL email?
Q2AVG(score) over the values 10, 20, NULL, 30 returns?
Q3To treat empty string the same as NULL when counting 'how many users have an email', best expression?

Sign in to track your progress

Completed lessons, your XP, level, and streak save to your account — it's free and takes a few seconds.

Practice this in an interview

All questions

Related lessons

Explore further

Skip to content