LEFT JOIN plus WHERE: the filter that turns it into an INNER JOIN
A WHERE on the right table's column after a LEFT JOIN drops every customer with no orders. Where the condition belongs, the IS NULL that finds the missing ones, and the count test: one page.
Get the free PDF
One page, print-ready, free to share. No signup needed.
Twelve thousand four hundred customers in the table, 8,310 in the report. Nobody filtered on purpose. Someone wrote a LEFT JOIN, then a WHERE on a column of the right table, and the 4,090 customers with no orders fell out. This page covers why, where the condition belongs, the IS NULL that finds the customers you were actually asked about, and the thirty-second count test. The print-ready A4 PDF is at the bottom.
The query
LEFT JOIN orders o: keep every customer, with or without orders.WHERE o.status = 'paid': for a customer with no orders,o.statusis NULL.NULL = 'paid'is not true, so the row is gone.
That second line is the whole trap. The LEFT JOIN did its job; the WHERE undid it.
The fix
AND o.status = 'paid'in the ON clause, with the join. It filters orders, not customers.- ON, not WHERE, for anything about the right table. The 4,090 customers come back, with
order_idNULL. WHERE o.id IS NULL, after the ON condition, gives the customers with no paid order.
Same join, two row counts
The condition filters orders in ON. In WHERE it filters the joined rows, and the customers with no orders are joined rows full of NULLs.
WHERE, 8,310 rows:
SELECT c.id, o.id AS order_id
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
WHERE o.status = 'paid';
-- no orders: no row
ON, 12,400 rows:
SELECT c.id, o.id AS order_id
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
AND o.status = 'paid';
-- no orders: order_id NULL
The customers with no paid order, 4,090 rows:
SELECT c.id
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
AND o.status = 'paid'
WHERE o.id IS NULL;
Remember
- NULL in a WHERE is never true and never false. The row is dropped either way.
WHERE c.country = 'FR'is fine: a left-table condition belongs in WHERE.- Count twice:
COUNT(*)with and without the WHERE.
The trap: three conditions, three results
| You write | What happens | Do instead |
|---|---|---|
WHERE o.status = 'paid' | customers with no orders have o.status NULL; the WHERE drops them | move it to ON |
WHERE o.status <> 'paid' | same: NULL <> 'paid' is not true either; the customer with no orders vanishes from both reports | move it to ON, or add OR o.status IS NULL |
ON ... AND c.country = 'FR' | a left-table condition in ON does not filter: every non-French customer still comes back, with NULL orders | left-table conditions go in WHERE |
The quick test before you send a number: run the query with and without the WHERE and compare COUNT(*). If the LEFT JOIN count equals the INNER JOIN count, the WHERE ate the NULLs.
The quiz
Which one keeps the customers with no orders?
A) SELECT c.id, o.id
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
WHERE o.status = 'paid';
B) SELECT c.id, o.id
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id
AND o.status = 'paid';
B. In A, a customer with no orders has o.status NULL, and NULL = 'paid' is not true, so the WHERE drops the row: the LEFT JOIN behaves like an INNER JOIN. In B the condition is part of the join, it filters orders only, and the customer stays with o.id NULL.
The four joins themselves are on the SQL joins sheet. This page is only the trap.
Frequently asked questions
Why does a WHERE clause turn my LEFT JOIN into an INNER JOIN?
Where should the condition on the right table go?
How do I find the customers with no paid order?
Does the same apply to a condition on the left table?
Get the free PDF
One page, print-ready, free to share. No signup needed.