What exactly is NULL in SQL, and why does it behave so differently from every other value?
NULL is the marker for "no value here" — the absence of a value, not a value in itself. That distinction drives everything surprising about it. Because NULL means unknown, no comparison against it can ever be definitively true, so every comparison involving NULL evaluates to UNKNOWN rather than TRUE or FALSE. Even NULL compared with NULL is UNKNOWN, because the engine cannot know that two unknowns are equal.
CREATE TABLE staff (
id INTEGER PRIMARY KEY,
name VARCHAR(80) NOT NULL,
manager_id INTEGER
);
INSERT INTO staff (id, name, manager_id) VALUES
(1, 'Ada Lovelace', NULL),
(2, 'Grace Hopper', 1),
(3, 'Alan Turing', NULL);
-- NULL = NULL is UNKNOWN, so this returns 0, not 2.
SELECT COUNT(*) AS null_equals_null FROM staff WHERE manager_id = manager_id;
-- IS NULL is the only correct way to test for a missing value.
SELECT COUNT(*) AS top_level FROM staff WHERE manager_id IS NULL;
SELECT COUNT(*) AS has_manager FROM staff WHERE manager_id IS NOT NULL;The three counts come back 0, 2 and 1. The first is the headline: a query asking "which rows have manager_id equal to their own manager_id" returns nothing at all, because every comparison is UNKNOWN and WHERE keeps only rows that are definitively TRUE.
Three-valued logic also decides what happens in aggregates and in boolean expressions. SUM, AVG and COUNT ignore NULLs, so AVG over a column with two missing values averages the values that are present, not zero for the missing ones. A WHERE clause discards UNKNOWN rows entirely, but a HAVING or a CHECK constraint treats UNKNOWN as satisfied — a CHECK constraint only rejects a row when the condition is definitely FALSE. That is why CHECK (discount > 0) lets a NULL discount through: the engine does not know that it is wrong.
Finally, NULL propagates. Any arithmetic with a NULL operand yields NULL, and most functions do too, with a handful of exceptions such as COALESCE and NULLIF that exist precisely to handle it deliberately.