DevLearningTools

LEARN · SQL

UNION, INTERSECT, and EXCEPT

Set operations combine the results of two separate SELECT queries by stacking rows, instead of adding columns like a join does. Learn UNION, UNION ALL, INTERSECT, and EXCEPT.

Every join covered so far combines two tables side by side, adding columns, matched row by row. A set operation does something different: it stacks the results of two separate SELECT queries on top of each other, combining rows, not columns, the same way you'd merge two lists into one.

Learning Objectives

  • Combine two queries' results with UNION and UNION ALL, and explain the difference.
  • Find rows common to two queries with INTERSECT, and rows unique to one with EXCEPT.
  • Know what has to match between two queries for a set operation to work at all.

The Rule: Both Queries Need Matching Columns

Before any of these operators make sense, there's one rule to understand: both queries being combined must return the same number of columns, in a compatible order and type. SELECT first_name, salary FROM employees combined with SELECT name FROM departments would fail, two columns on one side, one on the other. Think of it like stacking two spreadsheets, the column headers have to line up.

Query A

Returns some rows

Query B

Returns some rows, same column shape

Stacked Together

UNION / INTERSECT / EXCEPT

One Combined Result

Rows, not columns, combined

UNION vs UNION ALL

Picture two separate lists of names, one for Engineering, one for high earners. UNION merges them into a single list with no repeats, like running a duplicate-removal pass after combining. UNION ALL just pastes both lists together, anyone who appears on both source lists appears twice in the result. UNION ALL is also meaningfully faster, since the database skips the extra work of checking for and removing duplicates.

OperatorBehavior
UNIONCombines both queries' rows, and removes exact duplicates
UNION ALLCombines both queries' rows, keeps every row including duplicates
MySQL / PostgreSQL / SQLite / SQL Server
SELECT first_name FROM employees WHERE department = 'Engineering'
UNION
SELECT first_name FROM employees WHERE salary > 90000;

-- If Grace is in Engineering AND earns over 90000,
-- UNION lists her once. UNION ALL would list her twice.

INTERSECT: Only Rows in Both

INTERSECT keeps only the rows that show up in both queries' results, the overlap between the two lists. This is a genuinely useful operator that's easy to miss, since the same result can always be rewritten as a join or a WHERE ... IN (...) subquery instead, INTERSECT is just the more direct way to say "only what's common to both."

PostgreSQL / SQLite / SQL Server
SELECT first_name FROM employees WHERE department = 'Engineering'
INTERSECT
SELECT first_name FROM employees WHERE salary > 90000;

-- Only employees who are BOTH in Engineering AND earn over 90000
MySQL (8.0.31+, 2022)
SELECT first_name FROM employees WHERE department = 'Engineering'
INTERSECT
SELECT first_name FROM employees WHERE salary > 90000;
NOTE

MySQL is the one database here that's a recent addition to this list: INTERSECT and EXCEPT weren't supported at all in MySQL until version 8.0.31, released in 2022. Any MySQL server older than that has neither operator, and the equivalent has to be written as a join or a subquery instead.

EXCEPT: Only Rows in the First, Not the Second

EXCEPT keeps rows from the first query that don't also appear in the second, subtracting one list from another. Order matters here, unlike UNION and INTERSECT: query A EXCEPT query B is not the same as query B EXCEPT query A.

PostgreSQL / SQLite / SQL Server / MySQL (8.0.31+)
SELECT first_name FROM employees WHERE department = 'Engineering'
EXCEPT
SELECT first_name FROM employees WHERE salary > 90000;

-- Engineering employees who do NOT earn over 90000

Frequently Asked Questions

Does column order matter, or just the column count and types?

Order matters, the first column of query A is matched against the first column of query B, regardless of what either column is named. The combined result's column names actually come from the first query, not the second.

Can I sort the combined result of a UNION?

Yes, but only one ORDER BY is allowed, at the very end, applying to the whole combined result, not to each individual query separately.

Is there an equivalent to EXCEPT in every database, under a different name?

Within the four databases this course covers, EXCEPT is the name used by PostgreSQL, SQL Server, SQLite, and MySQL (8.0.31+). Outside this course's scope, Oracle uses the name MINUS for the exact same operation, worth knowing if you ever work with an Oracle database.

Common Beginner Mistakes

Using UNION when UNION ALL was actually intended, and not realizing rows silently disappeared

If duplicate rows are expected and meaningful (for example, the same name genuinely should appear twice), UNION's automatic deduplication quietly loses that. UNION ALL is the right default unless you specifically want duplicates removed.

Trying INTERSECT or EXCEPT on an older MySQL server

Both are rejected with a syntax error on any MySQL version before 8.0.31. On an older server, the same result has to be written with a JOIN or a WHERE ... IN (...) / WHERE NOT IN (...) subquery instead.

Assuming the two queries' column names need to match

They don't, only the count, order, and compatible types matter. The final result's column names come from whichever query was written first.

Best Practices

  • Default to UNION ALL unless you specifically need duplicates removed, it's faster and avoids silently losing legitimate repeated rows.
  • Use INTERSECT or EXCEPT when they directly express the intent, rather than reaching for a more roundabout join or subquery, on databases where they're supported.
  • Check your MySQL version before relying on INTERSECT or EXCEPT, both require 8.0.31 or newer.
  • Put ORDER BY only once, at the very end of the combined query, not after each individual SELECT.

Interview Questions

What's the practical difference between UNION and UNION ALL, and when would performance matter?

UNION removes duplicate rows from the combined result, which requires the database to compare and de-duplicate, extra work that gets slower as the result set grows. UNION ALL skips that step entirely. On a large combined result where duplicates are either impossible or acceptable, UNION ALL is meaningfully faster.

Write a query that finds employees who are in the engineering_candidates table but haven't actually been hired (aren't in employees).

SELECT name FROM engineering_candidates EXCEPT SELECT first_name FROM employees; — EXCEPT keeps rows from the first query (candidates) that don't appear in the second (current employees).

On a MySQL server, how would you get the effect of INTERSECT if the server is older than 8.0.31?

Rewrite it as an INNER JOIN on the matching columns, or as a WHERE ... IN (...) subquery, for example SELECT first_name FROM employees WHERE department = 'Engineering' AND first_name IN (SELECT first_name FROM employees WHERE salary > 90000). Both achieve the same "only rows in both" result without needing the INTERSECT keyword.

Summary

This lesson covered combining two queries' results by stacking rows rather than joining columns: UNION (de-duplicated) and UNION ALL (everything kept), INTERSECT (only rows common to both), and EXCEPT (rows in the first query but not the second), plus MySQL's comparatively recent addition of INTERSECT and EXCEPT support in 8.0.31.

What's Next?

The final lesson in this module covers views, saving a query under a name so it can be queried again like a regular table.

</>

Ready to Practice SQL?

Write and run real SQL queries online, right in your browser, no install or signup required. Pick a playground based on which database you want to practice on.

SQLite / PostgreSQL

Fast, no server, no ads.

Open DB Fiddle ↗

MySQL / SQL Server

Switch databases with one dropdown.

Open SQL Fiddle ↗

Both open a third-party SQL playground in a new tab. External site terms and privacy policy apply.