Postgres by Example: CASE
June 22, 2026 · View on GitHub
CASE is SQL's conditional expression — the closest thing to an if/else. It has two forms. The searched CASE (CASE WHEN cond THEN ... END) evaluates arbitrary boolean conditions. The simple CASE (CASE expr WHEN value THEN ... END) compares an expression to specific values. CASE can be used anywhere an expression is allowed — in SELECT, WHERE, ORDER BY, GROUP BY, and in expressions inside aggregates.
What you'll learn:
- Searched
CASEfor general conditions - Simple
CASEfor value equality ELSEand what happens without it- Using
CASEto bucket rows for grouping - The role of
CASEin conditional aggregation (and whenFILTERreplaces it)
-- Searched CASE (most flexible)
SELECT id, name,
CASE
WHEN id <= 2 THEN 'low'
WHEN id <= 4 THEN 'mid'
ELSE 'high'
END AS tier
FROM fruits
ORDER BY id
LIMIT 5;
-- Simple CASE (equality)
SELECT name,
CASE name
WHEN 'apricot' THEN 'A'
WHEN 'blueberry' THEN 'B'
ELSE '?'
END AS code
FROM fruits
ORDER BY id
LIMIT 4;
-- CASE in WHERE
SELECT id, name
FROM fruits
WHERE CASE WHEN id < 5 THEN id > 1 ELSE false END
ORDER BY id;
-- CASE for bucketing and counting per bucket
SELECT
CASE
WHEN id <= 2 THEN 'low'
WHEN id <= 4 THEN 'mid'
ELSE 'high'
END AS tier,
count(*) AS cnt
FROM fruits
GROUP BY tier
ORDER BY tier;
-- Conditional aggregation: CASE inside an aggregate vs. FILTER (cleaner)
SELECT
sum(CASE WHEN total > 30 THEN total ELSE 0 END) AS big_sum_old_style,
sum(total) FILTER (WHERE total > 30) AS big_sum_filter
FROM orders_example;
The searched form is the workhorse. PostgreSQL evaluates conditions top-to-bottom and returns the result of the first true one. If no condition matches and there is no ELSE, the result is NULL — sometimes useful, sometimes a bug.
The simple form is shorthand for equality: CASE name WHEN 'a' THEN ... END is the same as CASE WHEN name = 'a' THEN ... END. It is concise when you have many discrete values to map.
Conditional aggregation — counting or summing only matching rows — used to require sum(CASE WHEN cond THEN val ELSE 0 END). The modern equivalent, sum(val) FILTER (WHERE cond), is clearer and faster (no CASE evaluation per row). Use FILTER when you can.
To run:
$ psql -f source/case.sql postgres
id | name | tier
----+------------+------
1 | apricot | low
2 | blueberry | low
3 | chestnut | mid
4 | damson | mid
5 | elderberry | high
(5 rows)
...
Common pitfalls:
- All
THEN/ELSEbranches must agree on a common type —CASE WHEN ... THEN 1 ELSE 'x' ENDis an error. Cast explicitly when mixing types:CASE WHEN ... THEN 1::text ELSE 'x' END. - Forgetting
ENDis the most common syntax error. - Without
ELSE, the result isNULLwhen no branch matches. If that is surprising, add an explicitELSE .... CASEinWHEREworks but is usually unnecessary — most conditions are clearer as plain boolean expressions.
Tip: CASE and COALESCE overlap but are not the same. COALESCE(a, b, c) is shorter for "first non-null"; CASE is the right choice when conditions are not just "is null."
Try it: Use CASE to map id to a label like 'small' / 'medium' / 'large' and count per label. Use CASE in ORDER BY to sort priorities in a custom order: ORDER BY CASE priority WHEN 'high' THEN 1 WHEN 'mid' THEN 2 ELSE 3 END. Then rewrite a sum(CASE WHEN ...) as sum(...) FILTER (WHERE ...).
Source: case.sql
Next: CREATE INDEX
Home: Postgres by Example