For an INNER JOIN, does it matter whether a filter goes in the ON clause or in WHERE?
answer
- placement matters for only one join family
- inner joins never keep unmatched rows anyway
- two ANDed predicates over the same pairs
- outer joins insert a step between ON and WHERE
basics
~20 sFor an INNER JOIN, ON and WHERE are logically equivalent: a row must satisfy both predicates to survive either way. For outer joins they are not equivalent, because ON is evaluated before NULL-extension and WHERE after it.
solid answer
~50 sAn inner join keeps exactly the row pairs for which the join condition is TRUE, and `WHERE` then keeps the rows for which its predicate is TRUE. Both are conjunctive tests applied to the same candidate pairs, so `INNER JOIN orders o ON o.customer_id = c.id AND o.status = 'PAID'` and `INNER JOIN orders o ON o.customer_id = c.id` followed by `WHERE o.status = 'PAID'` return the same rows. That equivalence is specific to inner joins. An outer join inserts a step between the two: once `ON` has been applied, each preserved-side row with no surviving partner is added back with the other side's columns set to NULL, and only then does `WHERE` run. So in a `LEFT JOIN`, `ON` filters matches while `WHERE` filters the finished result. The usual convention is join conditions in `ON`, row filters in `WHERE` — readable, and harmless as long as you know that with outer joins placement is semantics, not style.
code
sql · 11 lines-- Both forms return exactly the same rows for an INNER JOIN
SELECT c.name, o.id
FROM customers c
INNER JOIN orders o
ON o.customer_id = c.id
AND o.status = 'PAID';
SELECT c.name, o.id
FROM customers c
INNER JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'PAID';go deeper
Be ready to state the rule cleanly: for an INNER JOIN both placements return the same rows; for a LEFT JOIN they do not. This is a quick screen, usually followed straight away by the harder outer-join version.
Explain the mechanism rather than the rule. An inner join applies both predicates to the same candidate pairs, while an outer join inserts NULL-extension between the ON test and the WHERE test, so the two predicates no longer see the same rows.
Demonstrate the review habit: scan every WHERE predicate for a reference to an outer-joined alias, because that is exactly where a LEFT JOIN quietly becomes an INNER JOIN in a report someone trusts.
Own the house convention — join conditions in ON, row filters in WHERE, outer-join filters placed deliberately — and back it with a review checklist or test that asserts preserved-side row counts, so an accidental collapse cannot ship unnoticed.
## Two clauses that look interchangeable `ON` and `WHERE` both take a boolean expression and both cause rows to disappear, which is why so many people treat them as two spellings of the same idea. For inner joins that instinct happens to be right. For outer joins it is wrong, and the reason is worth understanding precisely rather than memorising as a rule. ## The logical model of a joined table SQL defines the result of `FROM a JOIN b ON p` in steps. Reading them literally answers every question in this area: 1. Form the Cartesian product of `a` and `b` — every row of `a` paired with every row of `b`. 2. Keep the pairs for which the join condition `p` evaluates to TRUE. A predicate that evaluates to FALSE **or** to UNKNOWN (the value a comparison takes when an operand is NULL) is not TRUE, so those pairs are dropped. 3. **Outer joins only.** For each row of the preserved side that produced no surviving pair in step 2, emit one extra row containing that row's columns and NULL in every column of the other side. This is called NULL-extension. For `LEFT JOIN` the preserved side is the left table, for `RIGHT JOIN` the right, for `FULL OUTER JOIN` both. 4. The rest of the query runs against whatever step 3 produced: `WHERE`, then `GROUP BY`, `HAVING`, `SELECT`, `ORDER BY`. An inner join simply stops after step 2 — there is no step 3, because no unmatched row is ever preserved. ## Why inner joins make the two placements equivalent With no step 3, the pairs that reach `WHERE` are exactly the pairs that satisfied `ON`. Writing `ON p AND q` keeps the pairs where `p AND q` is TRUE. Writing `ON p ... WHERE q` keeps the pairs where `p` is TRUE and then, of those, the ones where `q` is TRUE — the same set. Conjunction is commutative and associative, and nothing between the two steps modifies a row, so the multisets are identical. Column values are untouched too, so the projected output matches row for row. ```sql -- identical results SELECT c.name, o.id FROM customers c INNER JOIN orders o ON o.customer_id = c.id AND o.status = 'PAID'; SELECT c.name, o.id FROM customers c INNER JOIN orders o ON o.customer_id = c.id WHERE o.status = 'PAID'; ``` ## Where the equivalence ends Add step 3 and the two placements stop describing the same thing. A predicate in `ON` participates in deciding what counts as a match, so failing it produces a NULL-extended row rather than no row. A predicate in `WHERE` runs after the NULL-extended rows already exist, and it is applied to them like any other row — where it usually evaluates to UNKNOWN, because the columns it names are NULL. UNKNOWN is not TRUE, so those rows are discarded and the outer join collapses into an inner join. ## The convention, and when to break it Most teams write join conditions in `ON` and row filters in `WHERE`, because it reads as "how these tables relate" followed by "which rows I want". That is a good default for inner joins, where it costs nothing. For outer joins the placement is a semantic decision: a filter on the optional side belongs in `ON` when you want to keep unmatched preserved-side rows, and in `WHERE` when you deliberately want to keep only rows that matched. Deliberately is the key word — the collapse is a legitimate result, just rarely the one someone intended when they typed `LEFT JOIN`. ## Two details worth knowing **One WHERE for the whole FROM clause.** A query may chain several joins, but there is a single `WHERE`, evaluated once against the fully joined row. In `a LEFT JOIN b ON ... INNER JOIN c ON ...`, predicates in `WHERE` see the row after every join has been applied, not after the join they happen to be written next to. **This is not a performance question.** Since the two forms are logically equivalent for inner joins, both describe the same result and an engine is free to evaluate either one however it likes. Choose the placement for meaning and readability; leave the execution strategy to the optimizer. ## What interviewers listen for A weak answer is "they're the same" with no qualification, or "`ON` is for join keys, `WHERE` is for filters" recited as a syntax rule with no model behind it. A strong answer names the step that only outer joins have — NULL-extension between the `ON` test and the `WHERE` test — and can immediately produce the `LEFT JOIN` example where moving one predicate changes the answer.
- Give an example where moving a predicate from WHERE into ON changes the result.`FROM customers c LEFT JOIN orders o ON o.customer_id = c.id WHERE o.status = 'PAID'` returns only customers with a paid order. Move `o.status = 'PAID'` into the `ON` clause and every customer comes back, with NULL order columns where no paid order exists. The predicate now decides what counts as a match instead of filtering the finished result.
- If a query mixes a LEFT JOIN and an INNER JOIN, which rows does a WHERE predicate see?There is one `WHERE`, evaluated once against the fully joined row after every join in the `FROM` clause has been applied, including any NULL-extension. So a `WHERE` predicate written after several joins can still discard rows the earlier `LEFT JOIN` preserved — its position in the text does not scope it to one join.
- Is a non-equality predicate allowed in ON, and does that change the rule?Yes. `ON` accepts any boolean expression — ranges, inequalities, `BETWEEN`, `OR`. The rule is unaffected: for an inner join a non-equality condition in `ON` behaves exactly like the same condition in `WHERE`, and for an outer join it still decides matches rather than filtering the result.
saying these in an interview costs you the question
- Says ON and WHERE are always interchangeable, outer joins included
- Claims ON may only hold equality predicates on join keys
- Insists the difference is performance rather than results
- Thinks an ON predicate on an inner join preserves unmatched rows
- Believes WHERE is evaluated before the join is formed