CASE Expressions
if statement the way a general purpose programming language does, but it has something that covers almost every situation an if/else would: the CASE expression. CASE evaluates one or more conditions and returns a single value depending on which condition matched — and because it is an expression rather than a statement, you can drop it anywhere a value is expected: in SELECT, in WHERE, in ORDER BY, even inside another function call.Two forms of CASE
CASE: the simple form, which compares one expression against a list of possible values, and the searched form, which evaluates an independent boolean condition for each branch. The searched form is more flexible and is what you will reach for most often.Form | Syntax | When to use it |
|---|---|---|
Simple CASE | CASE column WHEN val1 THEN result1 WHEN val2 THEN result2 ELSE default END | Comparing one column/expression against a fixed set of exact values (like a switch statement) |
Searched CASE | CASE WHEN condition1 THEN result1 WHEN condition2 THEN result2 ELSE default END | Arbitrary boolean conditions — ranges, multiple columns, comparisons, |
Simple CASE
The simple form is compact when every branch is just testing the same expression for equality against a different literal value:
Simple CASE
SELECT
order_id,
status,
CASE status
WHEN 'P' THEN 'Pending'
WHEN 'S' THEN 'Shipped'
WHEN 'D' THEN 'Delivered'
WHEN 'C' THEN 'Cancelled'
ELSE 'Unknown'
END AS status_label
FROM orders;Searched CASE
The searched form lets each branch have its own independent condition, which is what you need whenever you are testing ranges or combining multiple columns. A very common use is bucketing a numeric value into a labeled category — for example, turning a person's age into an age-group label:
Searched CASE — bucketing values
SELECT
customer_id,
age,
CASE
WHEN age < 18 THEN 'Minor'
WHEN age BETWEEN 18 AND 29 THEN '18-29'
WHEN age BETWEEN 30 AND 49 THEN '30-49'
WHEN age >= 50 THEN '50+'
ELSE 'Unknown'
END AS age_group
FROM customers;WHEN is returned — later conditions are never checked once one has matched. If no condition matches and there is no ELSE, the expression returns NULL rather than raising an error.Conditional aggregation
CASE is just an expression, you can nest it inside an aggregate function like SUM() or COUNT(). This is the classic trick for turning several rows into several labeled counts within a single query, instead of running one query per label:Conditional aggregation — counting order sizes
SELECT COUNT(CASE WHEN total_amount >= 100 THEN 1 END) AS large_orders, COUNT(CASE WHEN total_amount < 100 THEN 1 END) AS small_orders, SUM(CASE WHEN status = 'Cancelled' THEN 1 ELSE 0 END) AS cancelled_count FROM orders;
COUNT() only counts non-NULL values, so a CASE that returns 1 for a matching row and implicitly NULL otherwise (no ELSE) is an easy way to count how many rows satisfy a condition, all in the same pass over the table.CASE anywhere an expression is valid
SELECT — compute a derived/labeled column, as shown above
WHERE — filter using a condition that itself depends on a branch of logic
ORDER BY — apply custom sort orders, e.g. sorting a status column into a specific business-meaningful sequence rather than alphabetically
GROUP BY — group rows by a computed bucket (many databases let you group by the same CASE expression used in SELECT)
CASE in ORDER BY — custom sort order
SELECT order_id, status
FROM orders
ORDER BY
CASE status
WHEN 'Pending' THEN 1
WHEN 'Shipped' THEN 2
WHEN 'Delivered' THEN 3
WHEN 'Cancelled' THEN 4
END;switch, there is no fall-through between WHEN branches — each branch is fully independent and only one result is ever returned per row.ELSE unless a NULL result for unmatched rows is genuinely what you want. It makes the intent of the expression explicit and avoids silently-NULL output that is easy to miss while debugging.