skip to content

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

level: middleimportance: should knowfreq 50%

answer

  1. there is only one of them allowed
  2. which result does it act on?
  3. names come from somewhere specific
  4. ordinal positions always work here
  5. a branch's own sort does not carry through

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.

solid answer

~50 s

A set operation produces a single new result, and `ORDER BY` attaches to that result — not to either input query. So there is one `ORDER BY`, written after the last branch, sorting everything the operator produced. Because the sort sees only the *output* columns, it may use the result's column names (which come from the first branch) or ordinal positions like `ORDER BY 2 DESC`; a name that exists only in the second branch is not visible, and arbitrary expressions over the inputs generally are not either. A bare `ORDER BY` inside a branch is not part of the standard's grammar for a set-operator query. Some engines let you parenthesize a branch and give it its own `ORDER BY` with a row limit, but even then the branch's ordering is not guaranteed to survive into the combined result — only the final `ORDER BY` determines what the client sees. The same reasoning applies to `LIMIT`/`FETCH FIRST`.

code

sql · 6 lines
sql
-- One sort, applied to the combined result;
-- the name comes from the first branch
SELECT name, hired_on AS start_date FROM employees
UNION ALL
SELECT name, started_on FROM contractors
ORDER BY start_date DESC;

go deeper

for a junior

Know that a single ORDER BY goes at the very end and sorts everything the set operation produced. Sorting by column position, such as ORDER BY 2, is normal here.

for a middle

Explain why the sort can only see the combined result's columns, where those names come from, and why a bare ORDER BY inside a branch is not part of the standard grammar.

for a senior

Handle the per-branch top-N case: parenthesized branches with their own sorts and limits, plus a mandatory trailing ORDER BY, and be explicit that the syntax is engine-dependent.

for a principal

Push on determinism as a contract: a paginated or limited set-operator query without a total order over a unique tiebreaker is a reproducibility bug waiting to surface. Decide what ordering guarantee your APIs promise.

## The result is one result The mental model that makes all of this obvious: `A UNION B` is not "query A, then query B". It is a single query expression whose result is derived from both. `ORDER BY` is not part of either branch — it is applied to the query expression as a whole. Hence there is exactly one place it can go, at the end: ```sql SELECT name, hired_on FROM employees UNION ALL SELECT name, started_on FROM contractors ORDER BY hired_on DESC; ``` The sort applies to all rows from both branches, interleaved. ## What the sort can reference Since the sort operates on the combined result, it sees that result's columns and nothing else. Two consequences follow. **Names come from the first branch.** In the example above the second column is called `hired_on` because the first query named it so; `started_on` is not a visible name in the combined result and sorting by it fails. If you want a neutral name, alias the first branch: ```sql SELECT name, hired_on AS start_date FROM employees UNION ALL SELECT name, started_on FROM contractors ORDER BY start_date DESC; ``` **Ordinal positions always work.** `ORDER BY 2 DESC` sorts by the second output column whatever it is named, which is why you see positional sorts far more often after set operations than in ordinary queries. It is a legitimate use, though an alias is more readable and survives a change to the select list. What you generally cannot do is sort by an arbitrary expression over the input tables — `ORDER BY UPPER(e.name)` referring to a branch's table alias has no meaning once the branches have been combined. Compute the expression as an output column in **both** branches and sort by that column instead. ## Per-branch ORDER BY Writing `ORDER BY` inside a branch of a set operation is not part of the standard grammar: the sort belongs to the query expression, not to a query specification inside it. Several engines do accept a *parenthesized* branch carrying its own `ORDER BY` together with a row limit, which is how you express "the ten newest employees plus the five newest contractors": ```sql (SELECT name, hired_on FROM employees ORDER BY hired_on DESC FETCH FIRST 10 ROWS ONLY) UNION ALL (SELECT name, started_on FROM contractors ORDER BY started_on DESC FETCH FIRST 5 ROWS ONLY) ORDER BY 2 DESC; ``` Two things to understand about that shape. First, the per-branch `ORDER BY` is there to define *which* rows the limit keeps — it is doing selection, not presentation. Second, the ordering inside a branch is not guaranteed to survive into the combined result; only the trailing `ORDER BY` determines the order the client sees. If you drop the final `ORDER BY 2 DESC` above, the rows may come back in any order at all, and an engine that happens to emit them branch-by-branch today is under no obligation to keep doing so. Whether parenthesized branches with their own sorts and limits are accepted at all varies by engine, so check yours before relying on the syntax. ## LIMIT and FETCH FIRST The same rule governs row limiting. A `FETCH FIRST n ROWS ONLY` (or `LIMIT n`) written after the last branch applies to the combined, sorted result — it is the top *n* of everything, not *n* per branch. To get *n* per branch you need the parenthesized-branch form above, with its engine-dependence. And the usual warning applies with extra force here: a row limit without an `ORDER BY` is non-deterministic. Combined with a set operation, which makes no promise about which branch's rows appear first, "the first 10 rows of a UNION" without a sort is essentially an arbitrary sample. ## The mistakes this question is probing - Writing an `ORDER BY` after the first branch and expecting the statement to parse, or expecting it to order only that branch. - Expecting `UNION ALL` to emit the left branch's rows first without any sort. - Sorting by a column name that only exists in the second branch. - Assuming a trailing `LIMIT` applies per branch. All four dissolve once you hold the model: the set operator produces one result, and `ORDER BY` and the row limit belong to that result.

  • How do you take the ten newest employees plus the five newest contractors in one statement?
    Parenthesize each branch with its own ORDER BY and row limit, then order the combined result: `(SELECT ... ORDER BY hired_on DESC FETCH FIRST 10 ROWS ONLY) UNION ALL (SELECT ... FETCH FIRST 5 ROWS ONLY) ORDER BY 2 DESC`. The per-branch sorts choose which rows survive; the trailing ORDER BY defines the order the client sees. Engines differ on accepting this syntax, so verify against yours.
  • Why does ORDER BY after a UNION so often use a column number instead of a name?
    Because the sort sees only the combined result's columns, whose names come from the first branch. When the branches use different names — `hired_on` and `started_on` — the second branch's name simply is not visible, so a positional sort sidesteps the mismatch. Aliasing the first branch to a neutral name is the more readable fix and survives edits to the select list.
  • If a trailing LIMIT follows a UNION ALL of two queries, how many rows come from each branch?
    Unspecified. The limit applies to the combined result, so it takes that many rows in whatever order the statement produced — potentially all from one branch. Without an ORDER BY the choice is arbitrary, and no set operator promises the left branch's rows come first. If you need a per-branch quota, limit inside each parenthesized branch instead.

saying these in an interview costs you the question

  • Writes a separate ORDER BY inside each branch of a UNION
  • Expects a branch's own ORDER BY to determine the final row order
  • Sorts by a column name that exists only in the second branch
  • Thinks a trailing LIMIT applies to each branch separately
  • Assumes UNION ALL emits left-branch rows first without a sort

context