What NULL Really Means
NULL is not zero, not an empty string and not equal to itself. Learn what it represents, how it spreads through expressions and how to handle it deliberately.
- SQL Basics
- DDL
- DML
- SELECT
- WHERE
- Functions
- NULL and Logic
- Aggregate Functions
- GROUP BY
- JOIN
- Subqueries
- Set Operations
- CTEs
- Constraints
- Keys
- Relationships
- Database Design
- Normalisation
- Views
- Window Functions
- Advanced SQL
- Procedures and Functions
- Triggers
- Temporary Tables
- Transactions
- Isolation and Locking
- Indexes
- Query Performance
- SQL Security
- SQL Dialects
- Practical SQL
Concept
NULL is not a value. It is a marker meaning "no value here" - unknown, missing or not applicable. That single distinction explains every strange behaviour that follows.
| Value | Means | Takes storage | Equals itself |
|---|---|---|---|
0 | the number zero, a known quantity | Yes | Yes |
'' | an empty string, a known value of length 0 | Yes | Yes |
NULL | we do not know, or it does not apply | A flag only | No |
Syntax
-- The only two tests that work
WHERE column_name IS NULL
WHERE column_name IS NOT NULL
-- These never match anything
WHERE column_name = NULL
WHERE column_name <> NULLExample
-- In the sample data, Nikhil has no dept_id and no email
SELECT first_name, dept_id, email FROM employees WHERE dept_id IS NULL;
-- NULL is contagious in expressions
SELECT 10 + NULL AS arithmetic, -- NULL
CONCAT('a', NULL) AS concatenated, -- NULL in MySQL
NULL = NULL AS is_equal; -- NULL, not 1
-- Empty string and NULL are different things
SELECT COUNT(*) AS total,
COUNT(email) AS emails_present,
SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) AS emails_missing
FROM employees;Explanation
NULL = NULL returning NULL rather than true is the rule everything else follows from. If two people's phone numbers are both unknown, SQL cannot honestly claim they are the same number - so the answer is "unknown", not "yes".
Notice COUNT(*) versus COUNT(email) in the last query: COUNT(*) counts rows, COUNT(column) counts non NULL values in that column. The difference between them is exactly the number of missing emails.
Where NULL behaves inconsistently
| Context | How NULLs are treated |
|---|---|
WHERE | A row whose condition is UNKNOWN is dropped |
GROUP BY | All NULLs form one group together |
DISTINCT | All NULLs count as one value |
ORDER BY | MySQL and SQLite: first ascending. PostgreSQL and Oracle: last |
UNIQUE constraint | Most engines allow several NULLs, because they are not equal to each other |
| Aggregates | SUM, AVG, MIN, MAX, COUNT(col) all ignore NULLs |
Read that table twice. GROUP BY and DISTINCT treat NULLs as equal, while = treats them as not comparable. Both behaviours are in the standard, and both are intentional.
Important rules
- Any arithmetic or string operation involving
NULLreturnsNULL. - Any comparison with
NULLreturnsUNKNOWN, so the row failsWHERE. - Aggregates skip
NULLs, which makesAVGthe average of the present values, not of all rows. COUNT(*)counts rows;COUNT(expr)counts non NULL results.- MySQL offers
<=>, a NULL safe equality whereNULL <=> NULLis true. PostgreSQL and standard SQL spell itIS NOT DISTINCT FROM.
Common mistakes
- Writing
= NULLand concluding there are no missing values. - Expecting
WHERE status <> 'active'to include rows where status isNULL. - Using
NOT INagainst a subquery whose column contains aNULL, and getting an empty result. - Treating
NULLand''as interchangeable - and then having two kinds of "empty" in one column forever. - Reporting
AVG(bonus)as "the average bonus per employee" when half the employees have no bonus row at all.
Best practices
- Declare columns
NOT NULLunless unknown is a genuine, meaningful state for that column. - Pick one representation for "nothing" per column -
NULLor'', never both - and enforce it with a constraint. - Wrap nullable columns in
COALESCEwhenever they feed arithmetic or concatenation. - Say what you mean about NULL in reports:
AVG(COALESCE(bonus, 0))andAVG(bonus)answer different questions.
Practice
- Count how many employees have no department, using two different queries.
- Explain why
SELECT COUNT(email) FROM employeesandSELECT COUNT(*) FROM employeesdiffer. - Write a query returning every employee's email or the text
'not provided'.