NULL Values

NULL represents a missing or unknown value: a customer who didn't give a city, a product with no discount yet. It isn't zero and it isn't an empty string — and it follows special rules that cause more SQL bugs than anything else. This lesson makes those rules clear.

NULL is not a value you can compare

SQL
SELECT name FROM customers WHERE city = NULL;
Result no rows
name

No rows, even though Farah's city is NULL. Comparing anything with NULL — even NULL = NULL — gives unknown, and WHERE only keeps rows where the condition is true.

SQL
SELECT NULL = NULL AS equal, NULL <> 1 AS not_equal, 1 + NULL AS sum;
Result 1 row
equalnot_equalsum
NULLNULLNULL

IS NULL and IS NOT NULL

SQL
SELECT name, city FROM customers WHERE city IS NULL;
Result 1 row
namecity
Farah HaddadNULL
SQL
SELECT name, email FROM customers WHERE email IS NOT NULL AND country = 'Portugal';
Result 2 rows
nameemail
Ana Pereiraana.pereira@example.com
Isaac Leviisaac.levi@example.com

These special operators are the correct way to test for missing values.

NULL spreads through expressions

SQL
SELECT name, city, 'Lives in ' || city AS description
FROM customers
WHERE country IN ('France', 'Spain');
Result 3 rows
namecitydescription
Chloé MartinLyonLives in Lyon
Elena GarcíaMadridLives in Madrid
Farah HaddadNULLNULL

Any arithmetic or concatenation involving NULL produces NULL. (Oracle is an exception for strings: it treats an empty string as NULL and || ignores it.)

COALESCE: supplying a default

SQL
SELECT name, COALESCE(city, 'Unknown') AS city
FROM customers
WHERE country = 'France';
Result 2 rows
namecity
Chloé MartinLyon
Farah HaddadUnknown

COALESCE returns its first argument that isn't NULL. It accepts any number of arguments: COALESCE(mobile, landline, email, 'no contact'). Database-specific equivalents include IFNULL (MySQL, SQLite), ISNULL (SQL Server) and NVL (Oracle).

NULLIF: turning a value into NULL

SQL
-- 6 units of each product are kept for display.
-- What percentage of the current stock is that?
SELECT name, stock, ROUND(100.0 * 6 / NULLIF(stock, 0), 1) AS pct_reserved
FROM products
WHERE category = 'Electronics';
Result 4 rows
namestockpct_reserved
Wireless Earbuds6010
USB-C Charger1205
Mechanical Keyboard0NULL
Smart Plug758

NULLIF(a, b) returns NULL if a equals b, otherwise a. Its classic use is avoiding division by zero. (SQLite returns NULL for division by zero anyway; most other databases raise an error.)

NULL in aggregates

SQL
SELECT COUNT(*) AS customers,
       COUNT(city) AS with_city,
       COUNT(email) AS with_email
FROM customers;
Result 1 row
customerswith_citywith_email
1099

COUNT(*) counts rows; COUNT(column) counts non-NULL values. SUM, AVG, MIN and MAX also skip NULLs (lesson 18).

NOT and NULL

SQL
SELECT name, city FROM customers WHERE city <> 'Lisbon';
Result 7 rows
namecity
Ben CarterManchester
Chloé MartinLyon
David MüllerBerlin
Elena GarcíaMadrid
George BrownLondon
Hana NovakPrague
Isaac LeviPorto

Farah isn't in the result: her city isn't Lisbon, but NULL <> 'Lisbon' is unknown. If you want rows with no city too, say so: WHERE city <> 'Lisbon' OR city IS NULL.

IS DISTINCT FROM: NULL-safe comparison

SQL
SELECT name, city
FROM customers
WHERE city IS DISTINCT FROM 'Lisbon';
Result 8 rows
namecity
Ben CarterManchester
Chloé MartinLyon
David MüllerBerlin
Elena GarcíaMadrid
Farah HaddadNULL
George BrownLondon
Hana NovakPrague
Isaac LeviPorto

The standard IS [NOT] DISTINCT FROM treats NULL as an ordinary comparable value. It's supported by PostgreSQL, SQLite (3.39+) and SQL Server 2022; MySQL uses <=> for "null-safe equals". SQLite also allows IS and IS NOT with any value.

Should a column allow NULL?

When designing tables, mark columns NOT NULL unless a missing value is genuinely meaningful (lesson 42). Fewer NULLs mean fewer surprises in every query that touches the column.

Try it yourself

List all customers with their email, showing "(none)" when it's missing.

Try it yourself
Result