skip to content

How does SQL evaluate CASE WHEN branches when two WHEN conditions both match a row?

level: middleimportance: must knowfreq 62%

answer

  1. Branches are a ladder, not a lookup
  2. Written order decides the outcome
  3. Overlap is legal and unwarned
  4. Broad conditions listed first swallow narrow ones
  5. Most specific goes on top

basics

~20 s

Branches are considered in written order and the first WHEN whose condition is TRUE supplies the result; later matching branches are never reached. Overlapping conditions are legal, so ordering the branches wrongly makes a branch unreachable.

solid answer

~50 s

A `CASE` is an ordered ladder, not a best-match lookup. The engine considers the `WHEN` conditions top to bottom and stops at the **first** one that evaluates to TRUE (UNKNOWN counts as not matched); the rest are skipped, and if none matches the `ELSE` — implicitly NULL — applies. Overlapping conditions are perfectly legal, which is why `CASE WHEN score >= 50 THEN 'pass' WHEN score >= 90 THEN 'top' END` labels a 95 as `'pass'`: the broad band was tested first, so the specific branch is dead code. The fix is to order branches from most specific to most general, or to make each condition mutually exclusive with an explicit lower bound (`score >= 50 AND score < 90`). The first style is idiomatic; the second is self-documenting but repeats the boundaries, and boundary values are exactly where these bugs live.

code

sql · 14 lines
sql
-- wrong: the broad band is tested first, so 'top' is unreachable
SELECT score,
       CASE WHEN score >= 50 THEN 'pass'
            WHEN score >= 90 THEN 'top'
       END AS band
FROM exam_results;   -- score = 95 -> 'pass'

-- fixed: most specific condition first, with a total ELSE
SELECT score,
       CASE WHEN score >= 90 THEN 'top'
            WHEN score >= 50 THEN 'pass'
            ELSE 'fail'
       END AS band
FROM exam_results;   -- score = 95 -> 'top'

go deeper

for a junior

Know the rule cold: branches are read top to bottom and the first TRUE one wins, so a broad condition placed first hides everything below it.

for a middle

Diagnose an unreachable branch from a query listing, propose both fixes — reorder, or make the conditions mutually exclusive — and explain the tradeoff between them.

for a senior

Talk about how you catch this in production: counts per derived band, tests over boundary values, and treating a category ladder as logic that deserves a regression check.

for a principal

Own the wider question of where classification rules live — a threshold ladder repeated across many queries drifts, and you should be able to argue for a lookup table or a single view instead.

## First match wins The semantics of `CASE` are ordered. Conceptually the engine walks the `WHEN` clauses in the order you wrote them and asks, for each, whether the condition is TRUE for this row. The first TRUE wins: its `THEN` result becomes the value of the expression and no later branch is considered. If no condition is TRUE, the `ELSE` result is used, and an omitted `ELSE` means NULL. Critically, SQL's three-valued logic means "not TRUE" covers two cases: FALSE and UNKNOWN. A condition that evaluates to UNKNOWN — typically because a column involved is NULL — does **not** select its branch. `CASE WHEN discount > 0 THEN 'discounted' ELSE 'full price' END` labels a row whose `discount` is NULL as `'full price'`, because `NULL > 0` is UNKNOWN rather than TRUE. That is a common silent misclassification, and the cure is an explicit NULL branch placed before the branch that would otherwise absorb it. ## The unreachable-branch bug ```sql SELECT score, CASE WHEN score >= 50 THEN 'pass' WHEN score >= 90 THEN 'top' END AS band FROM exam_results; ``` Every score of 90 or more is also 50 or more, so the first branch matches first and `'top'` is unreachable — the expression is *syntactically* fine, the query runs, and the data is quietly wrong. Nothing in SQL warns you: unlike a `switch` in some languages there is no exhaustiveness or overlap check, and unlike a set of `WHERE` predicates there is no result to eyeball that shows the conflict. Two repairs: ```sql -- 1. most specific first (idiomatic) CASE WHEN score >= 90 THEN 'top' WHEN score >= 50 THEN 'pass' ELSE 'fail' END -- 2. mutually exclusive conditions (order-independent) CASE WHEN score >= 90 THEN 'top' WHEN score >= 50 AND score < 90 THEN 'pass' WHEN score < 50 THEN 'fail' END ``` The first is shorter and the standard idiom: a descending ladder where each branch implicitly means "and none of the above". The second states every boundary explicitly, so reordering the branches cannot change the answer — useful when the ladder is long or generated, at the cost of repeating each cut point and giving you two places to update when a threshold moves. ## Ordering as documentation Because a later branch means "and everything above failed", a CASE ladder reads as a decision list and should be written to be read that way. Three habits help: - **Sort the branches monotonically** — descending thresholds, or grouping by the column each branch tests — so a reader can scan the cut points. - **Put NULL handling where it belongs.** If NULL is its own category, `WHEN x IS NULL THEN 'unknown'` goes first; if NULL should share the default, let it fall through to `ELSE` and say so in a comment. - **Always finish with `ELSE`** when every row must have a label, so the ladder is total and adding a row with unexpected data cannot produce a silent NULL. ## What ordering does *not* promise Order is about **which branch supplies the result**, not a guarantee about what the engine computes along the way. The result is defined as if conditions were tested in order and evaluation stopped at the first TRUE, but engines are free to fold constants and to hoist parts of expressions, and some documentation is explicit that a CASE cannot always shield a contained expression from being evaluated. Practically: relying on branch order to compute a value is sound (`CASE WHEN d = 0 THEN NULL ELSE n / d END` is the standard way to avoid dividing by zero and works on the majors), but do not treat a CASE as a guaranteed guard around an expression that raises errors, and verify on your engine if a branch contains something expensive or error-prone. If a branch must never be evaluated for certain rows, filter those rows out instead. ## Related shape: overlapping equality branches The same rule applies to the simple form. `CASE code WHEN 'A' THEN 1 WHEN 'A' THEN 2 END` is legal and returns 1 — duplicates are not rejected, just shadowed. That happens in practice when a long generated mapping list accumulates entries; only the first occurrence of a value ever fires, so a "corrected" mapping appended at the bottom of the ladder does nothing at all. ## How to catch these Since the engine will not tell you, verify categorisation logic with data: group by the derived label and compare the per-band counts against what you expect, or run a query that returns the rows in the band you believe should be non-empty. A band that comes back with zero rows when it should have some is the signature of a shadowed branch.

  • Will the engine warn you that a WHEN branch is unreachable?
    No. SQL performs no exhaustiveness or overlap analysis on CASE branches — an unreachable branch is valid syntax and the query runs normally, returning wrong categories. Catch it with data instead: group by the derived label and check the per-band counts, or select the rows that should have landed in the shadowed band. A band with zero rows is the tell.
  • How does a WHEN condition that evaluates to UNKNOWN behave?
    It does not select its branch — only TRUE does. So a row whose column is NULL falls through to the next condition and often lands in a default that misdescribes it, for example labelling a NULL discount as 'full price'. If NULL is its own category, add an explicit `WHEN col IS NULL THEN ...` branch and place it before any branch that would otherwise absorb those rows.
  • Is it better to write mutually exclusive conditions or rely on branch order?
    Relying on order is idiomatic and shorter: each branch implicitly means "and none above matched". Spelling out exclusive ranges makes the ladder reorder-safe and self-documenting, which pays off in long or generated CASEs, but it repeats every boundary and doubles the places to edit when a threshold changes. Pick one style per codebase and keep boundaries in one place.

A CASE ladder is a sieve stack: the coarsest mesh on top catches everything, so a fine mesh underneath never sees a single grain.

saying these in an interview costs you the question

  • Saying the most specific matching branch wins
  • Believing SQL warns about overlapping conditions
  • Thinking all branches are evaluated and the last wins
  • Assuming a NULL column makes a condition TRUE somewhere
  • Claiming duplicate WHEN values are a syntax error

context