Conditional Expressions (CASE)
CASE brings if/else-style branching logic directly into a SQL expression — usable anywhere a value is expected: the SELECT list, WHERE, ORDER BY, inside an aggregate, and more.Simple CASE — comparing one value
Simple CASE syntax
SELECT
name,
CASE status
WHEN 'pending' THEN 'Awaiting confirmation'
WHEN 'shipped' THEN 'On the way'
WHEN 'delivered' THEN 'Complete'
ELSE 'Unknown status'
END AS status_label
FROM orders;CASE compares one expression (status here) against a list of exact values.Searched CASE — arbitrary conditions
CASE is more flexible: each branch is a full boolean condition, so you can compare ranges, combine multiple columns, or use any expression at all.Bucketing order totals into labeled tiers
SELECT
order_id,
total,
CASE
WHEN total >= 500 THEN 'large'
WHEN total >= 100 THEN 'medium'
ELSE 'small'
END AS order_tier
FROM orders;order_id | total | order_tier
---------+--------+-----------
1001 | 620.00 | large
1002 | 85.00 | small
1003 | 210.00 | mediumWHEN conditions are evaluated top to bottom and the first one that's true wins — order matters, which is why the largest threshold is checked first above. If no branch matches and there's no ELSE, the result is NULL.Conditional aggregation
GROUP BY query, by wrapping a CASE inside an aggregate function.Order status breakdown per customer, in one pass
SELECT customer_id, COUNT(*) AS total_orders, COUNT(CASE WHEN status = 'delivered' THEN 1 END) AS delivered_count, COUNT(CASE WHEN status = 'cancelled' THEN 1 END) AS cancelled_count, SUM(CASE WHEN status = 'delivered' THEN total ELSE 0 END) AS delivered_revenue FROM orders GROUP BY customer_id;
customer_id | total_orders | delivered_count | cancelled_count | delivered_revenue
------------+--------------+-----------------+------------------+-------------------
42 | 12 | 9 | 1 | 980.00
108 | 5 | 4 | 0 | 410.00COUNT(CASE WHEN condition THEN 1 END) works because COUNT() ignores NULL — rows where the condition is false produce NULL (no ELSE needed) and are simply not counted, while matching rows contribute a 1. This lets a single query compute several different conditional totals side by side, instead of running one query per condition.COALESCE() and NULLIF() — related conditional functions
COALESCE() and NULLIF() are shorthand for common patterns that could otherwise be written as CASE expressions, and are worth knowing alongside it.Function | Equivalent CASE | Purpose |
|---|---|---|
|
| Returns the first non-NULL argument |
|
| Returns NULL if |
-- Fall back to a default when a column is NULL SELECT COALESCE(nickname, first_name, 'Unknown') AS display_name FROM users; -- Avoid a division-by-zero error by turning 0 into NULL first SELECT total / NULLIF(quantity, 0) AS unit_price FROM order_items;
a / NULLIF(quantity, 0) is a very common defensive pattern: dividing by zero raises an error, but dividing by NULL simply produces NULL — which usually renders as a harmless blank in a report instead of failing the whole query.CASE when you need multi-branch logic or conditional aggregation; reach for COALESCE() / NULLIF() when the situation is specifically about NULL-handling — they read more clearly than an equivalent CASE for that narrower job.Simple
CASE value WHEN ... THEN ...compares one expression against exact values.Searched
CASE WHEN condition THEN ...supports arbitrary boolean conditions, checked top to bottom.Wrapping
CASEinside an aggregate (e.g.COUNT(CASE WHEN ... THEN 1 END)) computes several conditional totals in one grouped query.COALESCE()returns the first non-NULL value;NULLIF()turns a matching value into NULL.