Skip to the solution
MediumDSA12 min read

Does a filter in the ON clause behave differently from WHERE in a LEFT JOIN?

717 views

The problem

Question presented to candidate: "I want every customer with their paid orders, including customers who have none. I wrote LEFT JOIN orders ... WHERE o.status = 'paid' and some customers vanished. Why? Does it matter whether a condition goes in the ON clause or the WHERE clause?"

What a strong answer should cover:

  • For an INNER JOIN it does not matter: ON and WHERE give the same rows
  • For a LEFT JOIN it matters: ON decides which right rows match; unmatched left rows are still kept and padded with NULLs. WHERE runs afterwards and removes rows, including those NULL-padded ones
  • So a right-table filter in WHERE turns the LEFT JOIN into an inner join (PostgreSQL even plans it as a plain Hash Join)
  • A left-table filter in ON does not remove left rows; it only stops them from matching. To filter the left table, use WHERE
  • WHERE o.status = 'paid' OR o.status IS NULL is not a fix: it keeps customers with no orders but still drops customers whose orders are all unpaid
  • The deliberate use of WHERE on the right table: WHERE o.id IS NULL for an anti-join

Code / implementation expected: Yes β€” the same LEFT JOIN with the filter in ON and in WHERE, and the different results.

Schema used by the examples:

  • joins
  • left-join

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

12 min read Β· 11 sections

Run the code

Edit the query and run it against the tables for this question.

SQLFilter in ON: every customer kept, only paid orders attached
SQLSame filter in WHERE: customers without a paid order disappear
SQLTrap: a condition on the LEFT table in ON does not filter customers
PostgreSQL: with the filter in WHERE, the plan is a plain inner Hash Join
EXPLAIN (COSTS OFF)
SELECT c.name, o.id AS order_id, o.total
FROM customers AS c
LEFT JOIN orders AS o ON o.customer_id = c.id
WHERE o.status = 'paid';

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 43 of 82 in SQL

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All SQL questions
Up nextMedium

What is an UPSERT?

Continue