SQL · Beginner

SQL NULL: find missing matches and filter safely

Use the shared customers-and-orders example to understand NULL, find rows without a match, and keep unmatched rows when filtering a LEFT JOIN.

Who this is for: learners who know the basics of SELECT and joins. The examples build on the SQL SELECT and WHERE lesson and the INNER JOIN and LEFT JOIN lesson.

What you will learn: what NULL means in a query result, how to test for it with IS NULL, and how a filter on the right-hand table can remove unmatched rows from a left join.

Use the shared sample data

These are the same customers and orders used in the other SQL lessons. The schema deliberately requires an order amount, so the NULLs in the join results below are generated for a customer with no matching order; they are not stored order amounts.

CREATE TABLE customers (
    customer_id INTEGER PRIMARY KEY,
    customer_name VARCHAR(50) NOT NULL
);

CREATE TABLE orders (
    order_id INTEGER PRIMARY KEY,
    customer_id INTEGER NOT NULL,
    amount DECIMAL(8, 2) NOT NULL
);

INSERT INTO customers (customer_id, customer_name) VALUES
    (1, 'Asha'),
    (2, 'Ben'),
    (3, 'Chloe');

INSERT INTO orders (order_id, customer_id, amount) VALUES
    (101, 1, 120.00),
    (102, 1, 80.00),
    (103, 3, 200.00);

The examples use standard LEFT JOIN and IS NULL syntax supported by MySQL and PostgreSQL. The queries are not tested against every database version.

What does NULL mean?

NULL represents a missing, unknown, or inapplicable value. It is not the same as zero, an empty string, or the word 'NULL'. In a LEFT JOIN, SQL produces NULLs for columns on the right side when no matching row exists.

SELECT
    c.customer_name,
    o.order_id,
    o.amount
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.customer_id
ORDER BY c.customer_id, o.order_id;
Expected result: Ben has no matching order
customer_nameorder_idamount
Asha101120.00
Asha10280.00
BenNULLNULL
Chloe103200.00

Test for NULL with IS NULL

To find customers who have no orders, check a right-side column that cannot be NULL for an actual order, such as its primary key:

SELECT c.customer_id, c.customer_name
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL
ORDER BY c.customer_id;
Expected result: customers without orders
customer_idcustomer_name
2Ben

Use IS NOT NULL to find values that are present. Do not write o.order_id = NULL or o.order_id <> NULL: comparisons with NULL produce an unknown result, not true or false. A WHERE clause keeps only rows for which its condition is true, so those comparisons do not find the missing values.

Why a WHERE filter can undo a LEFT JOIN

This query keeps only joined rows whose order amount is at least 100. Ben’s generated NULL does not satisfy the comparison, so his row is removed. Asha’s 80 order is removed too:

SELECT c.customer_name, o.order_id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.customer_id
WHERE o.amount >= 100
ORDER BY c.customer_id, o.order_id;
Expected result: WHERE removes unmatched and lower-amount rows
customer_nameorder_idamount
Asha101120.00
Chloe103200.00

Keep every customer and filter matching orders

If you want all customers but only want matching orders of at least 100, put the order condition in the ON clause. Customers with no qualifying order remain, with NULLs in the order columns:

SELECT c.customer_name, o.order_id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.customer_id
    AND o.amount >= 100
ORDER BY c.customer_id, o.order_id;
Expected result: every customer, with qualifying orders only
customer_nameorder_idamount
Asha101120.00
BenNULLNULL
Chloe103200.00

Try it yourself

Write a query that lists every customer and shows only orders below 100, while keeping customers without a qualifying order. The result should contain Asha with order 102, Ben with NULL order details, and Chloe with NULL order details.

Show the answer
SELECT c.customer_name, o.order_id, o.amount
FROM customers AS c
LEFT JOIN orders AS o
    ON o.customer_id = c.customer_id
    AND o.amount < 100
ORDER BY c.customer_id, o.order_id;

The amount condition belongs in ON so it limits matches without filtering out customers that have no matching order.

Key idea

Use IS NULL and IS NOT NULL to test missing values. With a LEFT JOIN, decide whether a right-side condition should filter the final rows (WHERE) or only which rows are allowed to match (ON).

Continue learning

Review SQL SELECT and WHERE or SQL INNER JOIN vs LEFT JOIN, then continue with SQL aggregation. Use the SQL formatter to review query layout; it does not execute queries or validate results.