NULL in SQL: why = NULL never matches and how to handle missing values
NULL means "unknown", so comparisons with it are neither true nor false. Use IS NULL, COALESCE and careful aggregates to avoid rows silently disappearing.
NULL is not zero and not an empty string. It is a marker for “no value” or “unknown”. SQL follows a simple rule that causes many bugs: almost any operation involving an unknown value produces another unknown value.
Three-valued logic
A condition in SQL can be true, false or unknown. WHERE keeps only rows where the condition is true, so unknown rows are dropped silently.
SELECT NULL = NULL; -- NULL (unknown), not true
SELECT NULL <> 1; -- NULL
SELECT NULL + 1; -- NULL
SELECT 'x' || NULL; -- NULL in standard SQL, SQLite and PostgreSQL
MySQL uses CONCAT() for strings, and CONCAT('x', NULL) is also NULL. CONCAT_WS skips NULL arguments.
Test with IS NULL
SELECT name FROM customers WHERE email = NULL; -- always returns nothing
SELECT name FROM customers WHERE email IS NULL; -- correct
SELECT name FROM customers WHERE email IS NOT NULL;
Rows vanish from negative conditions
In the sample data, Dana’s city is NULL. This query looks like it returns everyone outside London, but it returns only Brian:
SELECT name FROM customers WHERE city <> 'London';
-- Include the unknowns explicitly:
SELECT name FROM customers WHERE city <> 'London' OR city IS NULL;
PostgreSQL and SQLite also have a NULL-safe comparison: city IS DISTINCT FROM 'London' in PostgreSQL, and city IS NOT 'London' in SQLite. MySQL uses the <=> operator: NOT (city <=> 'London').
The NOT IN trap
If a subquery used with NOT IN returns a NULL, no row can ever satisfy the condition, and the query returns an empty result. Use NOT EXISTS, or filter the NULLs out of the subquery:
SELECT name FROM customers
WHERE id NOT IN (SELECT customer_id FROM orders WHERE customer_id IS NOT NULL);
Replacing NULL with a default
SELECT name, COALESCE(city, 'unknown') AS city FROM customers;
SELECT 100 / NULLIF(0, 0); -- NULLIF turns 0 into NULL: no division error
COALESCE returns its first non-NULL argument and works everywhere. IFNULL (MySQL, SQLite) and ISNULL (SQL Server) are two-argument variants. Note that MySQL’s ISNULL(x) only tests a value and does not replace it.
Aggregates ignore NULL
SELECT COUNT(*) AS all_rows, -- 4: counts rows
COUNT(email) AS with_email -- 3: counts non-NULL values
FROM customers;
SUM,AVG,MINandMAXskip NULLs. The average of 10, NULL and 20 is 15, not 10.SUMover zero rows, or only NULLs, returns NULL, not 0. Wrap it asCOALESCE(SUM(amount), 0)when you need a number.- If a NULL should count as zero, say so:
AVG(COALESCE(score, 0)).
Grouping, sorting and uniqueness
GROUP BYandDISTINCTput all NULLs into one group, even though NULL is not equal to NULL.- Sort position differs: SQLite and MySQL put NULLs first in ascending order, PostgreSQL puts them last. PostgreSQL and SQLite (3.30+) accept
ORDER BY city NULLS LAST; in MySQL, sort oncity IS NULLfirst. - A
UNIQUEcolumn usually allows many NULL rows. PostgreSQL 15 addedUNIQUE NULLS NOT DISTINCTto forbid that.
Prevent NULLs you don't want
The simplest fix is in the schema. Declare columns NOT NULL and give them a DEFAULT when a value must always exist. Then you only have to think about NULL where it really means “unknown”.
See also
- 0067 Your first SQL queries: SELECT, WHERE, ORDER BY, LIMIT and GROUP BY Read data from a table, keep only the rows you want, sort them, take the first few, and summarize groups with COUNT,… Beginner · How-to · Any system Article Queries 15 Oct 2026
- 0071 Fix: column “x” must appear in the GROUP BY clause or be used in an aggregate function PostgreSQL rejects a grouped query that selects a column with more than one possible value per group. Add the column to GROUP… Intermediate · Fix · Any system Article Queries 15 Oct 2026
- 0077 SQL cheat sheet: a runnable reference for everyday queries One SQL file that builds a tiny database and demonstrates filtering, sorting, grouping, joins, CASE, CTEs, window functions, safe updates and indexes.… Beginner · Reference · Any system Script Joins · Queries 15 Oct 2026
- 0215 mysql command-line client cheat sheet The connection options, built-in commands and everyday SQL you need in the mysql (and mariadb) command-line client, on one page. Beginner · Reference · Any system Article Administration · Queries 15 Oct 2026
- 0260 Design and build a library database in SQLite: tables, keys, joins, reports and backups Model books, members and loans, create tables with keys and constraints, load sample data, write join and aggregate reports, add indexes checked… Beginner · Tutorial · Any system Article Joins · Queries 15 Oct 2026
Repos in the wild
- DuckDB duckdb/duckdb An in-process analytical SQL database. Run fast SQL directly on CSV, Parquet and JSON files, from the command line or inside Python and other languages. GitHub
- SQLite (official Git mirror) sqlite/sqlite The source tree of SQLite, the self-contained SQL database engine embedded in phones, browsers and countless applications, mirrored to GitHub by its developers. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- commitpedia-admin +3,291 chars