skip to content

Set Operations: UNION, INTERSECT, EXCEPT

Combining whole result sets with UNION, UNION ALL, INTERSECT, and EXCEPT. Interviewers test the duplicate-elimination difference between UNION and UNION ALL and the column-compatibility rules.

part ofSQLoverview, primer and where to startread it →
on this pageshow

questions

6

What is the difference between UNION and UNION ALL in SQL?

level: juniorimportance: must knowfreq 88%

answer

  1. one keyword changes the row count
  2. think about duplicate rows
  3. one of them does extra comparison work
  4. the whole row is compared, not a key
  5. the cheap one keeps everything

basics

~20 s

UNION combines the rows of two queries and removes duplicate rows from the combined result. UNION ALL concatenates them and keeps every row, duplicates included. UNION ALL is the cheaper choice whenever duplicates are impossible or wanted.

solid answer

~50 s

Both operators stack the rows of two queries into one result set, matching columns by position. The difference is duplicate elimination: `UNION` applies it, `UNION ALL` does not. Two points people miss. First, duplicate removal is **whole-row**: two rows are duplicates only when every column matches, so adding one differing column defeats it. Second, `UNION` also collapses duplicates that already existed *inside* a single branch, not just rows that appear in both branches. Because deduplication forces the engine to compare all rows against each other, `UNION` does strictly more work than `UNION ALL`. So `UNION ALL` is the default I reach for, and I use plain `UNION` only when I actually need distinct rows and cannot guarantee the branches are disjoint. The standard also allows the explicit spelling `UNION DISTINCT`, which is what plain `UNION` means.

code

sql · 9 lines
sql
-- Duplicates removed across the whole combined result
SELECT city FROM customers
UNION
SELECT city FROM suppliers;

-- Every row kept, including repeats inside one branch
SELECT city FROM customers
UNION ALL
SELECT city FROM suppliers;

go deeper

for a junior

Be ready to state the difference in one sentence and predict a row count from two small tables. Know that duplicate removal compares the entire row, not one column.

for a middle

Explain that plain UNION means UNION DISTINCT, that it also collapses duplicates occurring within a single branch, and that deduplication is extra work the ALL form skips entirely.

for a senior

Show the judgment: reach for UNION ALL by default and justify any plain UNION by naming the duplicates it removes. Recognise UNION used as a bandage over join fan-out and fix the real cause.

for a principal

Own the convention. In a codebase where UNION is the reflex, duplicate-producing defects stay invisible until a column is added. Decide whether the house rule is UNION ALL plus a stated uniqueness argument, and hold reviews to it.

## The two operators A set operator takes two complete query results and produces one. `UNION ALL` is pure concatenation: every row from the left query, then every row from the right query, with nothing removed. `UNION` does the same concatenation and then removes duplicate rows from the combined result. In standard SQL the keyword after the operator is optional and spells out the choice — `UNION ALL` versus `UNION DISTINCT` — and bare `UNION` means `UNION DISTINCT`. The same `ALL`/`DISTINCT` choice exists in the standard for `INTERSECT` and `EXCEPT`, where `DISTINCT` is likewise the default. ```sql SELECT city FROM customers UNION ALL SELECT city FROM suppliers; ``` ## Duplicate elimination is whole-row This is the point that trips people up in practice. `UNION` does not deduplicate on some key column; it compares the entire output row. Two rows are duplicates only if every corresponding column holds the same value. So this returns one row per distinct city: ```sql SELECT city FROM customers UNION SELECT city FROM suppliers; ``` but adding a second column that differs between the branches makes almost nothing a duplicate any more: ```sql SELECT city, 'customer' AS source FROM customers UNION SELECT city, 'supplier' AS source FROM suppliers; ``` Here the literal `source` column differs on every pair, so `UNION` removes nothing across the branches — it can still remove duplicates within one branch. That is the second surprise: `UNION` deduplicates the *combined* result, so if `customers` contains the same city three times, plain `UNION` collapses those three to one even though the second query was never involved. If you wanted "all customer cities plus supplier cities not already listed", `UNION` gives you something subtly different from that. ## Cost Removing duplicates means the engine must compare rows against each other, which is real work proportional to the size of the combined result — and work that `UNION ALL` simply never does. In a query that produces a large intermediate result, the difference between the two operators can dominate the runtime, and it is one of the cheapest wins available: replacing `UNION` with `UNION ALL` where the branches are provably disjoint costs nothing and removes an entire processing step. ## Choosing between them The decision is a correctness question first and a cost question second: - If duplicates are **impossible by construction** — the branches read disjoint sets of rows, or carry a discriminating column like `source` above — use `UNION ALL`. It is equivalent and cheaper. - If duplicates are **meaningful** — you are stacking two event streams and each row is a real occurrence — use `UNION ALL`, because `UNION` would silently destroy data. - If you genuinely need a set of distinct rows and cannot argue the branches apart, use `UNION` and let the engine do it. A useful habit: write `UNION ALL` by default, and when you switch to `UNION`, be able to say out loud which duplicates you are removing. "I used `UNION` to be safe" is the answer that hides bugs, because the operator is not being safe — it is masking whatever duplicate-producing defect exists upstream, such as an unintended join fan-out. ## Things that are the same for both Both operators require the two queries to be compatible: the same number of columns, matched **by position** rather than by name, with types that can be resolved to a common type. Both produce a result whose column names come from the first branch, which is why an `ORDER BY` written after the set operation has to use those names or ordinal positions. Neither operator guarantees any particular row order — not even "left branch first" for `UNION ALL`. If order matters, add a single `ORDER BY` at the end of the whole statement. ## Portability `UNION` and `UNION ALL` are the most universally implemented parts of SQL's set-operator family; you can rely on both in any SQL engine you are likely to meet. The `DISTINCT` spelling and the `ALL` forms of the other set operators are less uniformly available, so when you need those, check your engine's documentation.

  • If the two branches are guaranteed disjoint, is there any reason left to prefer UNION over UNION ALL?
    No. When no row can appear in both branches, the two operators produce the same rows, and `UNION` only adds a deduplication step that can never remove anything. The one caveat is that `UNION` also removes duplicates *within* a single branch, so "disjoint" has to mean each branch is itself duplicate-free — otherwise the two are not equivalent.
  • A colleague adds UNION instead of UNION ALL because the report shows doubled totals. What would you say?
    That `UNION` is treating a symptom. Doubled rows usually come from a join that fans out, or from a branch that overlaps the other. `UNION` hides it only while the duplicated rows are identical in every column; add one differing column — a timestamp, an id — and the doubling returns. I would find the source of the extra rows, fix it there, and go back to `UNION ALL`.
  • Does UNION ALL guarantee that the left query's rows come out before the right query's?
    No. SQL result order is undefined without an `ORDER BY`, and that applies to set operations too. It commonly looks left-then-right, but nothing in the standard promises it and an engine is free to produce the rows in any order. If the ordering matters, add a sort key — often a literal column marking the branch — and one `ORDER BY` at the end of the whole statement.

saying these in an interview costs you the question

  • Says UNION is just UNION ALL plus better performance
  • Thinks UNION deduplicates on a key column rather than the whole row
  • Believes UNION only removes rows appearing in both branches
  • Uses UNION reflexively to hide join fan-out duplicates
  • Claims UNION ALL guarantees left-branch rows come first

context

open as a page

What does EXCEPT return, and how does it treat duplicate rows?

level: middleimportance: must knowfreq 55%

basics

~20 s

A EXCEPT B returns the distinct rows produced by A that do not appear anywhere in B. Duplicates are removed by default, and the operator is not commutative — swapping the two queries changes the result.

open as a page

What does UNION require of the two SELECT lists it combines?

level: juniorimportance: should knowfreq 60%

basics

~20 s

UNION matches the two SELECT lists by position, not by name. Both must produce the same number of columns, and each positional pair must have types the engine can resolve to one common type. Result column names come from the first branch.

open as a page

Does UNION treat two rows containing NULL as duplicates, given NULL = NULL is unknown?

level: middleimportance: should knowfreq 38%

basics

~20 s

Yes. Duplicate elimination in UNION, INTERSECT and EXCEPT compares rows with NULLs treated as equal, so two rows that are NULL in the same column collapse into one — unlike the = operator, which yields UNKNOWN for NULL = NULL.

open as a page

Where can ORDER BY appear in a UNION query, and what does it sort?

level: middleimportance: should knowfreq 50%

basics

~20 s

One ORDER BY may appear at the very end of the statement, and it sorts the whole combined result rather than an individual branch. It can reference only the result's column names, taken from the first branch, or ordinal positions.

open as a page

In a query mixing UNION, INTERSECT and EXCEPT, which operator is evaluated first?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

INTERSECT binds more tightly than UNION and EXCEPT, which have equal precedence and associate left to right. Because engines have not always agreed on this, parenthesize any query that mixes set operators rather than relying on the default.

open as a page