In a query mixing UNION, INTERSECT and EXCEPT, which operator is evaluated first?
answer
- one of the three binds tighter
- think of it like AND versus OR
- two of them tie and go left to right
- engines have not always agreed here
- the safe answer costs one character pair
basics
~20 sINTERSECT 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.
solid answer
~50 sThe standard gives `INTERSECT` higher precedence than `UNION` and `EXCEPT`; those two rank equally and are evaluated left to right. So `A UNION B INTERSECT C` means `A UNION (B INTERSECT C)`, while `A EXCEPT B UNION C` means `(A EXCEPT B) UNION C`. That first one is the trap: people read it top to bottom and expect the union to happen first, and the two groupings can differ wildly in row count. The stakes are higher than a normal precedence question because engines have not been uniform here historically — an implementation that evaluated all set operators strictly left to right silently gives a different answer for the same text. My rule in review is simple: any statement combining more than one kind of set operator gets explicit parentheses, formatted so the grouping is visible. It costs nothing, documents intent, and removes the portability question entirely.
code
sql · 11 lines-- Reads as a UNION (b INTERSECT c), not (a UNION b) INTERSECT c
SELECT id FROM a
UNION
SELECT id FROM b
INTERSECT
SELECT id FROM c;
-- Say it explicitly instead
(SELECT id FROM a)
UNION
(SELECT id FROM b INTERSECT SELECT id FROM c);go deeper
Know that a mixed set-operator query is not simply evaluated top to bottom, and that parentheses settle the grouping. That alone prevents the common misreading.
State the rule precisely: INTERSECT binds tighter than UNION and EXCEPT, which tie and associate left to right. Be able to work a small example both ways and show the differing row counts.
Demonstrate the judgment that the rule is not the answer. Explain that implementations have differed, so a correct-here query can be silently wrong elsewhere, and that you parenthesize mixed set operators unconditionally.
Make it a standard rather than a habit: decide that mixed set operators must be parenthesized or decomposed into CTEs, and treat an unparenthesized one as a review defect — it is a silent-wrong-answer risk that no test necessarily catches.
## The precedence rule SQL's set operators are not all equal. The standard ranks `INTERSECT` above `UNION` and `EXCEPT`; `UNION` and `EXCEPT` share a level and associate left to right. Reading a mixed query therefore means applying the same kind of rule you already apply to `AND` binding tighter than `OR`: ```sql SELECT id FROM a UNION SELECT id FROM b INTERSECT SELECT id FROM c; ``` This is `a UNION (b INTERSECT c)` — the intersection of b and c, unioned with a. It is **not** `(a UNION b) INTERSECT c`, which is what most readers expect from scanning downward. With `EXCEPT` and `UNION` at the same level, left-to-right associativity decides: ```sql SELECT id FROM a EXCEPT SELECT id FROM b UNION SELECT id FROM c; -- means (a EXCEPT b) UNION c ``` Here the leftmost operator applies first, so rows of c come back even if they were in b. ## Why the difference matters Take concrete sets: a = {1}, b = {1, 2}, c = {2}. - `a UNION (b INTERSECT c)` = {1} ∪ {2} = {1, 2}. - `(a UNION b) INTERSECT c` = {1, 2} ∩ {2} = {2}. Same text under two readings, different results, and neither is obviously wrong on inspection. The failure mode in production is a query that returns plausible-looking data and is simply answering the wrong question — the sort of bug that survives review because nobody's eye catches the missing parentheses. ## The portability wrinkle This is where the question stops being trivia. Set-operator precedence has not been implemented uniformly across engines and versions; implementations have existed that evaluate all set operators strictly left to right, giving `(a UNION b) INTERSECT c` for the first query above. A query that is correct on one engine can therefore be silently wrong on another, or after an upgrade, with no error and no warning. Because this is exactly the class of divergence you cannot see in a diff, the defensive rule is worth stating flatly: > Any statement that combines more than one kind of set operator gets explicit parentheses. With parentheses the grouping is not a matter of engine behaviour at all — every implementation honours them, and the reader no longer has to know the rule. ```sql (SELECT id FROM a) UNION (SELECT id FROM b INTERSECT SELECT id FROM c); ``` ## Formatting that makes grouping visible Parentheses only help if the reader can see them, and a three-branch set operation reads badly as a flat block. Two habits help. Indent nested operands so the structure is visually apparent, as above. Or lift each operand into a CTE and combine the named results, which turns the grouping into something you can read aloud: ```sql WITH shared AS ( SELECT id FROM b INTERSECT SELECT id FROM c ) SELECT id FROM a UNION SELECT id FROM shared; ``` The second form has the extra benefit that each piece can be run and checked on its own, which is how you debug a mixed set-operator query that is returning the wrong count. ## Related rules to keep separate Precedence among set operators is a distinct question from two others that come up alongside it: - **`ALL` versus the default.** `UNION ALL` and `UNION` have identical precedence; the `ALL` keyword changes duplicate handling, not grouping. Mixing `UNION ALL` and `UNION` in one statement is legal and often accidental — worth flagging, because the deduplicating operator's placement changes which duplicates survive downstream. - **Where `ORDER BY` attaches.** It applies to the entire query expression regardless of the operators inside it, so it always goes last, never inside a grouping. ## What an interviewer is checking Partly whether you know the rule — `INTERSECT` first, then `UNION`/`EXCEPT` left to right. Mostly whether you recognise that *knowing the rule is not the answer*. The senior answer is that you parenthesize regardless, because the cost is zero, the intent becomes explicit for the next reader, and you stop depending on a behaviour that has not been uniform across implementations. A candidate who recites the precedence table and then writes the unparenthesized query has missed the point of the question.
- With a = {1}, b = {1,2} and c = {2}, what do the two possible groupings of a UNION b INTERSECT c return?Under the standard's precedence it is `a UNION (b INTERSECT c)` = {1} ∪ {2} = {1, 2}. The other reading, `(a UNION b) INTERSECT c`, gives {1, 2} ∩ {2} = {2}. Same text, different answer, and both results look plausible — which is why the grouping should be written explicitly rather than inferred.
- Does UNION ALL have different precedence from UNION?No. The `ALL` keyword controls duplicate elimination, not grouping, so `UNION ALL` sits at exactly the same precedence level as `UNION` and `EXCEPT`. What is worth noticing when both appear in one statement is placement: a plain `UNION` anywhere in the chain deduplicates the result at that point, which can quietly remove rows a later `UNION ALL` was meant to preserve.
- How would you make a three-way set-operator query readable and debuggable?Lift each operand into a CTE and combine the named results, so the grouping reads as prose and every piece can be run and counted on its own. Failing that, parenthesize and indent so the nesting is visible at a glance. Both approaches remove the reliance on precedence rules, which is the real goal.
saying these in an interview costs you the question
- Assumes set operators always evaluate strictly top to bottom
- Says all set operators share one precedence level
- Recites the precedence rule and still writes it unparenthesized
- Thinks UNION ALL binds differently from UNION
- Believes parentheses around branches change duplicate handling