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.
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.
JOIN usually combines related columns horizontally. Set operations work differently: they combine query results vertically, stacking or comparing whole 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.”
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:
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.
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.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.
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.
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.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.
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:
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.
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.
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.
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.
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.
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.
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.
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.INTERSECT compares too many columns and misses entities that should match.EXCEPT is written in the wrong direction.Pick the operator by the relationship you want
UNION, INTERSECT, and EXCEPT.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?
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.
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.