What do INTERSECT and EXCEPT (or MINUS) do?
Quick Answer
`INTERSECT` returns only rows that appear in *both* result sets (deduplicated). `EXCEPT` (called `MINUS` in Oracle) returns rows from the first query that do *not* appear in the second. Both require the same column count/types as `UNION`, and both deduplicate by default. They're less commonly supported and used than joins/`EXISTS`, but are the most direct way to express set comparisons.
Detailed Answer
INTERSECT and EXCEPT are the other two set operators alongside UNION, each with the same column-count/type requirements.
-- INTERSECT: rows present in BOTH result sets
SELECT customer_id FROM orders_2023
INTERSECT
SELECT customer_id FROM orders_2024;
-- customers who ordered in both years
-- EXCEPT (Oracle: MINUS): rows in the first set but NOT the second
SELECT customer_id FROM orders_2023
EXCEPT
SELECT customer_id FROM orders_2024;
-- customers who ordered in 2023 but churned before 2024
Equivalent using joins/EXISTS
These set operators are often more readable than the join-based equivalent, but the optimizer typically rewrites them to a semi-join or anti-join internally anyway:
-- Equivalent to the INTERSECT above
SELECT DISTINCT o23.customer_id
FROM orders_2023 o23
WHERE EXISTS (SELECT 1 FROM orders_2024 o24 WHERE o24.customer_id = o23.customer_id);
-- Equivalent to the EXCEPT above
SELECT DISTINCT o23.customer_id
FROM orders_2023 o23
WHERE NOT EXISTS (SELECT 1 FROM orders_2024 o24 WHERE o24.customer_id = o23.customer_id);
Support across engines
- PostgreSQL, SQL Server, SQLite:
INTERSECTandEXCEPT - Oracle:
INTERSECTandMINUS(notEXCEPT) - MySQL: added
INTERSECTandEXCEPTin 8.0.31+; earlier versions require rewriting asJOIN/EXISTS
Both operators deduplicate by default, and (like UNION) most engines also support an ALL variant (INTERSECT ALL, EXCEPT ALL) that preserves duplicate counts using multiset semantics, though these are used far less often.