Skip to content

Intermediate: combining and changing data · Subqueries, CTEs and set operations · topic 31 of 62 · 30 min

UNION, INTERSECT and EXCEPT

Explanation

Set operations combine the results of two SELECTs with the same number of columns. UNION gives the rows in either (removing duplicates), UNION ALL keeps every row, INTERSECT gives rows in both, and EXCEPT gives rows in the first but not the second (MINUS in Oracle). ORDER BY goes once, at the very end.

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

sql
SELECT city FROM customers
UNION
SELECT city FROM departments
ORDER BY city;
-- Bengaluru, Chennai, Delhi, Hyderabad, Mumbai, Pune
Every city where the business has a customer or an office, each once. (SHOP and COMPANY loaded together.)

UNION 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.

sql
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 | employee
A constant column labels where each row came from: a common trick for a combined contact list.

INTERSECT: rows in both

sql
SELECT city FROM customers
INTERSECT
SELECT city FROM departments
ORDER BY city;
-- Chennai, Delhi, Mumbai, Pune
Cities that have both a customer and an office.

EXCEPT: rows in the first but not the second

sql
SELECT id FROM products
EXCEPT
SELECT product_id FROM order_items;
-- 8   (the Power Bank, never ordered)
Order matters: A EXCEPT B is not B EXCEPT A. Oracle calls it MINUS (Oracle 21c also accepts EXCEPT).

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.

DatabaseUNION / UNION ALLINTERSECT, EXCEPT
PostgreSQLYesYes, plus INTERSECT ALL and EXCEPT ALL
SQLiteYesYes (no ALL versions)
SQL ServerYesYes
MySQLYesFrom 8.0.31
OracleYesINTERSECT 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

UNION, INTERSECT and EXCEPT

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.

sql
SELECT name FROM customers WHERE email IS NOT NULL
EXCEPT
SELECT name FROM customers WHERE city = 'Delhi';
Customers with an email, minus those in Delhi: Asha Verma, Imran Khan and Sneha Patil.

Practice (0/4 done)

Task 1 · Code · SQL

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.

Task 2 · Code · SQL

Which cities have both a customer and a department? Use INTERSECT. One column, city, sorted A to Z.

Task 3 · Code · SQL

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.

Task 4 · Code · SQL

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

Question 1

1.What is the difference between UNION and UNION ALL?

Question 2

2.Customers live in 5 cities, departments in 5 cities, and 4 cities appear in both. How many rows does INTERSECT return?

Question 3

3.Where does ORDER BY go in a UNION query?

Question 4

4.What does Oracle call EXCEPT (traditionally)?

Question 5

5.SELECT name, city FROM customers UNION SELECT city, name FROM departments. What happens?

Read more (free)

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.

From IN, EXISTS and NOT EXISTS

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.

From Joins: combining tables

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.

From LEFT, RIGHT and FULL OUTER JOIN

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

  1. This is the last topic of “Subqueries, CTEs and set operations”. The module exam comes next.
  2. Module 6 exam: subqueries, CTEs and set operations · 14 questions · 25 min, without help. 6 topics of this module still to finish first.
  3. Then the next module, Window functions. 62 topics left in the course before the final exam.

← Previous: Recursive CTEs