Set Operations
Combine, keep, or subtract whole result sets
UNION, INTERSECT and EXCEPT combine whole result sets. UNION ALL preserves duplicates and rows; the others thin them — the classic reconciliation toolkit.
What you will learn
- Choose UNION vs UNION ALL by duplication intent
- Use EXCEPT to diff two tables
- Use INTERSECT to find common rows
What is this?
Set operations combine entire SELECT results vertically. UNION merges rows and removes duplicates; UNION ALL keeps everything. INTERSECT returns rows present in both; EXCEPT returns rows in the first but not the second. They are the fastest way to reconcile 'source says X, target says Y'.
Why do you need it?
- Reconciliation checks (diff source vs warehouse) are pure EXCEPT/diff queries.
- UNION ALL append is how staging and daily loads physically combine partitioned data.
- Understanding duplicate semantics prevents double-counting when merging sets.
Mental model
Two book shelves being merged
Set operations
UNION shelves the books once (no duplicates), UNION ALL copies every book even repeats, INTERSECT keeps only titles on both shelves, EXCEPT keeps titles only on the first.
How it works
- UNION removes duplicate rows across the combined result; UNION ALL preserves every row.
- Column counts and order must match in both SELECTs; typing is reconciled, not inferred.
- EXCEPT compares whole rows — a single differing column disqualifies a match.
- INTERSECT/EXCEPT can be phrased with NOT EXISTS but set ops are clearer and often optimized better.
- ORDER BY applies once to the combined result.
Set operation row flows.
Examples
Reconcile source vs warehouse
-- rows in the source but missing from the warehouseSELECT 'source_only' AS side, * FROM ( SELECT id, amount FROM source_orders EXCEPT SELECT id, amount FROM warehouse_orders) d;-- rows in the warehouse but not the source:SELECT 'warehouse_only', * FROM ( SELECT id, amount FROM warehouse_orders EXCEPT SELECT id, amount FROM source_orders) d;The two EXCEPT directions name exactly where the drift lives — the fastest reconciliation primitive.
In production
- A nightly reconciliation job EXCEPTs the warehouse against the source and pages on non-empty diffs.
- A data merge uses UNION ALL by partition, then a dedup layer collapses overlaps.
- A uniqueness audit INTERSECTs two candidate-join results to reveal exactly which rows made both filters.
Best practices
- Default to UNION ALL in pipelines; add UNION only when dedup across sources is the actual spec.
- Reconcile on modeled keys + values, not SELECT * (a reshuffle is a diff only where it matters).
- Keep the two operands symmetric for EXCEPT diffs to be shallow and readable.
- Document NULL semantics: EXCEPT treats NULLs as equal in some engines, unequal in others.
Common mistakes
- UNION where UNION ALL belonged — accidentally thinning a daily append and undercounting.
- SELECT * in EXCEPT so a benign column (updated_at) makes everything 'differ'.
- Mismatched column lists causing 'each UNION query must have the same number of columns'.
Common issues
Reconciliation always reports driftComparing volatile columns (like updated_at) or NULL type mismatches
Compare business keys + material totals only, and COALESCE nullable columns consistently.
UNION returned too few rowsUNION deduped rows that were genuinely repeated business transactions
Use UNION ALL if every row must survive; dedup later with row_number on the natural key.
Debugging tips
- When a diff never empty, reduce the EXCEPT to a single column (id) to localize it.
- Run each side's COUNT(*) first; if counts match but diff doesn't, values differ, not rows.
- Check whether the planner treats NULLs consistently in your dialect before trusting EXCEPT as 'equal'.
Interview checkpoint
Quick quiz
Which operation returns rows present in the first result but absent from the second?
UNION and UNION ALL return identical row counts for identical inputs.