← All cheatsheets
Data Analyst · #088 · October 31, 2026 · 3 min read

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.

Download the PDF

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.status is 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_id NULL.
  • 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 writeWhat happensDo instead
WHERE o.status = 'paid'customers with no orders have o.status NULL; the WHERE drops themmove it to ON
WHERE o.status <> 'paid'same: NULL <> 'paid' is not true either; the customer with no orders vanishes from both reportsmove 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 ordersleft-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?
After a LEFT JOIN, a customer with no orders gets NULL in every column of the orders table. A WHERE such as o.status = 'paid' compares NULL to 'paid', which is neither true nor false, and WHERE keeps only rows where the condition is true. The customer is dropped, which is exactly what an INNER JOIN would have done.
Where should the condition on the right table go?
In the ON clause: LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'paid'. The condition then filters which orders are joined, and customers with no matching order are kept with NULLs.
How do I find the customers with no paid order?
Put the status condition in ON, then add WHERE o.id IS NULL. That keeps only the customers for whom no paid order was joined. It is the anti-join pattern.
Does the same apply to a condition on the left table?
No. A condition on the left table, such as c.country = 'FR', belongs in WHERE. Put it in ON and it does not filter at all: every non-French customer still comes back, with NULL orders.

Get the free PDF

One page, print-ready, free to share. No signup needed.

Download the PDF

More cheatsheets