Tutorial

SQL NULL Guide: How IS NULL, COUNT & COALESCE Actually Work

Master SQL NULL: learn why = NULL fails, how COUNT(column) silently skips missing data, when to use COALESCE, and how to avoid the empty-string trap.

Anuj SainiAug 23, 2026Updated Aug 24, 20269 min read

NULL is the single most misunderstood concept in SQL, and it is everywhere in real data. Customer emails go missing. Phone numbers are never collected. Cities are left blank. And every one of those gaps is a NULL waiting to break your query — not with an error, but with a confidently wrong answer.

This guide walks through every way NULL silently corrupts your results, using a real e-commerce dataset with 500 customers, 2,000 orders, and 5,000 line items. Every number here is verified against a live PostgreSQL database. For the broader SQL toolkit, see our SQL JOIN Fan-Out Guide and SQL CTE Guide.



1. NULL Means "Unknown," Not "Empty"

When a database says a value is NULL, it is telling you: I do not know this value. It is not saying the value is blank, zero, or a space. It is saying the information does not exist.

This distinction matters because NULL does not follow the normal rules of comparison. Every other value in SQL can be tested with = or <>. NULL cannot. Comparing NULL to anything — even NULL itself — returns "unknown," which SQL treats as "not true," which means the row gets excluded from your results.

Spotting NULLs in Your Data

sql
SELECT customer_id, first_name, email, phone, city
FROM customers
ORDER BY (email IS NULL) DESC, (phone IS NULL) DESC, (city IS NULL) DESC
LIMIT 20;

This query floats the rows with missing values to the top using a clever trick: (email IS NULL) evaluates to true (1) for NULL rows and false (0) for the rest. Sorting by that in descending order puts all the NULL-containing rows first.

In our dataset, the first 20 rows reveal blank cells in email, phone, and city — those are NULLs, not empty strings.


2. The COUNT Trap: Your Numbers Are Quietly Wrong

This is the gotcha that catches every SQL learner, and it is a favorite in technical interviews.

The Setup

Simple question: how many of our 500 customers actually have an email on file? Let's count it two ways in one query:

sql
SELECT COUNT(*)     AS total_rows,
       COUNT(email) AS has_email
FROM customers;

The Result

total_rowshas_email
500443

Same table, two counts, two different answers. Here's why:

  • COUNT(*) counts every row — including rows where email is NULL. It returns 500.
  • COUNT(email) counts only rows where email has a non-NULL value. It returns 443.

The gap is 57 customers with no email. COUNT(email) silently skipped them. No error. No warning. If you reported "we have 443 customers" based on COUNT(email), you'd be wrong, and nothing on screen would tell you.

The Interview Trap

Interviewers love this one. The question "how many customers have an email?" has two valid interpretations, and COUNT(*) vs COUNT(column) gives different answers to each. Always clarify which count you need, and always check the gap between the two.


3. The WHERE Trap: NULL Dodges Every Comparison

So you've noticed 57 customers are missing an email. Instinct says: filter where email equals an empty value.

Attempt 1: Equals Empty String

sql
SELECT customer_id, email
FROM customers
WHERE email = '';

Result: 0 rows. An empty grid. But the counts just proved 57 are missing.

Attempt 2: Not Equals a Random Value

sql
SELECT customer_id, email
FROM customers
WHERE email <> 'zzz@nope.com';

Result: 443 rows. The same 443 with emails. The 57 missing-email rows are absent again.

Why Both Fail

NULLs fall through every comparison because unknown is never equal-to or not-equal-to anything. Here is the logic:

ExpressionResultRow Kept by WHERE?
NULL = 'anything'unknownNo
NULL <> 'anything'unknownNo
NULL = NULLunknownNo
NULL <> NULLunknownNo

WHERE only keeps rows where the condition evaluates to true. "Unknown" is not true, so NULL rows are silently excluded from every = and <> comparison. No error. It just hands you a wrong answer.


4. The Fix: IS NULL and IS NOT NULL

NULL has its own dedicated operators that do not use =:

Find the Missing Emails

sql
SELECT customer_id, first_name, email
FROM customers
WHERE email IS NULL;

Result: 57 rows — exactly the gap between COUNT(*) and COUNT(email).

Find Customers WITH Emails

sql
SELECT customer_id, email
FROM customers
WHERE email IS NOT NULL;

Result: 443 rows.

Feature / Criteria

The rule of thumb: test for NULL with IS NULL or IS NOT NULL, never = NULL. The expression = NULL returns unknown and quietly matches nothing.


5. Empty String Is NOT NULL

This is the trap that confuses even experienced analysts. "Missing" can mean two different things in a database, and they are not interchangeable.

The Reviews Table Proof

Our reviews table has 800 rows. Some reviews have an empty text box — an empty string. Let's count those:

sql
SELECT COUNT(*)
FROM reviews
WHERE review_text = '';

Result: 246. There really are empty-string reviews. Here, = '' works because these are genuinely empty strings, not NULL.

Now the same test with IS NULL:

sql
SELECT COUNT(*)
FROM reviews
WHERE review_text IS NULL;

Result: 0. None of them are NULL.

The Contrast

TableColumnMissing ValuesTypeCorrect Filter
customersemail57NULLIS NULL
reviewsreview_text246Empty string ''= ''

An empty string is a value you have — it is just blank. NULL is a value you don't have — it is unknown. They look identical in a results grid, but they answer to completely different filters.

Always Check Both

When data is missing, always ask: empty, or NULL? Run both = '' and IS NULL to find out which type of missing you are dealing with. The wrong filter returns zero rows and you will never know.


6. NULL Poisons Arithmetic

NULL does not just break filters — it corrupts calculations. When any value in an arithmetic expression is NULL, the entire result is NULL.

The Setup

Our order_items table has 5,000 rows. 169 of them have a NULL quantity. If we multiply quantity by unit price to get a line amount:

sql
SELECT item_id, quantity, unit_price,
       quantity * unit_price AS raw_line_amount
FROM order_items
WHERE quantity IS NULL
ORDER BY item_id
LIMIT 10;

The Result

item_idquantityunit_priceraw_line_amount
3NULL89.99NULL
17NULL45.00NULL
42NULL129.50NULL
............

Every raw_line_amount is NULL, even though unit_price right next to it has a real number. One missing quantity poisons the whole calculation. This is technically correct — unknown times anything is unknown — but it is useless in a revenue report.


7. COALESCE: The Safety Net

COALESCE returns the first non-NULL value from a list of arguments. Think of it as: "use this, or if that's missing, use this instead."

Fixing Arithmetic

sql
SELECT item_id, quantity,
       COALESCE(quantity, 0) AS quantity_clean,
       COALESCE(quantity, 0) * unit_price AS raw_line_amount
FROM order_items
WHERE quantity IS NULL
ORDER BY item_id
LIMIT 10;

Now quantity_clean reads 0, and raw_line_amount is 0 instead of NULL. The math survives.

Fixing Display Values

sql
SELECT customer_id, first_name,
       COALESCE(city, 'Unknown') AS city
FROM customers
ORDER BY (city IS NULL) DESC, customer_id
LIMIT 20;

Blank cities now read "Unknown" instead of showing as empty.

The Judgment Call: When NOT to COALESCE

Replacing NULL with 0 is not always correct. A missing rating is not a rating of zero. If you COALESCE(rating, 0), you drag your average down and lie about the product.

Feature / Criteria

The rule: use COALESCE to 0 for sums where "missing means nothing added." For averages, leave NULLs alone so they are excluded from the calculation.


8. NULL in Aggregates: How Each Function Treats It

Every aggregate function handles NULL differently, and knowing the difference prevents silent errors:

FunctionIgnores NULL?Example (rating column: 714 values, 86 NULL)Result
COUNT(*)No — counts all rowsCOUNT(*) on reviews800
COUNT(rating)Yes — skips NULLsCOUNT(rating)714
SUM(rating)Yes — skips NULLsSUM(rating)Sum of 714 values
AVG(rating)Yes — skips NULLsAVG(rating)2.941 (avg of 714, not 800)
AVG(COALESCE(rating, 0))No — treats NULL as 0AVG(COALESCE(rating, 0))2.625 (dragged down by 86 zeros)

The difference between AVG(rating) = 2.941 and AVG(COALESCE(rating, 0)) = 2.625 is significant. The first says "the average rating among people who rated." The second says "the average rating assuming non-raters gave zero." Only the first is honest.


9. Quick Reference: NULL Rules Cheatsheet

RuleCorrectWrong
Test for NULLWHERE email IS NULLWHERE email = NULL
Test for NOT NULLWHERE email IS NOT NULLWHERE email <> NULL
Test for empty stringWHERE review_text = ''WHERE review_text IS NULL
Replace NULL in mathCOALESCE(quantity, 0) * pricequantity * price
Count non-NULL valuesCOUNT(email)COUNT(*) (counts all rows)
Count NULL valuesCOUNT(*) - COUNT(email)COUNT(email = NULL)

10. Practice NULL Handling on Real Databases

Understanding NULL conceptually is the first step. Writing IS NULL queries fluently under interview pressure requires hands-on practice.

Practice SQL Data Cleaning & NULL Handling

Clean messy datasets, handle missing values with COALESCE, and deduplicate rows in our live PostgreSQL practice sandbox.

Start Data Cleaning Practice

Frequently Asked Questions

What does NULL mean in SQL?

NULL means 'unknown' or 'no value exists.' It is not the same as zero, an empty string, or a space. NULL represents missing information, and it follows different rules than regular values in comparisons and arithmetic.

Why does COUNT(*) return more rows than COUNT(column)?

COUNT(*) counts every row in the table, including rows where a column is NULL. COUNT(column) only counts rows where that specific column has a non-NULL value. The difference between the two reveals how many rows have NULL in that column.

Why does WHERE column = '' return zero rows when NULLs exist?

NULL is not an empty string. Comparing NULL with any operator (=, <>, >, <) returns 'unknown,' not true or false. WHERE only keeps rows where the condition is true, so NULL rows silently fall through every comparison. Use IS NULL to find them.

What does COALESCE do in SQL?

COALESCE returns the first non-NULL value from a list of arguments. It is commonly used to replace NULL with a default value, such as COALESCE(quantity, 0) to treat missing quantities as zero in calculations.

Is an empty string the same as NULL in SQL?

No. An empty string ('') is a value you have that happens to be blank. NULL is a value you do not have at all. They look identical in a results grid but answer to completely different filters: = '' catches empty strings, while IS NULL catches NULLs.

Anuj Saini

Written by

Anuj SainiFounder & Lead Instructor

Founder at Topfolio with 6+ years in data & analytics across JPMC, Ultrahuman, and high-growth startups. Sat on hiring panels, reviewed 500+ resumes, and writes practical SQL & data guides.