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 used less often 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. Both share the same column-count and 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 into 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 need a rewrite asJOIN/EXISTS
Both operators deduplicate by default. 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.