CASE WHEN ... THEN ... END in SQLConditional aggregation in SQL lets you calculate multiple metrics in a single query instead of running several separate queries. The idea is simple: use CASE inside an aggregate function (SUM, COUNT, AVG) so that only rows matching a condition are included in a metric.
This approach is especially useful for reporting, dashboards, and analytics where you need multiple KPIs at once: counts, totals, shares, status splits, and more.
In this lesson, we will cover:
CASE.Classic conditional aggregation pattern:
SUM(CASE WHEN condition THEN value ELSE 0 END)
or for counting rows:
SUM(CASE WHEN condition THEN 1 ELSE 0 END)
What happens here:
CASE returns a value only for matching rows;SELECT
customer_id,
SUM(CASE WHEN amount < 2 THEN 1 ELSE 0 END) AS low_payments,
SUM(CASE WHEN amount BETWEEN 2 AND 6 THEN 1 ELSE 0 END) AS medium_payments,
SUM(CASE WHEN amount > 6 THEN 1 ELSE 0 END) AS high_payments
FROM payment
GROUP BY customer_id
LIMIT 20;
Result: for each customer, the query returns the count of low, medium, and high payments.
SELECT
staff_id,
SUM(CASE WHEN amount < 2 THEN amount ELSE 0 END) AS low_amount_total,
SUM(CASE WHEN amount BETWEEN 2 AND 6 THEN amount ELSE 0 END) AS medium_amount_total,
SUM(CASE WHEN amount > 6 THEN amount ELSE 0 END) AS high_amount_total
FROM payment
GROUP BY staff_id;
Result: one query returns three different totals per staff member.
COUNT()Conditional counts can also be calculated with COUNT, not only with SUM(...1/0...):
COUNT(CASE WHEN condition THEN 1 END)
This is also valid because COUNT includes only non-NULL values.
SELECT
staff_id,
COUNT(CASE WHEN return_date IS NULL THEN 1 END) AS not_returned_count,
COUNT(CASE WHEN return_date IS NOT NULL THEN 1 END) AS returned_count
FROM rental
GROUP BY staff_id;
CASEA pivot transforms rows into columns. Source data often stores categories as row values, while reports usually require those categories as separate columns.
Some DBMSs provide a dedicated PIVOT operator, but the most universal and portable approach is conditional aggregation with CASE.
SELECT
group_column,
SUM(CASE WHEN pivot_key = 'A' THEN measure ELSE 0 END) AS col_a,
SUM(CASE WHEN pivot_key = 'B' THEN measure ELSE 0 END) AS col_b,
SUM(CASE WHEN pivot_key = 'C' THEN measure ELSE 0 END) AS col_c
FROM source_table
GROUP BY group_column;
In the example below, for each film category we count films by rating into separate columns:
SELECT
c.name AS category,
SUM(CASE WHEN f.rating = 'G' THEN 1 ELSE 0 END) AS rating_g,
SUM(CASE WHEN f.rating = 'PG' THEN 1 ELSE 0 END) AS rating_pg,
SUM(CASE WHEN f.rating = 'PG-13' THEN 1 ELSE 0 END) AS rating_pg13,
SUM(CASE WHEN f.rating = 'R' THEN 1 ELSE 0 END) AS rating_r,
SUM(CASE WHEN f.rating = 'NC-17' THEN 1 ELSE 0 END) AS rating_nc17
FROM film f
JOIN film_category fc ON f.film_id = fc.film_id
JOIN category c ON fc.category_id = c.category_id
GROUP BY c.name
ORDER BY c.name;
Result: each row is a category, and rating_* columns show the distribution of films by rating.
ELSE 0 in numeric aggregations to avoid unexpected NULL values.*_count, *_total).CASE conditions do not overlap when categories must be mutually exclusive.LIMIT.Payment report in one query:
SELECT
staff_id,
COUNT(*) AS payments_total,
SUM(amount) AS amount_total,
SUM(CASE WHEN amount >= 5 THEN 1 ELSE 0 END) AS big_payment_count,
SUM(CASE WHEN amount >= 5 THEN amount ELSE 0 END) AS big_payment_total
FROM payment
GROUP BY staff_id;
Pivot by weekday (idea):
count orders for each weekday into separate columns using SUM(CASE WHEN weekday = ... THEN 1 ELSE 0 END).
Conditional share calculation:
SELECT
customer_id,
SUM(CASE WHEN amount >= 5 THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS high_payment_share
FROM payment
GROUP BY customer_id;
CASE.SUM(CASE ...) and COUNT(CASE ...), you can compute multiple metrics in one query.CASE is a universal technique for turning rows into columns.By mastering conditional aggregation, you can write more compact and expressive SQL queries for business analytics.