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 Compiler

Result:

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 Compiler

Result:

sourcerows
customers24

(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 Compiler

Result:

personcitytype
Aarav SharmaBengaluruCustomer
Divya MenonBengaluruCustomer
Harish GowdaBengaluruEmployee
Neha JoshiBengaluruEmployee
Rajesh KhannaBengaluruEmployee
Suresh RaoBengaluruEmployee

(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 Compiler

Result:

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 Compiler

Result:

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 Compiler

Result:

metricvalue
Delivered orders16
Units sold59
Active products14

(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 Compiler

Error: each UNION query must have the same number of columns

  • Using UNION when you meant UNION ALL. UNION silently removes legitimate duplicate rows (two identical sales lines, for example) and is slower.
  • ORDER BY inside each query. Put one ORDER BY at the end.
  • Columns in a different order. SQL matches by position, not name. SELECT name, city UNION SELECT city, name mixes 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 Compiler

Result:

customer_id
1
2
4
6

(4 rows)

Key takeaways

  • UNION combines rows and removes duplicates; UNION ALL keeps them and is faster.
  • INTERSECT keeps rows in both results; EXCEPT keeps rows only in the first.
  • Queries must have the same number of compatible columns, matched by position.
  • One ORDER BY at 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.

Newsletter

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)