All 40 lessons
Lesson 25 of 40 · Subqueries & CTEs
SQL UNION, INTERSECT and EXCEPT
By Skillancy Editorial Team
Every example runs on the ShopKart dataset, already loaded in the free SQL Compiler. Click Try it in the SQL Compiler under a query to open it there, then press Run.
Set operators combine the results of two queries into one. UNION stacks them, INTERSECT keeps rows found in both, and EXCEPT keeps rows in the first but not the second. Where a join adds columns, set operators add or compare rows.
Syntax
SELECT columns FROM table_a
UNION | UNION ALL | INTERSECT | EXCEPT
SELECT columns FROM table_b;Rules:
1. Both queries must return the same number of columns.
2. Matching columns must have compatible types.
3. Column names come from the first query.
4. One ORDER BY goes at the very end and sorts the combined result.
Examples
1. UNION: one contact list from two tables
Every city where ShopKart has either customers or employees:
SELECT city FROM customers
UNION
SELECT city FROM employees
ORDER BY city;Try it in the SQL CompilerResult:
| city |
|---|
| Ahmedabad |
| Bengaluru |
| Chennai |
| Delhi |
| Hyderabad |
| Jaipur |
| Kochi |
| Kolkata |
| Lucknow |
| Mumbai |
| Pune |
(11 rows)
UNION removed duplicates: Bengaluru appears once even though it's in both tables.
2. UNION ALL keeps duplicates
SELECT 'customers' AS source, COUNT(*) AS rows FROM (
SELECT city FROM customers
UNION ALL
SELECT city FROM employees
) AS u;Try it in the SQL CompilerResult:
| source | rows |
|---|---|
| customers | 24 |
(1 row)
12 customers + 12 employees = 24 rows. With UNION, the same subquery gives only the distinct cities.
3. Tag each source
Add a constant column to show where each row came from:
SELECT customer_name AS person, city, 'Customer' AS type
FROM customers WHERE city = 'Bengaluru'
UNION ALL
SELECT employee_name, city, 'Employee'
FROM employees WHERE city = 'Bengaluru'
ORDER BY type, person;Try it in the SQL CompilerResult:
| person | city | type |
|---|---|---|
| Aarav Sharma | Bengaluru | Customer |
| Divya Menon | Bengaluru | Customer |
| Harish Gowda | Bengaluru | Employee |
| Neha Joshi | Bengaluru | Employee |
| Rajesh Khanna | Bengaluru | Employee |
| Suresh Rao | Bengaluru | Employee |
(6 rows)
4. INTERSECT: in both
Cities with both customers and employees:
SELECT city FROM customers
INTERSECT
SELECT city FROM employees
ORDER BY city;Try it in the SQL CompilerResult:
| city |
|---|
| Ahmedabad |
| Bengaluru |
| Chennai |
| Hyderabad |
| Kolkata |
| Mumbai |
(6 rows)
5. EXCEPT: in the first, not the second
Cities with customers but no ShopKart staff:
SELECT city FROM customers
EXCEPT
SELECT city FROM employees
ORDER BY city;Try it in the SQL CompilerResult:
| city |
|---|
| Delhi |
| Jaipur |
| Kochi |
| Lucknow |
| Pune |
(5 rows)
Order matters: employees EXCEPT customers would give cities with staff but no customers (none here).
6. Combining metrics in one report
Stack two different summaries into one table:
SELECT 'Delivered orders' AS metric, COUNT(*)::numeric AS value
FROM orders WHERE status = 'Delivered'
UNION ALL
SELECT 'Units sold', SUM(quantity)
FROM order_items
UNION ALL
SELECT 'Active products', COUNT(DISTINCT product_id)
FROM order_items;Try it in the SQL CompilerResult:
| metric | value |
|---|---|
| Delivered orders | 16 |
| Units sold | 59 |
| Active products | 14 |
(3 rows)
Common mistakes
Different number of columns:
SELECT customer_name, city FROM customers
UNION
SELECT employee_name FROM employees;Try it in the SQL CompilerError: each UNION query must have the same number of columns
- Using UNION when you meant UNION ALL.
UNIONsilently removes legitimate duplicate rows (two identical sales lines, for example) and is slower. - ORDER BY inside each query. Put one
ORDER BYat the end. - Columns in a different order. SQL matches by position, not name.
SELECT name, city UNION SELECT city, namemixes them up.
Practice question
Find customers who have paid with both UPI and Card at least once. Show their customer_id.
Show solution
SELECT customer_id FROM orders WHERE payment_method = 'UPI'
INTERSECT
SELECT customer_id FROM orders WHERE payment_method = 'Card'
ORDER BY customer_id;Try it in the SQL CompilerResult:
| customer_id |
|---|
| 1 |
| 2 |
| 4 |
| 6 |
(4 rows)
Key takeaways
UNIONcombines rows and removes duplicates;UNION ALLkeeps them and is faster.INTERSECTkeeps rows in both results;EXCEPTkeeps rows only in the first.- Queries must have the same number of compatible columns, matched by position.
- One
ORDER BYat the end sorts the combined result.
Frequently asked questions
What is the difference between UNION and UNION ALL?
UNION removes duplicate rows from the combined result; UNION ALL keeps them. UNION ALL is faster because it skips the de-duplication step, so use it unless you need duplicates removed.
What is the difference between UNION and JOIN?
UNION stacks results vertically, adding rows. JOIN combines tables horizontally, adding columns.
Is EXCEPT the same as MINUS?
Yes. Oracle calls it MINUS; PostgreSQL, SQL Server and the SQL standard call it EXCEPT. MySQL supports INTERSECT and EXCEPT from version 8.0.31.
Keep learning SQL
One email a week with SQL and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Prefer a one-page summary? Download the free SQL cheat sheet (PDF)