SQL CASE WHEN and Conditional Aggregation
CASE WHEN is SQL's if/else. On its own it labels rows; inside SUM or COUNT it counts only the rows that match a condition. That second use, conditional aggregation, answers a whole family of interview questions: rates, percentages, and turning rows into columns.
Examples run in MySQL 8 and PostgreSQL · Updated
In short
CASE WHEN cond THEN a … ELSE b ENDreturns the value of the first true condition.- Conditions are checked in order, so put the most specific first.
SUM(CASE WHEN cond THEN 1 ELSE 0 END)counts matching rows inside a group.- One
SUM(CASE …)column per value pivots rows into columns.
Labelling rows
A CASE expression can go anywhere a value can: in SELECT, ORDER BY, GROUP BY or inside a function. The first WHEN that is true wins; if none is true, the ELSE value is used, or NULL when there is no ELSE.
Conditional aggregation
Inside an aggregate, CASE decides what each row contributes. SUM(CASE WHEN salary >= 64000 THEN 1 ELSE 0 END) adds 1 for each matching row and 0 for the rest, giving a count of matches per group, next to other totals in the same query.
Divide that count by COUNT(*) to get a rate. Multiply by 100.0 first: dividing two integers in PostgreSQL drops the decimals.
The example tables
Every example on this page uses these tables. They are small on purpose, so you can check each result by eye.
employees
| emp_id | name | dept_id | salary | manager_id | hire_date |
|---|---|---|---|---|---|
| 1 | Aarav | 10 | 95000 | NULL | 2021-03-01 |
| 2 | Diya | 10 | 88000 | 1 | 2022-06-15 |
| 3 | Kabir | 10 | 72000 | 1 | 2023-01-10 |
| 4 | Meera | 20 | 64000 | NULL | 2020-11-20 |
| 5 | Rohan | 20 | 58000 | 4 | 2024-02-05 |
| 6 | Isha | 30 | 61000 | NULL | 2022-09-12 |
| 7 | Arjun | 20 | 64000 | 4 | 2023-07-30 |
| 8 | Zoya | NULL | 52000 | NULL | 2024-05-01 |
orders
| order_id | customer_id | order_date | amount |
|---|---|---|---|
| 101 | 1 | 2025-01-05 | 120 |
| 102 | 1 | 2025-01-20 | 80 |
| 103 | 2 | 2025-01-07 | 200 |
| 104 | 1 | 2025-02-02 | 150 |
| 105 | 3 | 2025-02-10 | 90 |
| 106 | 2 | 2025-02-25 | 60 |
| 107 | 3 | 2025-03-01 | 130 |
| 108 | 1 | 2025-03-03 | 40 |
Examples
Salary bands
Each employee gets a band from the first condition their salary meets.
SELECT name, salary,
CASE
WHEN salary >= 80000 THEN 'High'
WHEN salary >= 60000 THEN 'Mid'
ELSE 'Entry'
END AS band
FROM employees
ORDER BY salary DESC, name;| name | salary | band |
|---|---|---|
| Aarav | 95000 | High |
| Diya | 88000 | High |
| Kabir | 72000 | Mid |
| Arjun | 64000 | Mid |
| Meera | 64000 | Mid |
| Isha | 61000 | Mid |
| Rohan | 58000 | Entry |
| Zoya | 52000 | Entry |
Count and percentage that match
Per department: headcount, how many earn at least 64,000, and that as a percentage.
SELECT dept_id,
COUNT(*) AS employees,
SUM(CASE WHEN salary >= 64000 THEN 1 ELSE 0 END) AS earning_64k_plus,
ROUND(100.0 * SUM(CASE WHEN salary >= 64000 THEN 1 ELSE 0 END) / COUNT(*), 1) AS pct
FROM employees
WHERE dept_id IS NOT NULL
GROUP BY dept_id
ORDER BY dept_id;| dept_id | employees | earning_64k_plus | pct |
|---|---|---|---|
| 10 | 3 | 3 | 100.0 |
| 20 | 3 | 2 | 66.7 |
| 30 | 1 | 0 | 0.0 |
Pivot months into columns
One row per customer, one column per month. Each SUM only adds amounts from its own month.
SELECT customer_id,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 1 THEN amount ELSE 0 END) AS jan,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 2 THEN amount ELSE 0 END) AS feb,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 3 THEN amount ELSE 0 END) AS mar
FROM orders
GROUP BY customer_id
ORDER BY customer_id;| customer_id | jan | feb | mar |
|---|---|---|---|
| 1 | 200 | 150 | 40 |
| 2 | 200 | 60 | 0 |
| 3 | 0 | 90 | 130 |
Common mistakes
- Conditions in the wrong order. With
WHEN salary >= 60000first, a 95,000 salary is labelled "Mid" and never reaches the "High" branch. - Forgetting ELSE 0. Without
ELSE 0, non-matching rows give NULL.SUMskips them, but a group with no matches returns NULL instead of 0. - Integer division.
SUM(…) / COUNT(*)of two integers can round down to 0 in PostgreSQL. Write100.0 * SUM(…) / COUNT(*).
Practise CASE WHEN
Interview questions that use this concept, easiest first. Each one has the step-by-step visualizer and answer checking.
Ad Click-Through Rate (CTR)
Calculate the percentage CTR per ad campaign rounded to 2 decimals, safely handling campaigns with zero impressions.
Listing Booking Windows
Count each listing's bookings in the last 90 days, the last 365 days and all time.
Valid Promotion Order Share
Percentage of all orders that used a promotion that was actually valid on the order date.
Composer Post Rate by App
Yesterday's share of opened composers that ended in a published post, per app.
Amazon: Repeat Purchase Rate
Calculate the percentage of all registered customers who have placed two or more orders, rounded to 2 decimal places.
Amazon: Monthly Category Revenue Pivot
Pivot monthly product category sales into dedicated columns for Electronics, Apparel, and Home merchandise.
Want a full set? Work through the GROUP BY & Aggregation study plan.
Questions people ask
- Can I use COUNT instead of SUM?
- Yes: COUNT(CASE WHEN cond THEN 1 END) counts matches, because rows without a match return NULL and COUNT skips NULLs.
- Is there a shorter IF function?
- MySQL has IF(cond, a, b) and PostgreSQL has FILTER (WHERE cond) for aggregates, but CASE WHEN works in both.