SQL CASE WHEN Explained: Conditional Logic in Queries
On this page 9 sections ▾
- The searched CASE — test any condition
- The simple CASE — compare one column against values
- CASE in ORDER BY — sorting by your own rules
- Counting with CASE — one row of answers
- CASE inside SUM — conditional totals
- Three rules worth remembering
- Practice this topic
- Frequently asked questions
- Related reading
Every query so far either kept a row or dropped it. CASE is how a query decides something about a row instead: it looks at each row, tests your conditions in order, and produces a value based on the first one that matches. It is an expression, not a statement — it produces a value wherever a value can go, which means it works inside SELECT, WHERE, ORDER BY, GROUP BY and even inside aggregate functions.
The searched CASE — test any condition
The most common form lists conditions with WHEN, each paired with a result after THEN. Conditions are checked top to bottom, and the first one that is true wins — later matches are never looked at:
SELECT first_name, salary,
CASE
WHEN salary >= 120000 THEN 'Senior band'
WHEN salary >= 90000 THEN 'Mid band'
ELSE 'Starting band'
END AS pay_band
FROM Employees
ORDER BY salary DESC;
| first_name | salary | pay_band |
|---|---|---|
| Chen | 156000 | Senior band |
| Ananya | 142000 | Senior band |
| Tomas | 131000 | Senior band |
| Marcus | 118000 | Mid band |
| Priya | 105000 | Mid band |
| Leila | 96000 | Mid band |
| Isabela | 92000 | Mid band |
| Grace | 89500 | Starting band |
| Sofia | 79000 | Starting band |
| Hiroshi | 74500 | Starting band |
| Daniel | 68000 | Starting band |
| Omar | 61000 | Starting band |
● 12 rows · produced by running this query on the sample database
Notice that an employee earning 142,000 also satisfies the second condition — but they are labelled 'Senior band' because the first match ends the search. This is why you order the WHEN branches from most specific to least specific. The ELSE branch is optional; without it, rows that match nothing get NULL.
The simple CASE — compare one column against values
When every branch compares the same column against a plain value, there is a shorter form: name the column once after CASE, then list the values:
SELECT name, unit_price,
CASE category
WHEN 'Hardware' THEN 'Physical product'
WHEN 'Software' THEN 'Licence'
ELSE 'Other'
END AS kind
FROM Products;
| name | unit_price | kind |
|---|---|---|
| Aurora Laptop 14" | 1299 | Physical product |
| Nimbus Docking Hub | 189.5 | Other |
| Vertex Monitor 27" | 449.99 | Physical product |
| Quill Keyboard | 119 | Other |
| Atlas Cloud Suite | 899 | Licence |
| Beacon Analytics | 1450 | Licence |
| Harbor Support Plan | 400 | Other |
● 7 rows · produced by running this query on the sample database
The two forms do the same job; the simple form just reads better when it fits. It cannot express ranges or
combined conditions, though — WHEN salary >= 90000 needs the searched form.
CASE in ORDER BY — sorting by your own rules
Because CASE is an expression, it can sit in ORDER BY and define a custom sort order that no column naturally provides. Here, customer tiers sort by importance rather than alphabetically:
SELECT company, tier,
CASE tier
WHEN 'Platinum' THEN 1
WHEN 'Gold' THEN 2
WHEN 'Silver' THEN 3
ELSE 4
END AS tier_rank
FROM Customers
ORDER BY tier_rank, company;
| company | tier | tier_rank |
|---|---|---|
| Acme Robotics | Platinum | 1 |
| Fjord Analytics | Platinum | 1 |
| Andes Textiles | Gold | 2 |
| Cedar Health | Gold | 2 |
| Meridian Trading Co. | Gold | 2 |
| Kilimanjaro Foods | Silver | 3 |
| Sakura Logistics | Silver | 3 |
| Baltic Systems | Bronze | 4 |
● 8 rows · produced by running this query on the sample database
Sorted alphabetically, the tiers would come out Bronze, Gold, Platinum, Silver: the least important tier first and Platinum stuck in third place. Mapping each tier to a number makes the intended order explicit. The ELSE puts Bronze last here, and it would do the same for any tier added later that the CASE does not name yet, which is usually the safe place for it. The CASE sits in the SELECT list here so you can see each rank; written directly after ORDER BY instead, it sorts the rows the same way without showing the number.
Counting with CASE — one row of answers
This is the pattern that makes CASE genuinely powerful. COUNT counts non-NULL values, and a CASE with no ELSE produces NULL for non-matching rows — put the two together and each column counts a different subset in a single pass over the table:
SELECT
COUNT(CASE WHEN status = 'Shipped' THEN 1 END) AS shipped,
COUNT(CASE WHEN status = 'Pending' THEN 1 END) AS pending,
COUNT(CASE WHEN status = 'Cancelled' THEN 1 END) AS cancelled
FROM Orders;
| shipped | pending | cancelled |
|---|---|---|
| 10 | 3 | 2 |
● 1 row · produced by running this query on the sample database
Without CASE this takes three separate queries, or a GROUP BY that returns one row per status stacked vertically. The CASE version returns one row with the statuses side by side — the shape a report usually wants.
CASE inside SUM — conditional totals
The same trick works with SUM to split a total by condition. Here each employee's shipped revenue and cancelled revenue land in separate columns:
SELECT e.first_name,
SUM(CASE WHEN o.status = 'Shipped'
THEN o.order_total ELSE 0 END) AS shipped_revenue,
SUM(CASE WHEN o.status = 'Cancelled'
THEN o.order_total ELSE 0 END) AS lost_revenue
FROM Employees e
JOIN Orders o ON o.emp_id = e.emp_id
GROUP BY e.first_name
ORDER BY shipped_revenue DESC;
| first_name | shipped_revenue | lost_revenue |
|---|---|---|
| Tomas | 40734 | 0 |
| Hiroshi | 22627.89 | 0 |
| Grace | 2697 | 3892.5 |
● 3 rows · produced by running this query on the sample database
With SUM the ELSE 0 matters more than it looks. Leave it out and every non-matching row
contributes NULL, which SUM skips. That is harmless while a group has at least one matching row, but Tomas
and Hiroshi have no cancelled orders at all, so their CASE produces nothing but NULLs, and a SUM of only NULLs
is NULL, not 0:
SELECT e.first_name,
SUM(CASE WHEN o.status = 'Cancelled'
THEN o.order_total END) AS lost_revenue
FROM Employees e
JOIN Orders o ON o.emp_id = e.emp_id
GROUP BY e.first_name
ORDER BY e.first_name;
| first_name | lost_revenue |
|---|---|
| Grace | 3892.5 |
| Hiroshi | NULL |
| Tomas | NULL |
● 3 rows · produced by running this query on the sample database
A NULL in a revenue column reads as "unknown", when the truth is "none". ELSE 0 is what makes the
zero appear; wrapping the whole thing in COALESCE(SUM(...), 0) works too.
Try it yourself
Every query on this page runs against the sample database as-is. Paste one in, change a condition, and watch the result change.
Open the PlaygroundThree rules worth remembering
First match wins
Branches are tested in the order written, and evaluation stops at the first true condition. Overlapping conditions are fine — as long as you wrote the most specific one first.
No ELSE means NULL
A row that matches no branch gets NULL, silently. Sometimes that is exactly right (as in the COUNT pattern above); in output people will read, an explicit ELSE with a label like 'Other' is usually kinder.
Every branch must return a compatible type
One branch returning text and another returning a number is an error in strict databases and a source of quiet weirdness in lenient ones. Keep all THEN results the same kind of value.
Practice this topic
Reading explains it; solving it is what makes it stick. These exercises use exactly what this page covered, and your answer is checked by running it against the same database:
- Label the price bands Easy
- Support levels by tier Easy
- One-row status report Medium
- Revenue won and lost Hard
Frequently asked questions
Is CASE a statement or an expression in SQL?
An expression. It produces a single value per row, so it can appear anywhere a value can: in the SELECT list, in WHERE, in ORDER BY, in GROUP BY, and inside aggregate functions like COUNT and SUM.
What is the difference between simple CASE and searched CASE?
Simple CASE names one column and compares it against listed values. Searched CASE lists full conditions, so it can test ranges, combine columns and use AND/OR. Anything simple CASE can do, searched CASE can also do.
What happens if no WHEN condition matches and there is no ELSE?
The CASE expression returns NULL for that row. This is often used deliberately — for example, CASE inside COUNT counts only the matching rows, because COUNT ignores NULLs.
Can I use CASE in a WHERE clause?
Yes, since it is an expression — but a condition that reaches for CASE can usually be rewritten more clearly with AND and OR. Reserve it for cases where the branching genuinely cannot be flattened.
Can CASE conditions overlap?
Yes. Evaluation runs top to bottom and stops at the first true condition, so overlaps are resolved by position. Write the most specific condition first, or a broader one above it will capture its rows.
Is CASE WHEN the same as IF in SQL?
CASE is the standard, portable form and works in every major database. IF and IIF exist in some dialects as shorthand for two-branch logic: MySQL has IF(), while SQL Server and SQLite, which runs this site's playground, have IIF(). Neither works in every database, so when in doubt, use CASE.
Related reading
About the author
SQL Practice
SQL Practice publishes free SQL tutorials, an in-browser playground and 42 practice exercises, built so you can run what you read. Every runnable example in our tutorials is executed against the site’s sample database before it is published. Read how we keep tutorials accurate, or report a mistake.