Skip to content
AgileLogics
5 min read

CASE Expressions

Prerequisites: Grouping with GROUP BY

CASE is SQL's if/else: it returns different values depending on conditions, and it works anywhere a value can go.

Syntax

CASE
  WHEN condition1 THEN result1
  WHEN condition2 THEN result2
  ELSE default_result
END

Labelling values

SELECT name, price,
  CASE
    WHEN price < 50  THEN 'budget'
    WHEN price < 150 THEN 'mid-range'
    ELSE 'premium'
  END AS price_band
FROM products
ORDER BY price;
+-----------------------------+--------+------------+
|            name             | price  | price_band |
+-----------------------------+--------+------------+
| USB-C Hub                   |   35.5 | budget     |
| Laptop Stand                |     42 | budget     |
| Webcam                      |   59.9 | mid-range  |
| Mechanical Keyboard         |     89 | mid-range  |
| Noise-Cancelling Headphones |    199 | premium    |
| 27-inch Monitor             | 249.99 | premium    |
+-----------------------------+--------+------------+

Conditions are checked in order, and the first true one wins. Without an ELSE, unmatched rows get NULL.

Simple CASE

When comparing one value against several options:

SELECT id,
  CASE status
    WHEN 'delivered' THEN 'Done'
    WHEN 'cancelled' THEN 'Cancelled'
    ELSE 'In progress'
  END AS stage
FROM orders
ORDER BY id;
+----+-------------+
| id |    stage    |
+----+-------------+
|  1 | Done        |
|  2 | Done        |
|  3 | In progress |
|  4 | Done        |
|  5 | Cancelled   |
|  6 | In progress |
|  7 | In progress |
|  8 | In progress |
+----+-------------+

Conditional aggregation

CASE inside an aggregate counts or sums only matching rows — a powerful way to build summary tables:

SELECT
  COUNT(*) AS total_orders,
  SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) AS delivered,
  SUM(CASE WHEN status IN ('pending', 'shipped') THEN 1 ELSE 0 END) AS open,
  SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled
FROM orders;
+--------------+-----------+------+-----------+
| total_orders | delivered | open | cancelled |
+--------------+-----------+------+-----------+
|            8 |         3 |    4 |         1 |
+--------------+-----------+------+-----------+

PostgreSQL and SQLite also support the standard FILTER clause: COUNT(*) FILTER (WHERE status = 'delivered').

Custom sorting

SELECT id, status
FROM orders
ORDER BY CASE status
           WHEN 'pending' THEN 1
           WHEN 'shipped' THEN 2
           WHEN 'delivered' THEN 3
           ELSE 4
         END, id;
+----+-----------+
| id |  status   |
+----+-----------+
|  6 | pending   |
|  8 | pending   |
|  3 | shipped   |
|  7 | shipped   |
|  1 | delivered |
|  2 | delivered |
|  4 | delivered |
|  5 | cancelled |
+----+-----------+

Practice

For each customer, show their name and a region column: 'UK' for London, 'USA' for New York and Boston, 'Other' for everything else (including unknown).

Show solution
SELECT name,
  CASE
    WHEN city = 'London' THEN 'UK'
    WHEN city IN ('New York', 'Boston') THEN 'USA'
    ELSE 'Other'
  END AS region
FROM customers;
+-------------------+--------+
|       name        | region |
+-------------------+--------+
| Ada Lovelace      | UK     |
| Linus Torvalds    | Other  |
| Grace Hopper      | USA    |
| Alan Turing       | UK     |
| Margaret Hamilton | USA    |
| Tim Berners-Lee   | Other  |
+-------------------+--------+