SQL Tutorial › Querying Data › CASE Expression

SQL CASE Expression

IntermediateLesson 17 of 6912 min read7 queries run on MySQL 8.4

CASE is SQL’s if-then-else. It looks at each row, checks conditions in order, and returns the value of the first one that is true. Use it to label rows (“High”, “Low”), to sort in a custom order, and, combined with SUM or COUNT, to turn rows into report columns.

Advertisement
In this lesson
  • Write searched CASE WHEN ... THEN ... ELSE ... END
  • Use the short simple CASE form
  • Sort with a custom order
  • Count and pivot with SUM(CASE ...)

CASE syntax

MySQL
CASE  WHEN condition1 THEN result1  WHEN condition2 THEN result2  ...  ELSE default_resultEND
  • Conditions are checked top to bottom; the first true one wins.
  • If none is true and there is no ELSE, the result is NULL.
  • CASE is an expression, so it can be used anywhere a value can: SELECT, WHERE, ORDER BY, GROUP BY, inside functions.

Sample data

employees · 12 rows (some columns)
emp_idfirst_namedept_idsalarycommission
100MeeraNULL250000NULL
101Asha1125000NULL
102Rahul198000NULL
103Priya172000NULL
104Karan172000NULL
105Vikram210500012000
106Neha2540006000
107Arjun2480004500
108Kavya388000NULL
109Rohan345000NULL
110Sneha478000NULL
111Amit125000NULL

Label rows: salary bands

SQL
SELECT first_name, salary,       CASE         WHEN salary >= 100000 THEN 'High'         WHEN salary >= 60000  THEN 'Medium'         ELSE 'Entry'       END AS salary_bandFROM employeesORDER BY salary DESC;
ResultMySQL 8.4
first_namesalarysalary_band
Meera250000High
Asha125000High
Vikram105000High
Rahul98000Medium
Kavya88000Medium
Sneha78000Medium
Priya72000Medium
Karan72000Medium
Neha54000Entry
Arjun48000Entry
Rohan45000Entry
Amit25000Entry
12 rows in set
How this query works, step by step
  1. For each row, MySQL tests salary >= 100000 first. Meera, Asha and Vikram get 'High'.
  2. Rows that failed the first test are checked against salary >= 60000: these get 'Medium'. A salary of 125,000 never reaches this test, so order matters.
  3. Everything else falls through to ELSE 'Entry'.
  4. AS salary_band names the new column.
Watch out

Put the most specific condition first. If WHEN salary >= 60000 came first, every high earner would be labelled ‘Medium’.

Simple CASE

When every test compares the same expression for equality, the short form is easier to read:

SQL
SELECT order_id, status,       CASE status         WHEN 'Delivered' THEN 'Closed'         WHEN 'Cancelled' THEN 'Closed'         ELSE 'Open'       END AS ticket_stateFROM orders;
ResultMySQL 8.4
order_idstatusticket_state
1001DeliveredClosed
1002DeliveredClosed
1003DeliveredClosed
1004CancelledClosed
1005DeliveredClosed
1006ShippedOpen
1007DeliveredClosed
1008PendingOpen
1009DeliveredClosed
1010DeliveredClosed
10 rows in set
How this query works, step by step
  1. CASE status names the expression once.
  2. Each WHEN 'value' is compared with status using =.
  3. Pending and Shipped orders fall through to 'Open'.
Note

The simple form cannot test NULL (CASE x WHEN NULL is never true) or ranges. Use the searched form for those.

CASE with NULL

SQL
SELECT first_name,       CASE         WHEN commission IS NULL THEN 'No commission'         WHEN commission >= 10000 THEN 'Top seller'         ELSE 'Seller'       END AS sales_roleFROM employeesWHERE dept_id IN (2, 3);
ResultMySQL 8.4
first_namesales_role
VikramTop seller
NehaSeller
ArjunSeller
KavyaNo commission
RohanNo commission
5 rows in set

CASE in ORDER BY

Sort products so that out-of-stock items come last, and the rest by price:

SQL
SELECT product_name, stock, priceFROM productsORDER BY CASE WHEN stock = 0 THEN 1 ELSE 0 END, price;
ResultMySQL 8.4
product_namestockprice
Gel Pens250199
Notebook Pack300249
Wireless Mouse120799
Desk Lamp301499
Monitor Arm182799
Mechanical Keyboard453499
Office Chair128999
Standing Desk518999
USB-C Hub01999
9 rows in set
How this query works, step by step
  1. The CASE gives 0 to products in stock and 1 to out-of-stock products.
  2. Sorting by it first moves the USB-C Hub to the end.
  3. price orders the rest from cheapest to dearest.

Conditional counts with SUM(CASE …)

How many orders does each customer have, and how many of them were delivered?

SQL
SELECT customer_id,       COUNT(*) AS orders,       SUM(CASE WHEN status = 'Delivered' THEN 1 ELSE 0 END) AS delivered,       SUM(CASE WHEN status <> 'Delivered' THEN 1 ELSE 0 END) AS not_deliveredFROM ordersGROUP BY customer_idORDER BY customer_id;
ResultMySQL 8.4
customer_idordersdeliverednot_delivered
1321
2220
3211
4110
5211
5 rows in set
How this query works, step by step
  1. GROUP BY customer_id makes one group per customer.
  2. Inside each group, the CASE turns every delivered order into 1 and every other order into 0.
  3. SUM of those ones and zeros is the number of delivered orders.
  4. This pattern, conditional aggregation, is one of the most useful tricks in SQL reports.

Pivot rows into columns

Headcount per department, with one column per city group:

SQL
SELECT d.dept_name,       SUM(CASE WHEN e.city = 'Bengaluru' THEN 1 ELSE 0 END) AS bengaluru,       SUM(CASE WHEN e.city = 'Pune'      THEN 1 ELSE 0 END) AS pune,       SUM(CASE WHEN e.city NOT IN ('Bengaluru', 'Pune') THEN 1 ELSE 0 END) AS otherFROM departments dJOIN employees e ON e.dept_id = d.dept_idGROUP BY d.dept_nameORDER BY d.dept_name;
ResultMySQL 8.4
dept_namebengalurupuneother
Engineering410
HR001
Marketing020
Sales003
4 rows in set
How this query works, step by step
  1. The join gives each employee their department name.
  2. Each SUM(CASE ...) counts the employees of one city group.
  3. The result has one row per department and one column per city group: a pivot table.

IF() in MySQL

For a single condition MySQL also offers IF(condition, then, else):

SQL
SELECT product_name, IF(stock > 0, 'In stock', 'Sold out') AS availabilityFROM products;
ResultMySQL 8.4
product_nameavailability
Wireless MouseIn stock
Mechanical KeyboardIn stock
USB-C HubSold out
Office ChairIn stock
Standing DeskIn stock
Notebook PackIn stock
Gel PensIn stock
Desk LampIn stock
Monitor ArmIn stock
9 rows in set

IF() is MySQL-only. CASE works in every database, so prefer it in code that might move.

Exercise: Price labels

Label each product as 'Budget' (price under 1,000), 'Standard' (1,000 to 4,999) or 'Premium' (5,000 and above). Show product_name, price and the label as price_label, cheapest first.

Expected resultMySQL 8.4
product_namepriceprice_label
Gel Pens199Budget
Notebook Pack249Budget
Wireless Mouse799Budget
Desk Lamp1499Standard
USB-C Hub1999Standard
Monitor Arm2799Standard
Mechanical Keyboard3499Standard
Office Chair8999Premium
Standing Desk18999Premium
9 rows in set
Hint 1

Check the cheapest condition first, then the next, then ELSE.

Exercise: Orders by status

In one row, count the delivered, cancelled and other orders.

Expected resultMySQL 8.4
deliveredcancelledother
712
1 row in set
Hint 1

Three SUM(CASE ...) columns and no GROUP BY.

Quick quiz

1. A CASE has no ELSE and no condition matches. What is returned?

2. Two WHEN conditions are both true for a row. Which result is used?

3. What does SUM(CASE WHEN status = 'Delivered' THEN 1 ELSE 0 END) calculate?

4. Can CASE be used in ORDER BY?

CASE statement FAQ

How do you write IF-THEN-ELSE in SQL?

Use CASE WHEN condition THEN value ELSE other_value END. MySQL also has IF(condition, value, other_value) for a single test.

What is the difference between simple CASE and searched CASE?

Simple CASE x WHEN 1 THEN ... compares one expression with values using equality. Searched CASE WHEN condition THEN ... can test any condition, including ranges and IS NULL.

Can I use CASE in a WHERE clause?

Yes, because CASE returns a value, but a plain combination of AND and OR is usually clearer and can use indexes better.

How do I pivot rows to columns in MySQL?

Group the rows and use one SUM(CASE WHEN ... THEN 1 ELSE 0 END) (or MAX(CASE ...)) column per value you want as a column.