Readineer
PATTERN 9SQL Patterns / Combining Data

SQL set operations: combining query results with UNION, UNION ALL, INTERSECT, and EXCEPT

Learn how to combine complete query results with UNION and UNION ALL, find rows shared between datasets with INTERSECT, find rows present in one result but not another with EXCEPT, and avoid silently removing meaningful duplicates.

8 min read10 sections2 predictions
01Foundation

Combining results instead of joining tables

Sometimes we need to combine information from two tables that have a similar structure. Consider two customer lists: one contains customers who bought through the online store, the other customers who bought through a physical store. Ben appears in both because he has purchased through both channels.

online_customers
customer_idcustomer_namecountry
1AshaIndia
2BenUK
3CarlaUAE
store_customers
customer_idcustomer_namecountry
2BenUK
4DevIndia
5EshaSingapore

Suppose we want to answer questions such as which customers purchased through either channel, how many customer-channel records exist across both datasets, which customers purchased through both channels, or which purchased online but not in store. These problems do not require us to match columns from two tables side by side. Instead, we want to compare or combine complete query results. SQL provides four important set operations for this: UNION, UNION ALL, INTERSECT, and EXCEPT.

Key idea
A JOIN usually combines related columns horizontally. Set operations work differently: they combine query results vertically, stacking or comparing whole rows.
02Stacking rows

UNION ALL: combine results and keep every row

Suppose the requirement is “combine the online and store customer records and keep every record from both sources.”

Query
SELECT customer_id, customer_name, country
FROM online_customers

UNION ALL

SELECT customer_id, customer_name, country
FROM store_customers;
Result
customer_idcustomer_namecountry
1AshaIndia
2BenUK
3CarlaUAE
2BenUK
4DevIndia
5EshaSingapore

All six input rows remain. Ben appears twice because there are two source records. UNION ALL does not ask whether the rows are duplicates; it simply appends the second query result to the first: query 1 rows + query 2 rows = all rows.

UNION: combine results and remove duplicate rows

Now suppose the requirement changes to “return the unique customers who purchased through either channel.” We no longer want Ben twice. Use UNION:

Query
SELECT customer_id, customer_name, country
FROM online_customers

UNION

SELECT customer_id, customer_name, country
FROM store_customers;
Result
customer_idcustomer_namecountry
1AshaIndia
2BenUK
3CarlaUAE
4DevIndia
5EshaSingapore

The identical Ben row appears in both query results, so UNION returns it once. UNION ALL keeps duplicate rows; UNION removes duplicate rows. Neither is inherently better; the correct choice depends on whether repeated rows have meaning for the business question.

Key idea
Just like DISTINCT, UNION compares the complete row returned by each query. If the online query returns 2 | Ben | UK and the store query returns 2 | Ben | USA, these rows are not duplicates: even though the id and name match, UK <> USA, so both rows remain. UNION does not make one selected column unique; it removes rows only when the complete selected values are duplicates.
03The featured failure

UNION silently removes meaningful rows

Suppose we want to know: how many customer-channel records exist across online and store purchases? There are 3 online + 3 store = 6 customer-channel records. Someone combines the lists with UNION and counts the result.

PAUSE & PREDICTHow many rows does the combined result contain?
Query
SELECT customer_id, customer_name, country
FROM online_customers

UNION

SELECT customer_id, customer_name, country
FROM store_customers;
Your prediction

The fix: use UNION ALL when duplicates are meaningful

If every source row represents a meaningful record, use UNION ALL. Now all six rows remain. The important question before choosing between the two operators is: if the exact same row appears in both query results, should it appear once or twice? If once, consider UNION; if once for every occurrence, use UNION ALL.

Query
SELECT customer_id, customer_name, country
FROM online_customers

UNION ALL

SELECT customer_id, customer_name, country
FROM store_customers;
Result
customer_idcustomer_namecountry
1AshaIndia
2BenUK
3CarlaUAE
2BenUK
4DevIndia
5EshaSingapore
Key idea
Do not use UNION just to be safe. Duplicate removal changes the semantics of the query, and if two source systems legitimately contain the same values because they represent two different events, removing one may destroy information. A better rule: use UNION ALL when combining records unless the requirement specifically calls for duplicate elimination, and use UNION when uniqueness is actually part of the business question.
04Alignment

The queries must have compatible shapes

Set operations combine query results by column position. If each query returns customer_id then customer_name, the shapes are compatible. But unioning SELECT customer_id, customer_name with SELECT customer_name, customer_id does not work correctly as a meaningful combination: the positions now mean different things, and SQL is conceptually trying to combine customer_id with customer_name. Depending on the database and data types, this may produce an error or require type conversion, and even when the types happen to be compatible, the result can be logically wrong.

Before combining queries, verify: they return the same number of columns; corresponding columns represent the same kind of information; corresponding data types are compatible. Think positionally: query 1 column 1 corresponds to query 2 column 1, and so on.

Align the meaning, not just the data type. Unioning SELECT customer_id, customer_name with SELECT customer_id, country may combine two text columns the database happily accepts, but the result would place names and countries (Asha, Ben, Carla, India, Singapore) in the same output column. The query may be technically valid while being semantically meaningless. Compatible data types are not enough; the columns should represent the same concept.

Adding a source column

Sometimes we want to combine rows but still know where each row came from. We can add a constant value to each query:

Query
SELECT customer_id, customer_name, country, 'online' AS source
FROM online_customers

UNION ALL

SELECT customer_id, customer_name, country, 'store' AS source
FROM store_customers;
Result
customer_idcustomer_namecountrysource
1AshaIndiaonline
2BenUKonline
3CarlaUAEonline
2BenUKstore
4DevIndiastore
5EshaSingaporestore

Now Ben’s two rows are clearly different (Ben | online and Ben | store). This is useful when combining data from different systems, regions, sales channels, historical and current tables, or separate partitions and archives; it preserves the source as part of the result.

05Comparing results

INTERSECT and EXCEPT

INTERSECT finds rows present in both results. “Which customers appear in both the online and store customer lists?” is not a stacking problem; we want the overlap between two result sets.

Query
SELECT customer_id, customer_name, country
FROM online_customers

INTERSECT

SELECT customer_id, customer_name, country
FROM store_customers;
Result
customer_idcustomer_namecountry
2BenUK

Ben appears in both query results. INTERSECT also compares complete rows: if Ben is 2 | Ben | UK online but 2 | Ben | USA in store, those rows are not considered identical. If the actual requirement is “which customer IDs occur in both datasets?”, select only the identifier that defines the comparison (SELECT customer_id on each side). The columns you select define what SQL compares.

EXCEPT: rows present only in the first result

“Customers who purchased online but do not appear in the store customer list” uses EXCEPT: first result minus rows also found in the second.

Query
SELECT customer_id, customer_name, country
FROM online_customers

EXCEPT

SELECT customer_id, customer_name, country
FROM store_customers;
Result
customer_idcustomer_namecountry
1AshaIndia
3CarlaUAE

Ben is removed because he appears in both results. Dev and Esha are not returned because they exist only in the second query. EXCEPT is directional: online EXCEPT store asks “which customers are online-only?” (Asha, Carla), while store EXCEPT online asks “which are store-only?” (Dev, Esha). Those are different questions: A EXCEPT B is rows in A that are not in B.

INTERSECT and EXCEPT vs EXISTS, and UNION is not a JOIN

The previous article used EXISTS and NOT EXISTS for matching problems. Set operators work naturally when the problem is phrased as “compare these two query results.” EXISTS and NOT EXISTS often fit better when you are returning rows from one table based on whether related rows exist elsewhere. Both can express similar logic; choose the form that makes the intent clearest.

And a set operation is not a join. A JOIN combines related columns based on relationships; UNION/INTERSECT/EXCEPT combine or compare rows from compatible query results.

06Ordering the result

ORDER BY applies to the combined result

Suppose we want the unique customers from both sources sorted alphabetically. A single ORDER BY at the end applies to the final combined result, not to each branch.

Query
SELECT customer_id, customer_name, country
FROM online_customers

UNION

SELECT customer_id, customer_name, country
FROM store_customers

ORDER BY customer_name;

Do not assume that rows will remain in the order in which the individual queries happen to produce them. You might observe online rows first and store rows second after a UNION ALL, but that observed order is not guaranteed presentation order. If source grouping matters, add a source column and sort explicitly (ORDER BY source, customer_id). As with every SQL result, request the ordering you depend on.

07Recognition cues

When to reach for a set operation

Consider set operations when the requirement says combine rows from two similar datasets, merge current and archived records, combine customers from multiple channels, return unique rows across several sources, find values appearing in both datasets, find values appearing only in one dataset, compare two query results, or stack several similarly shaped queries into one result. Look for these common mistakes:

UNION removes rows that were meaningful occurrences.
UNION ALL keeps duplicates when the requirement wanted unique rows.
Corresponding columns appear in different positions, or have compatible types but represent different concepts.
INTERSECT compares too many columns and misses entities that should match.
EXCEPT is written in the wrong direction.
A set operation is used where a relational join is actually required.
Final ordering is assumed rather than specified.
08A practical mental model

Pick the operator by the relationship you want

you want → operator
you wantoperator
keep everything from bothA UNION ALL B
unique rows from eitherA UNION B
rows present in bothA INTERSECT B
rows in A but not BA EXCEPT B (directional)
Key idea
Then ask one more question: which selected columns define whether two rows are considered the same? That determines the semantics of UNION, INTERSECT, and EXCEPT.
09Check your understanding

One more prediction

Requirement: return every customer record from both channels and preserve Ben twice because his presence in each channel is meaningful. Which query is correct?

online_customers
customer_idcustomer
1Asha
2Ben
3Carla
store_customers
customer_idcustomer
2Ben
4Dev
5Esha
ONE MORE PREDICTIONWhich query is correct?
Query
-- Option A
SELECT customer_id, customer FROM online_customers
UNION
SELECT customer_id, customer FROM store_customers;

-- Option B
SELECT customer_id, customer FROM online_customers
UNION ALL
SELECT customer_id, customer FROM store_customers;

-- Option C
SELECT customer_id, customer FROM online_customers
INTERSECT
SELECT customer_id, customer FROM store_customers;

-- Option D
SELECT customer_id, customer FROM online_customers
EXCEPT
SELECT customer_id, customer FROM store_customers;
Your prediction
10Summary

Summary

Set operations combine or compare complete query results. Use UNION ALL when every row from both results should remain, UNION when duplicate result rows should be removed, INTERSECT when you need rows present in both results, and EXCEPT when you need rows from the first result that do not appear in the second.

Key idea
The most important distinction between UNION and UNION ALL is not syntax; it is business meaning. A UNION B can quietly remove records that look identical, and if those repeated rows represent meaningful occurrences, that changes the answer. So before choosing UNION, ask: if the same row occurs twice, should the result contain one row or two? Then make sure the query results align: same number of columns, compatible data types, same meaning by position. And remember the broader distinction: a JOIN combines related columns; UNION / INTERSECT / EXCEPT combine or compare rows from complete query results.

Next, we will learn how to place one query inside another and structure larger problems using subqueries and common table expressions.

Go deeper

Ask Colearn about this pattern

Not sure whether you want UNION or UNION ALL, or which columns define a duplicate? Ask about the concept, or paste a simplified version of your query.

Ask Colearn

3 of 3 free questions left

Instant answers, grounded in the same verified material the diagnostic grades against.

Why did my UNION drop a row I needed to count?

UNION removes duplicate result rows, so identical rows from the two queries collapse into one. If those repeats are meaningful records, the count changes.

Use UNION ALL when every source row is a meaningful occurrence; reserve UNION for when uniqueness is part of the requirement.

from the unit →
How do INTERSECT and EXCEPT work?

INTERSECT returns rows present in both results; EXCEPT returns rows in the first result that are not in the second, and it is directional (A EXCEPT B is not B EXCEPT A).

Both compare complete rows, so the columns you select define what counts as the same row.

from the unit →
Why is my UNION combining the wrong columns?

Set operations align columns by position, not by name, so query 1 column 1 pairs with query 2 column 1. If the order differs, you can combine unrelated concepts.

Check that both queries return the same number of columns, in the same order, with matching meaning and compatible types.

from the unit →
Does UNION treat two NULLs as the same row?

Yes. For deduplication in UNION, INTERSECT, and EXCEPT, two NULLs in the same position are treated as equal, unlike a normal = comparison where NULL = NULL is UNKNOWN.

So identical rows containing NULLs are collapsed by UNION and matched by INTERSECT.

from the unit →

That’s the free taste

Two ways to go deeper.

Don’t paste credentials, personal data, or confidential production records.

Related patterns

P07Pattern · 8 minSQL joinsCombine related columns horizontally, the opposite of stacking rows.P08Pattern · 7 minEXISTS & anti-joinsINTERSECT/EXCEPT vs EXISTS/NOT EXISTS for matching questions.P02Pattern · 8 minNULL & UNKNOWNWhy set operations treat two NULLs as equal, unlike a normal comparison.
Up next · Pattern 10
Subqueries and common table expressions

Next, we place one query inside another and structure larger problems with subqueries and common table expressions.

Continue to P10 →Browse all patterns