Where do NULLs sort in an ORDER BY, and how do you control their placement portably?
answer
- The standard leaves the choice to the engine
- They cluster at one end, always together
- There is a clause for it per key
- Two words after the sort key
- A CASE flag key emulates it anywhere
basics
~20 sThe standard leaves it implementation-defined: an engine treats NULLs as either all-highest or all-lowest, so they cluster at one end. NULLS FIRST or NULLS LAST on a sort key states it explicitly; where the engine lacks that clause, sort by a CASE flag first.
solid answer
~50 sNULL is not a value you can compare, so the standard sidesteps the problem: it says an implementation orders NULLs as either greater than every non-NULL value or less than every non-NULL value, consistently, and leaves the choice to the engine. The practical consequence is that NULLs always end up bunched at one end, but *which* end differs between products — PostgreSQL and Oracle treat them as largest, so they land last under ASC and first under DESC, while MySQL and SQL Server treat them as smallest and do the opposite. Standard SQL lets you pin it per key with `ORDER BY shipped_at NULLS LAST`, but not every engine implements that clause. The fully portable idiom is to sort by an explicit flag first: `ORDER BY CASE WHEN shipped_at IS NULL THEN 1 ELSE 0 END, shipped_at`.
code
sql · 4 lines-- Standard null ordering, per sort key (PostgreSQL, Oracle)
SELECT order_id, shipped_at
FROM orders
ORDER BY shipped_at DESC NULLS LAST;go deeper
Know that NULL rows all end up bunched at one end of the sort, and that which end depends on the database. Be ready to say you would check rather than guess.
Explain that the placement is implementation-defined, name the NULLS FIRST/NULLS LAST clause as the explicit control, and write the CASE-flag rewrite when the clause is unavailable.
Show you treat NULL placement as part of the requirement, not an accident — pinning it explicitly in reports and exports so the output does not change when the query moves to another engine or a key flips to DESC.
Own the portability policy: whether teams may use engine-specific null ordering at all, and how cross-engine queries and generated SQL keep a single documented convention for where missing values appear.
## Why NULL is a problem for sorting Sorting needs a total order: for any two rows the engine must be able to say which comes first. NULL breaks that, because NULL is a marker for *no value*, and every comparison against it (`NULL < 5`, `NULL = NULL`) yields UNKNOWN rather than true or false. If ORDER BY used ordinary comparison semantics, a NULL could never be placed anywhere. The standard resolves this with a special rule that applies only to ordering: for the purpose of ORDER BY, all NULLs are considered equal to each other, and they are considered either greater than every non-NULL value or less than every non-NULL value. Which of the two is implementation-defined. Note the two separate guarantees — NULLs cluster together, and they cluster at one end — with only the choice of end left open. ## Where the majors actually put them | Engine | NULLs treated as | Position under ASC | Position under DESC | |---|---|---|---| | PostgreSQL | largest | last | first | | Oracle | largest | last | first | | MySQL | smallest | first | last | | SQL Server | smallest | first | last | Two things follow. First, a query that reads correctly on one engine can put NULLs at the opposite end on another, which is a classic surprise when a report is ported. Second, adding DESC to a key flips the NULLs along with everything else — people frequently forget that reversing the sort also moves the NULL block from one end to the other. ## The standard clause: NULLS FIRST and NULLS LAST A sort specification may carry an explicit null ordering: ```sql SELECT order_id, shipped_at FROM orders ORDER BY shipped_at DESC NULLS LAST; ``` The null ordering is per key, like the direction, and it overrides the engine default. PostgreSQL and Oracle accept it; MySQL and SQL Server do not, so a query using it is not universally portable — check your own engine's documentation before relying on it. ## The portable idiom: sort by a flag first When you cannot rely on NULLS FIRST/LAST, add a computed key that encodes the placement you want and put it *before* the real key: ```sql SELECT order_id, shipped_at FROM orders ORDER BY CASE WHEN shipped_at IS NULL THEN 1 ELSE 0 END, -- NULLs last shipped_at; ``` The CASE produces 0 for rows with a value and 1 for rows without, so the flag key splits the result into two blocks and the second key orders within them. Swap 1 and 0 to put NULLs first. This works on every engine, since it uses nothing but CASE, IS NULL and a two-key ORDER BY — and it does not need to appear in the SELECT list, because ORDER BY may sort by an expression it does not return (the exception is SELECT DISTINCT, where sort keys must be select-list items). A shorter variant, `ORDER BY shipped_at IS NULL, shipped_at`, relies on a boolean-valued expression being usable as a sort key. That is fine on engines with a boolean type but not universal, so the CASE form is the safer default. ## What about COALESCE? A tempting alternative is `ORDER BY COALESCE(shipped_at, DATE '9999-12-31')`. It works, but it is fragile: you must invent a sentinel of the right type that is guaranteed larger (or smaller) than any real value, and the day a real row carries that sentinel value the ordering quietly merges the two groups. The CASE flag has no such failure mode because the flag is computed from IS NULL, not from the data's range. ## Interaction with direction Direction and null ordering are independent knobs on the same key. `ORDER BY score DESC NULLS LAST` means "highest score first, and rows with no score at the bottom" — a common reporting requirement that neither DESC alone nor NULLS LAST alone expresses. When you emulate this with the flag trick, remember the flag key itself normally stays ASC while the real key takes DESC: ```sql ORDER BY CASE WHEN score IS NULL THEN 1 ELSE 0 END, score DESC; ``` ## Practical guidance - Decide deliberately where NULLs belong; do not inherit whatever the engine defaults to. - If the query must run on more than one product, use the CASE flag. - If you use NULLS FIRST/LAST, note in the query or the migration guide which engines you are targeting. - Remember NULLs move ends when you flip ASC to DESC. - Sorting NULLs is a placement question only; it never removes them. If NULL rows should not be in the result at all, that is a WHERE ... IS NOT NULL decision.
- If you switch a key from ASC to DESC, what happens to the NULL rows?They move to the other end. The engine's default treats NULLs as uniformly largest or uniformly smallest, and reversing the sort reverses their position along with every other value. That is why `ORDER BY score DESC` on an engine that treats NULLs as largest suddenly shows the score-less rows at the top — you need an explicit NULLS LAST, or the CASE flag, to keep them at the bottom.
- Are the NULLs guaranteed to stay together, or can they be scattered through the result?They stay together. The ordering rule treats all NULLs as equal to one another and as uniformly above or below every non-NULL value, so they form one contiguous block at one end of that sort key's ordering. They can only be split apart by a *preceding* sort key that separates the rows first.
- Why prefer a CASE flag over COALESCE with a sentinel value?COALESCE requires inventing a value of the right type that outranks all real data, and the ordering silently breaks if a genuine row ever holds that value. It also forces a type-compatible sentinel for every column type. The CASE flag derives placement from IS NULL alone, so it is correct regardless of the column's type or value range.
saying these in an interview costs you the question
- Says the SQL standard fixes NULLs last everywhere
- Assumes NULLs sort as zero or empty string
- Believes DESC leaves the NULL block where it was
- Thinks NULLS LAST works on every engine
- Claims ORDER BY drops rows whose sort key is NULL