On this page
Syntax
CASE WHEN condition THEN result [WHEN ...] [ELSE result] ENDCASE expression WHEN value THEN result [...] [ELSE result] END | Parameter | Type | Required | Description |
|---|---|---|---|
condition |
boolean expression | yes | In the searched form, a full boolean test such as amount > 100 or status IS NULL. |
expression |
scalar expression | yes | In the simple form, the value that is compared with equality against each WHEN value. |
value |
scalar | yes | A literal or expression compared for equality to the CASE expression in the simple form. |
result |
any | yes | The value returned when the branch matches. All results should share a compatible type. |
ELSE result |
any | no | The fallback value when no branch matches. If omitted, CASE returns NULL. |
How it works
CASE is SQL's if/then/else. It is an expression, not a function, so it has no parentheses of its own and can appear anywhere a value is allowed: in SELECT, WHERE, ORDER BY, GROUP BY, HAVING, UPDATE and inside other functions. It walks its branches from top to bottom and returns the result of the first one that matches.
There are two forms. The searched form, CASE WHEN condition THEN result ... END, evaluates a full boolean test in each branch and is the more flexible one. The simple form, CASE expression WHEN value THEN result ... END, compares one expression for equality against a list of values, which reads cleanly when you are mapping a single column to labels.
If no branch matches and there is no ELSE, CASE returns NULL. That NULL behaviour is what makes CASE the engine behind conditional aggregation: because COUNT() and SUM() ignore NULLs, wrapping a condition in CASE lets you count or total only the rows you care about in a single pass. See COALESCE() when you specifically want to replace NULLs with a default.
Examples
Bucket values into labels (searched CASE in SELECT)
SELECT name, score,
CASE
WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
WHEN score >= 70 THEN 'C'
ELSE 'F'
END AS grade
FROM students;
name | score | grade ------+-------+------ Ali | 93 | A Sara | 81 | B Omar | 64 | F
Map a code to text (simple CASE)
SELECT id,
CASE status
WHEN 'P' THEN 'Paid'
WHEN 'U' THEN 'Unpaid'
WHEN 'R' THEN 'Refunded'
ELSE 'Unknown'
END AS status_label
FROM orders;
id | status_label ---+------------- 1 | Paid 2 | Unpaid 3 | Unknown
Conditional aggregation (CASE inside COUNT and SUM)
-- Paid vs unpaid totals in one pass over the table
SELECT
COUNT(CASE WHEN status = 'paid' THEN 1 END) AS paid_orders,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS paid_revenue
FROM orders;
paid_orders | paid_revenue
------------+-------------
1301 | 84250Custom sort order (CASE in ORDER BY)
-- Force a business priority, not alphabetical
SELECT id, priority
FROM tickets
ORDER BY
CASE priority
WHEN 'high' THEN 1
WHEN 'medium' THEN 2
WHEN 'low' THEN 3
ELSE 4
END;
id | priority ---+--------- 7 | high 3 | medium 9 | low
Conditional UPDATE (CASE in SET)
UPDATE employees
SET salary = CASE
WHEN rating = 'top' THEN salary * 1.10
WHEN rating = 'good' THEN salary * 1.05
ELSE salary
END;
-- top performers +10%, good performers +5%, -- everyone else unchanged, in a single statement
Common mistakes
-- Missing END: syntax error
SELECT id,
CASE WHEN amount > 100 THEN 'big'
ELSE 'small'
FROM orders;
Right
SELECT id,
CASE WHEN amount > 100 THEN 'big'
ELSE 'small'
END AS size
FROM orders;
Every CASE must be closed with END. Because CASE is an expression, forgetting END makes the parser read the following clause as part of the expression and the query fails.
-- Simple CASE uses equality, and x = NULL is never true,
-- so this branch never matches. Result is always 'has value'.
SELECT CASE phone
WHEN NULL THEN 'no phone'
ELSE 'has value'
END
FROM customers;
Right
-- Use the searched form with IS NULL
SELECT CASE
WHEN phone IS NULL THEN 'no phone'
ELSE 'has value'
END
FROM customers;
Simple CASE compares with equality, and comparing anything to NULL yields UNKNOWN, never true. To test for NULL you must use the searched form with IS NULL. See COALESCE() for replacing NULLs directly.
-- Mixed result types force a coercion. Here numbers and
-- text collide, which errors or returns text unexpectedly.
SELECT CASE WHEN in_stock THEN qty
ELSE 'none'
END
FROM products;
Right
-- Keep all branches the same type
SELECT CASE WHEN in_stock THEN qty
ELSE 0
END
FROM products;
All THEN/ELSE branches are coerced to one common type. Mixing numbers and strings can raise an error or silently convert the whole column. Keep every branch the same type.
Performance
CASE is evaluated once per row, and its conditions are checked top to bottom until one matches. Put the cheapest and most likely conditions first so most rows short circuit early instead of falling through every branch.
Avoid putting a heavy correlated subquery inside a CASE branch, because it can run for every row and every branch that is reached. Where possible compute the value once (a JOIN or a CTE) and reference it, rather than repeating an expensive lookup in multiple branches.
A CASE in a WHERE or ORDER BY that wraps an indexed column usually makes the expression non sargable, so the index cannot be used. If sort or filter performance matters, consider a persisted computed column or restructuring the predicate so the raw column is exposed to the optimiser.
Interview questions
What is the difference between simple and searched CASE?
Simple CASE expression WHEN value THEN ... compares one expression for equality against each value. Searched CASE WHEN condition THEN ... evaluates a full boolean condition per branch, so it supports ranges, IS NULL and any operator. Searched is more flexible; simple is more concise for equality mapping.
Why can a simple CASE not match a NULL value?
Simple CASE uses equality, and any comparison with NULL evaluates to UNKNOWN, never true. So CASE x WHEN NULL never fires. You must use the searched form with WHEN x IS NULL to detect NULLs.
SELECT CASE WHEN x IS NULL THEN 'null' ELSE 'not null' END
FROM t;
How do you do conditional aggregation with CASE?
Wrap a condition in CASE so non-matching rows become NULL, then aggregate. Because COUNT() and SUM() ignore NULLs, you get a filtered count or total in one scan without a second query.
SELECT
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid,
SUM(CASE WHEN status <> 'paid' THEN 1 ELSE 0 END) AS unpaid
FROM orders;
How do you pivot rows into columns using CASE?
Group by the row key, then use one aggregated CASE per output column so each condition lands in its own column. This turns a tall result into a wide one without a dedicated PIVOT operator.
SELECT year,
SUM(CASE WHEN quarter = 'Q1' THEN amount END) AS q1,
SUM(CASE WHEN quarter = 'Q2' THEN amount END) AS q2,
SUM(CASE WHEN quarter = 'Q3' THEN amount END) AS q3,
SUM(CASE WHEN quarter = 'Q4' THEN amount END) AS q4
FROM sales
GROUP BY year;
What does CASE return when no branch matches and there is no ELSE?
It returns NULL. The ELSE clause is optional and defaults to NULL, which is exactly the behaviour that makes CASE useful inside COUNT and SUM for conditional aggregation.