Difficulty: Advanced · Estimated time: 30 min Schema:
00_Sample_Schema.sql
Lessons 01–05 covered CASE in a flat SELECT. This lesson covers
every other place CASE shows up in real engineering work: inside
aggregates (conditional aggregation), inside window functions, nested
CASE, and how CASE compares to its cousins (COALESCE, IIF,
DECODE).
SUM(CASE WHEN ...)) to pivot rows into columnsCASE with window functions for per-row and per-group logic in one queryCASE expressions correctly without losing readabilityCASE, COALESCE, IIF, and DECODE for a given taskThe single most valuable production pattern in this lesson. Instead of
running one query per category, SUM/COUNT combined with CASE
turns rows into columns in a single pass — the SQL equivalent of a
pivot table.
SELECT
e.emp_id,
e.emp_name,
SUM(CASE WHEN o.order_status = 'delivered' THEN o.order_amount ELSE 0 END) AS delivered_revenue,
SUM(CASE WHEN o.order_status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_orders,
COUNT(CASE WHEN o.order_status = 'delivered' THEN 1 END) AS delivered_count
FROM employees e
JOIN orders o ON e.emp_id = o.emp_id
GROUP BY e.emp_id, e.emp_name;
Engineering note: SUM(CASE ... ELSE 0 END) and
COUNT(CASE ... END) (no ELSE) are both common, but they behave
differently — SUM needs an explicit ELSE 0 because
SUM(NULL, NULL, NULL) returns NULL, not 0. COUNT(expression)
already ignores NULLs by definition, so omitting ELSE there is
intentional, not a bug.
SELECT
e.emp_name,
e.dept_id,
e.salary,
CASE
WHEN e.salary = MAX(e.salary) OVER (PARTITION BY e.dept_id) THEN 'Top Earner'
ELSE 'Standard'
END AS pay_tier
FROM employees e;
This runs CASE after the window function computes the per-department
maximum, giving you row-level logic that’s aware of group-level
context — without collapsing rows the way GROUP BY would.
SELECT
e.emp_name,
CASE
WHEN e.performance_score IS NULL THEN 'Not Yet Reviewed'
ELSE
CASE
WHEN e.performance_score >= 4 THEN 'High Performer'
WHEN e.performance_score >= 2 THEN 'Meets Expectations'
ELSE 'Needs Improvement'
END
END AS performance_label
FROM employees e;
Engineering note: nested CASE is legitimate when the outer
condition is a genuinely different kind of check (here: “does a
score even exist?”) from the inner ranking logic. If both levels are
just more thresholds on the same value, flatten them into one CASE
with more WHEN branches instead — nesting for its own sake hurts
readability.
-- ORDER BY: custom sort order not achievable with a plain column sort
SELECT emp_name, dept_id
FROM employees
ORDER BY CASE WHEN dept_id = 1 THEN 0 ELSE 1 END, emp_name;
-- GROUP BY: bucket rows into custom ranges before aggregating
SELECT
CASE WHEN salary >= 150000 THEN 'High' ELSE 'Standard' END AS salary_band,
COUNT(*) AS headcount
FROM employees
GROUP BY CASE WHEN salary >= 150000 THEN 'High' ELSE 'Standard' END;
-- HAVING: filter groups by a classification computed post-aggregation
SELECT dept_id, COUNT(*) AS headcount
FROM employees
GROUP BY dept_id
HAVING COUNT(*) > CASE WHEN dept_id = 1 THEN 2 ELSE 1 END;
-- JOIN condition: rare, but valid -- e.g. different join keys per data source
-- UPDATE: apply the same classification logic to persist a column
UPDATE employees
SET performance_score = CASE
WHEN performance_score IS NULL THEN 3 -- default unreviewed employees to 'meets expectations'
ELSE performance_score
END;
| Function | Purpose | Branch limit | Portable? |
|---|---|---|---|
CASE |
General conditional logic, any boolean expression | Unlimited | Yes — ANSI SQL, all dialects |
COALESCE(a, b, c) |
Return first non-NULL argument | N/A (not conditional logic, just NULL fallback) | Yes — ANSI SQL |
IIF(cond, t, f) |
Shorthand two-branch CASE | 2 | SQL Server, BigQuery only |
DECODE(expr, v1, r1, v2, r2, default) |
Exact-value lookup, like simple CASE | Unlimited but equality-only | Oracle only |
CHOOSE(index, v1, v2, ...) |
Return the Nth value by position | Fixed list | SQL Server only |
Rule of thumb: use COALESCE for pure NULL-fallback logic (it’s
shorter and clearer intent than CASE WHEN x IS NULL THEN y ELSE x
END). Use CASE for everything else — it’s the only one of these
that’s both fully general and portable across every dialect.
SUM(CASE WHEN ... THEN amount ELSE 0 END) need an explicit ELSE 0, but COUNT(CASE WHEN ... THEN 1 END) doesn’t?CASE justified vs when should it be flattened?COALESCE(x, 0) and CASE WHEN x IS NULL THEN 0 ELSE x END?CASE expression in ORDER BY instead of just sorting by a column?