Set operations
Concept  ·  SQL & Querying

EXCEPT is a set anti-join, INTERSECT a set semi-join — equivalent to NOT EXISTS / EXISTS on full rows.

Set operators answer membership questions ('which values appear in A but not B?') and membership has no counts — the dedup isn't a bug, it's the semantics. The trap is applying a membership tool to a quantity question: daily tallies, inventory movements, event streams — anywhere two identical rows are two facts. ALL variants subtract occurrences: EXCEPT ALL removes one match per match.

See it break
morning
sp
a
a
b
afternoon
sp
a
The trap
SELECT
  sp
FROM
  morning
EXCEPT SELECT
  sp
FROM
  afternoon
ORDER BY
  sp;
The fix
SELECT
  sp
FROM
  morning
EXCEPT ALL
SELECT
  sp
FROM
  afternoon
ORDER BY
  sp;
The trap
b
The fix
a
b

The morning-minus-departures tally lost a resident — plain EXCEPT dedups as a side effect, so one of the two monarchs the morning legitimately had twice vanished; EXCEPT ALL subtracts one occurrence per match and keeps the other.

The rule

ALL variants for quantity semantics; or aggregate to counts first and compare counts (the explicit spelling).

EXCEPT/INTERSECT ALL are standard and in DuckDB/Postgres; some engines lack them — the GROUP-BY-counts comparison is the portable fallback.

Shows up elsewhere
UNION dedups; UNION ALL doesn'tSet ops treat NULLs as equal
Try one →Prove it in practice →