Intermediate: combining and changing data · Subqueries, CTEs and set operations · topic 31 of 62 · 30 min
UNION, INTERSECT and EXCEPT
Explanation
Joins go sideways, set operations go down
A join puts columns from two tables side by side. A set operation stacks two results on top of each other, or compares them row by row. You use it when the same kind of thing lives in two places: cities of customers and cities of offices, this year's and last year's customer lists, active and archived orders.
The rules
- Both SELECTs must return the same number of columns.
- Matching columns should have compatible types: text with text, numbers with numbers. PostgreSQL raises an error for text against integer; SQLite is lenient and allows it, which can hide mistakes.
- Columns are matched by position, not by name. The result takes its column names from the first SELECT.
- ORDER BY (and LIMIT) is written once, after the last SELECT, and sorts the whole combined result.
UNION and UNION ALL
SELECT city FROM customers
UNION
SELECT city FROM departments
ORDER BY city;
-- Bengaluru, Chennai, Delhi, Hyderabad, Mumbai, PuneUNION removes duplicate rows: Pune appears in both tables (and twice among customers) but once in the result. UNION ALL keeps every row, so the same query with UNION ALL returns 11 rows. Removing duplicates costs work (sorting or hashing every row), so when you know there are no duplicates, or you want them (for example to count), use UNION ALL. It is the faster default.
SELECT name, 'customer' AS kind FROM customers WHERE city = 'Pune'
UNION ALL
SELECT name, 'employee' FROM employees WHERE dept_id = 3
ORDER BY name;
-- Asha Verma | customer
-- Farhan Ali | employee
-- Imran Khan | customer
-- Neha Joshi | employeeINTERSECT: rows in both
SELECT city FROM customers
INTERSECT
SELECT city FROM departments
ORDER BY city;
-- Chennai, Delhi, Mumbai, PuneEXCEPT: rows in the first but not the second
SELECT id FROM products
EXCEPT
SELECT product_id FROM order_items;
-- 8 (the Power Bank, never ordered)Set operations compare whole rows, and unlike WHERE they treat two NULLs as equal, so a NULL row in both inputs counts as a match. That makes EXCEPT a handy, NULL-safe way to compare two tables: run A EXCEPT B and B EXCEPT A; if both are empty, the tables hold the same rows.
| Database | UNION / UNION ALL | INTERSECT, EXCEPT |
|---|---|---|
| PostgreSQL | Yes | Yes, plus INTERSECT ALL and EXCEPT ALL |
| SQLite | Yes | Yes (no ALL versions) |
| SQL Server | Yes | Yes |
| MySQL | Yes | From 8.0.31 |
| Oracle | Yes | INTERSECT and MINUS (EXCEPT also from 21c) |
Set operation or join or subquery?
Products never ordered can be found with EXCEPT, with NOT EXISTS (topic 2) or with LEFT JOIN ... IS NULL (module 5). EXCEPT is shortest when you only need the key columns; if you also need other columns, such as the name, NOT EXISTS is usually simpler, because with EXCEPT both sides must return the same columns.
Common mistakes
- Different column counts: "SELECTs to the left and right of UNION do not have the same number of result columns". Make both lists match.
- Columns in a different order on each side, such as name, city UNION city, name. The rows mix up silently, because matching is by position.
- Writing ORDER BY inside the first SELECT. Put one ORDER BY at the very end.
- Using UNION when duplicates are impossible or wanted. UNION ALL is faster and keeps every row.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
UNION, INTERSECT and EXCEPT · 1/6
Joins put columns side by side. Set operations stack whole results, or compare them row by row.
Prefer a video by a teacher? Find free videos on “sql union union all intersect except tutorial” on YouTube ↗
In real life: One contact list from several systems
A company may keep customers in one system and suppliers in another. To send a festival greeting, the marketing team builds one list with UNION: name and email from customers, UNION name and email from suppliers, removing anyone who appears in both. EXCEPT then takes out everyone on the unsubscribed list, so nobody gets a message they refused.
SELECT name FROM customers WHERE email IS NOT NULL
EXCEPT
SELECT name FROM customers WHERE city = 'Delhi';Practice (0/4 done)
This database holds both SHOP and COMPANY. Return every city that has a customer or a department, each city once, sorted A to Z. One column: city.
Which cities have both a customer and a department? Use INTERSECT. One column, city, sorted A to Z.
Use EXCEPT to find the ids of products that have never been ordered (ids in products but not among the product_ids of order_items). One column: id.
Build one list of people from Pune customers and Finance employees (dept_id 3). Return name and kind, where kind is the text 'customer' or 'employee', keeping every row, sorted by name.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Common table expressions (WITH)Subqueries, CTEs and set operations · same module
- Correlated subqueries and derived tablesSubqueries, CTEs and set operations · same module
- Joins: combining tablesBackend Development · SQL in depth
- LEFT, RIGHT and FULL OUTER JOINJOINs
- GROUP BYAggregation: GROUP BY and HAVING
- UPDATE and DELETE safelyChanging data, transactions and ACID
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What is the difference between UNION and UNION ALL?
Answer: UNION removes duplicate rows; UNION ALL keeps every row and is faster
Removing duplicates costs extra work, so use UNION ALL when you can.
Customers live in 5 cities, departments in 5 cities, and 4 cities appear in both. How many rows does INTERSECT return?
Answer: 4
INTERSECT keeps rows present in both results.
Where does ORDER BY go in a UNION query?
Answer: Once, after the last SELECT; it sorts the combined result
It applies to the whole compound query.
What does Oracle call EXCEPT (traditionally)?
Answer: MINUS
Oracle uses MINUS; Oracle 21c also accepts EXCEPT.
SELECT name, city FROM customers UNION SELECT city, name FROM departments. What happens?
Answer: It runs, but names and cities are mixed in the same column, because matching is by position
Set operations line columns up by position; keep the same order on both sides.
The subquery SELECT dept_id FROM employees returns 1, 2, 3, 4 and one NULL. How many rows does WHERE id NOT IN (that subquery) return from departments (ids 1 to 5)?Related
Answer: 0
id <> NULL is unknown, so the NOT IN is never true for any row.
A query joining 1,000 customers and 10,000 orders returns 1,00,00,000 rows. What most likely went wrong?Related
Answer: The ON condition is missing or wrong
Without a correct ON, every customer is paired with every order: 1,000 x 10,000 rows.
In PostgreSQL, a subquery used with = returns 3 rows. What happens?Related
Answer: An error: more than one row returned by a subquery
PostgreSQL, MySQL and SQL Server raise an error; SQLite quietly uses the first row.
From Scalar subqueries
In the anti-join products LEFT JOIN order_items ... WHERE ? IS NULL, which column is the safest to test?Related
Answer: oi.product_id, the join column of the right table
The right table's join key is NULL only when there was no match. p.id is never NULL, and qty could be NULL in a real row.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Subqueries, CTEs and set operations”. The module exam comes next.
- Module 6 exam: subqueries, CTEs and set operations · 14 questions · 25 min, without help. 6 topics of this module still to finish first.
- Then the next module, Window functions. 62 topics left in the course before the final exam.