Joins I — semantics
Concept  ·  SQL & Querying

A WHERE filter on the right table discards the NULL-extended rows — your LEFT JOIN silently becomes INNER; right-side filters belong in ON.

A LEFT JOIN keeps unmatched left rows with NULL-extended right columns. A WHERE that tests a right-side column is UNKNOWN on those NULL rows, so WHERE drops them and the outer join collapses to inner.

See it break
customers
idname
1Ada
2Bo
orders
customer_idorder_date
12024-02-01
12023-12-15
The trap
SELECT
  c.name,
  COUNT(o.customer_id) AS n
FROM
  customers c
  LEFT JOIN orders o ON o.customer_id = c.id
WHERE
  o.order_date >= DATE '2024-01-01'
GROUP BY
  c.name
ORDER BY
  c.name;
The fix
SELECT
  c.name,
  COUNT(o.customer_id) AS n
FROM
  customers c
  LEFT JOIN orders o ON o.customer_id = c.id
  AND o.order_date >= DATE '2024-01-01'
GROUP BY
  c.name
ORDER BY
  c.name;
The trap
Ada1
The fix
Ada1
Bo0

The 'all customers incl. zero orders' report quietly drops every zero-order customer — the rows it exists to show.

The rule

Move the predicate into the ON clause, or test for the null with WHERE col = value OR col IS NULL.

LEFT JOIN t ON t.fk = base.id AND <right-side predicate>
Shows up elsewhere
Fan-out before aggregate
Try one →Prove it in practice →