UNION, INTERSECT & EXCEPT
Stack result sets with UNION and UNION ALL, and find overlaps and differences in MySQL 8.0.31+.
Joins put tables side by side, adding columns. Set operations stack result sets on top of each other, adding rows. Use them to combine similar data from different tables or queries, or to find what two lists have in common.
Sample data#
UNION: combine and remove duplicates#
Alan and Linus were customers in both years, but they appear only once: UNION removes duplicate rows.
UNION ALL: keep everything#
UNION ALL skips the de-duplication step, so it's faster. Use it whenever duplicates are impossible or wanted. Adding a constant column like yr is a neat way to label where each row came from.
Duplicates are judged on whole rows. Linus moved city, so these two rows differ and both survive a plain UNION:
The rules#
- Every
SELECTmust return the same number of columns. - Columns are matched by position, not by name. Corresponding columns should have compatible types.
- Column names come from the first
SELECT. ORDER BYandLIMITat the end apply to the whole result. To sort or limit one part, wrap it in parentheses:
A practical example: one activity feed#
Set operations shine when similar events live in different tables:
INTERSECT: rows in both (MySQL 8.0.31+)#
These are the returning customers.
EXCEPT: rows in the first but not the second (MySQL 8.0.31+)#
Ada bought in 2025 but not in 2026: a churned customer. Order matters with EXCEPT. Swapping the queries gives the new customers instead (Grace).
Like UNION, both INTERSECT and EXCEPT remove duplicates by default. Add ALL to keep them. When mixed without parentheses, INTERSECT is evaluated before UNION and EXCEPT.
On older versions: EXISTS instead#
Before 8.0.31 (and on some MariaDB versions), write the same logic with EXISTS and NOT EXISTS:
These work everywhere and can use indexes on email.
Using set operations as building blocks#
A set operation can feed a derived table, a CTE, an INSERT ... SELECT or a view, just like any other query. For example, count how many customers fall into each group across both years:
And copy a de-duplicated mailing list into its own table in one statement:
With UNION ALL here, the second copy of Alan would violate the primary key, which is another reason to choose the operator deliberately.
Set operations vs joins#
Common mistakes#
- Different column counts: ERROR 1222: The used SELECT statements have a different number of columns.
- Columns in a different order in each
SELECT. MySQL matches by position, so you'll silently mix up emails and cities. - Using
UNIONwhenUNION ALLwill do. You pay for an unnecessary sort or hash step on big results. - Expecting
ORDER BYinside one part to survive. WithoutLIMIT, MySQL may ignore an innerORDER BY. Sort the final result.
What's next#
Long queries with nested subqueries get hard to read. Next you'll name intermediate steps with CTEs and solve hierarchical problems with recursive CTEs.
Check your understanding
Quick quiz
1.What is the difference between UNION and UNION ALL?
2.Where do the column names of a UNION result come from?
3.Which MySQL version first supports INTERSECT and EXCEPT?
Finished reading?
Mark this lesson complete to track your progress.