CASE / WHEN in T-SQL
CASE picks a value by conditions, like if and else if. Use it to label rows, to count only the rows that match with SUM(CASE ...), and to sort in a custom order.
Journeyman · T-SQL · SQL · Updated
After this lesson you can
- Turn values into labels with a CASE expression, searched or simple
- Count or total only the rows that match, with SUM(CASE ...)
Key takeaways
CASEchecks itsWHENs in order and returns the value of the first true one; it ends withEND.- Rows that no
WHENmatches get theELSEvalue, orNULLwhen there's noELSE. - A searched
CASEhas a full condition after eachWHEN; a simpleCASEnames one column and lists the values to match. SUM(CASE WHEN ... THEN 1 ELSE 0 END)counts only the rows that match, and several such counts fit in one query.CASEalso works inGROUP BYto count rows per label, and inORDER BYto sort in your own order.
CASE is SQL's if ... else if ... else. It checks conditions in order, and returns the value of the first one that's true.
Labels from conditions
SELECT TOP (6) ProductName
, UnitsInStock
, CASE
WHEN UnitsInStock = 0 THEN 'Out of stock'
WHEN UnitsInStock < 20 THEN 'Low'
ELSE 'In stock'
END AS StockLevel
FROM Products
ORDER BY ProductId;
| ProductName | UnitsInStock | StockLevel |
|---|---|---|
| Chai | 39 | In stock |
| Chang | 17 | Low |
| Aniseed Syrup | 13 | Low |
| Chef Anton's Cajun Seasoning | 53 | In stock |
| Chef Anton's Gumbo Mix | 0 | Out of stock |
| Grandma's Boysenberry Spread | 120 | In stock |
- The
WHENs are checked in order, and the first true one wins. Chef Anton's Gumbo Mix has 0, which is also less than 20, but= 0comes first. ELSEcovers every row noWHENmatched. Without anELSE, those rows getNULL.CASEends withEND, and usually gets an alias.
This form, with a full condition after each WHEN, is a searched CASE.
Simple CASE: one value, many matches
When every condition compares the same column with =, the simple form is shorter: name the column once, then list the values.
SELECT TOP (4) OrderId
, ShipVia
, CASE ShipVia
WHEN 1 THEN 'Speedy Express'
WHEN 2 THEN 'United Package'
WHEN 3 THEN 'Federal Shipping'
ELSE 'Unknown'
END AS Shipper
FROM Orders
ORDER BY OrderId;
| OrderId | ShipVia | Shipper |
|---|---|---|
| 10248 | 3 | Federal Shipping |
| 10249 | 1 | Speedy Express |
| 10250 | 2 | United Package |
| 10251 | 1 | Speedy Express |
(Here, a join to the Shippers table would be better: the names belong in the database, not in your query. CASE shines when the labels are your own, such as price bands.)
Grouping by a CASE
Group by the CASE, and you can count rows per label. How many products are in each price band?
SELECT CASE
WHEN UnitPrice < 20 THEN 'Budget'
WHEN UnitPrice < 50 THEN 'Standard'
ELSE 'Premium'
END AS PriceBand
, COUNT(*) AS Products
FROM Products
GROUP BY CASE
WHEN UnitPrice < 20 THEN 'Budget'
WHEN UnitPrice < 50 THEN 'Standard'
ELSE 'Premium'
END
ORDER BY Products DESC;
| PriceBand | Products |
|---|---|
| Budget | 39 |
| Standard | 31 |
| Premium | 7 |
Counting only some rows: SUM(CASE ...)
A CASE that returns 1 or 0, added up with SUM, counts the rows that match, and several counts fit in one query:
SELECT COUNT(*) AS Orders
, SUM(CASE WHEN ShippedDate IS NULL THEN 1 ELSE 0 END) AS NotShipped
, SUM(CASE WHEN ShippedDate > RequiredDate THEN 1 ELSE 0 END) AS Late
FROM Orders;
| Orders | NotShipped | Late |
|---|---|---|
| 830 | 21 | 37 |
One pass over the table, three answers. With WHERE, each would need its own query.
Sorting in your own order
CASE works in ORDER BY too. Products that are out of stock first, then the rest, each part by name:
SELECT TOP (5) ProductName, UnitsInStock
FROM Products
ORDER BY CASE WHEN UnitsInStock = 0 THEN 0 ELSE 1 END, ProductName;
| ProductName | UnitsInStock |
|---|---|
| Alice Mutton | 0 |
| Chef Anton's Gumbo Mix | 0 |
| Gorgonzola Telino | 0 |
| Perth Pasties | 0 |
| Thüringer Rostbratwurst | 0 |
Check yourself
2 questions to try on the lesson page.
So what
CASE brings if and else into SQL: labels people understand, counts of exactly the rows you care about, and orders no column could give you, all without leaving the query.