ShareYourGroupShareYourGroupSQL

Lesson 23 of 26 · see the practice database

CASE: if, then, else in a query

CASE is SQL's way of saying "if this, then that, otherwise something else". It lets a query turn values into labels, sort rows into bands, and count different things in different columns. Once you know it, you will find a use for it in most reports.

The shape of CASE

SELECT name, price,
       CASE
         WHEN price >= 1000 THEN 'premium'
         WHEN price >= 300  THEN 'mid-range'
         ELSE 'budget'
       END AS band
FROM products
ORDER BY price DESC;

Result

namepriceband
Pressure Cooker 3 L1450premium
Power Bank1299premium
Bedsheet Double899mid-range
School Bag750mid-range
Basmati Rice 5 kg650mid-range
Earphones499mid-range
Steel Bottle 1 L450mid-range
Extension Board399mid-range
Wall Clock349mid-range
Tea 500 g280budget
Toor Dal 1 kg160budget
Sunflower Oil 1 L145budget

16 rows

Showing the first 12 of 16 rows.

A CASE starts with the word CASE and ends with END. In between:

Because the first match wins, the order of the WHEN lines matters. A price of 1,450 is also greater than 300, but the first test catches it. Put the narrowest condition first.

CASE produces a value, so it can go anywhere a value can: in the SELECT list, in WHERE, in ORDER BY, inside a function.

Without ELSE you get NULL

If nothing matches and there is no ELSE, the result is NULL.

SELECT name, stock,
       CASE WHEN stock = 0 THEN 'sold out' END AS note
FROM products
WHERE category = 'Electronics';

Result

namestocknote
LED Bulb 9 W200NULL
Extension Board35NULL
Earphones0sold out
Power Bank18NULL

4 rows

Writing an ELSE every time is a good habit, even if it only says ELSE 'other'. It shows that you thought about the remaining cases.

The short form

When every test compares the same column with a fixed value, name the column once after CASE.

SELECT id, status,
       CASE status
         WHEN 'delivered' THEN 'Done'
         WHEN 'shipped'   THEN 'On the way'
         WHEN 'pending'   THEN 'Waiting'
         ELSE 'Stopped'
       END AS meaning
FROM orders
LIMIT 6;

Result

idstatusmeaning
1deliveredDone
2deliveredDone
3cancelledStopped
4deliveredDone
5deliveredDone
6deliveredDone

6 rows

The short form can only test for equality. For ranges, NULL checks or anything with AND, use the full form.

Dealing with NULL

CASE can replace a missing value with a label. For this simple job COALESCE is shorter, but CASE lets you do more.

SELECT name,
       CASE WHEN email IS NULL THEN 'ask for email' ELSE 'has email' END AS email_status
FROM customers
ORDER BY name
LIMIT 5;

Result

nameemail_status
Arjun Mehtaask for email
Asha Vermahas email
Imran Alihas email
Joy Thomashas email
Kabir Khanhas email

5 rows

Grouping by a CASE

Label the rows, then count by label. This is how bands and brackets are reported.

SELECT CASE
         WHEN salary >= 50000 THEN '50,000 and above'
         WHEN salary >= 35000 THEN '35,000 to 49,999'
         ELSE 'below 35,000'
       END AS salary_band,
       COUNT(*) AS staff
FROM employees
GROUP BY 1
ORDER BY MIN(salary) DESC;

Result

salary_bandstaff
50,000 and above2
35,000 to 49,9994
below 35,0002

3 rows

GROUP BY 1 means "group by the first column of the SELECT list". It saves repeating the whole CASE. SQLite, MySQL and PostgreSQL accept it. In SQL Server and Oracle, repeat the CASE in the GROUP BY.

Conditional counting

This is the most valuable CASE pattern. Put a CASE inside an aggregate, and one query can count several different things side by side.

SELECT COUNT(*) AS all_orders,
       SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) AS delivered,
       SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled,
       SUM(CASE WHEN status IN ('pending', 'shipped') THEN 1 ELSE 0 END) AS in_progress
FROM orders;

Result

all_ordersdeliveredcancelledin_progress
201325

1 row

Each CASE turns a row into 1 or 0, and SUM adds the ones. Without CASE this would take four separate queries.

Combine it with GROUP BY and you get a cross table: one row per customer, one column per status.

SELECT c.name,
       SUM(CASE WHEN o.status = 'delivered' THEN 1 ELSE 0 END) AS delivered,
       SUM(CASE WHEN o.status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled,
       SUM(CASE WHEN o.status IN ('pending', 'shipped') THEN 1 ELSE 0 END) AS open
FROM customers c
JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY c.name;

Result

namedeliveredcancelledopen
Asha Verma301
Imran Ali200
Joy Thomas100
Kabir Khan111
Neha Joshi011
Priya Nair101
Ravi Kumar300
Sunita Rao101
Vikram Das100

9 rows

Turning rows into columns like this is called pivoting.

Conditional totals

Return an amount in place of the 1 and you get totals split by condition.

SELECT p.category,
       SUM(CASE WHEN o.status = 'delivered' THEN oi.quantity * p.price ELSE 0 END) AS delivered_sales,
       SUM(CASE WHEN o.status = 'cancelled' THEN oi.quantity * p.price ELSE 0 END) AS cancelled_sales
FROM order_items oi
JOIN products p ON oi.product_id = p.id
JOIN orders o   ON oi.order_id = o.id
GROUP BY p.category
ORDER BY p.category;

Result

categorydelivered_salescancelled_sales
Electronics48141299
Grocery43550
Home2800899
Stationery25200

4 rows

A percentage

The average of ones and zeros is the share of ones. Multiply by 100 for a percentage. Note the 1.0, which keeps the decimals.

SELECT ROUND(100 * AVG(CASE WHEN status = 'delivered' THEN 1.0 ELSE 0 END), 1) AS percent_delivered
FROM orders;

Result

percent_delivered
65.0

1 row

A custom sort order

Alphabetical order is rarely the order a business wants. CASE in ORDER BY gives each value a rank of your choosing.

SELECT id, status, order_date
FROM orders
ORDER BY CASE status
           WHEN 'pending'   THEN 1
           WHEN 'shipped'   THEN 2
           WHEN 'delivered' THEN 3
           ELSE 4
         END,
         order_date
LIMIT 8;

Result

idstatusorder_date
18pending2026-09-01
19pending2026-09-17
20pending2026-10-04
13shipped2026-05-19
17shipped2026-08-11
1delivered2026-01-05
2delivered2026-01-12
4delivered2026-02-02

8 rows

Pending orders, the ones needing attention, come first.

Avoiding division by zero

A CASE can guard a division.

SELECT name, price, stock,
       CASE WHEN stock = 0 THEN NULL ELSE price * 1.0 / stock END AS price_per_unit_in_stock
FROM products
WHERE category = 'Grocery';

Result

namepricestockprice_per_unit_in_stock
Basmati Rice 5 kg6504016.25
Toor Dal 1 kg160752.1333333333333333
Sunflower Oil 1 L145602.4166666666666665
Tea 500 g2800NULL

4 rows

A shorter way is the function NULLIF(a, b), which returns NULL when the two values are equal: price / NULLIF(stock, 0). In SQLite and MySQL, dividing by zero quietly gives NULL anyway. PostgreSQL, SQL Server and Oracle stop with an error, so the guard matters there.

CASE in an UPDATE

A single UPDATE can change different rows in different ways.

UPDATE products
SET price = CASE
              WHEN category = 'Grocery' THEN price + 5
              WHEN category = 'Home'    THEN price - 50
              ELSE price
            END;

Result

Done. 16 rows changed.

Do not forget the ELSE price. Without it, every product outside the two categories would have its price set to NULL.

Common mistakes

Try it yourself

Exercise 1. Show each employee's name and a column level that says senior if the salary is 45000 or more and junior otherwise.

Solve it in the practice box

Show one answer
SELECT name, CASE WHEN salary >= 45000 THEN 'senior' ELSE 'junior' END AS level FROM employees;
namelevel
Anita Kapoorsenior
Rohit Bansalsenior
Deepa Iyersenior
Manoj Yadavsenior
Farah Sheikhjunior
Gopal Reddyjunior
Harpreet Kaurjunior
Nikhil Jainjunior

8 rows

Exercise 2. In one row, show how many products are in stock (stock above 0) and how many are sold out. Two columns.

Solve it in the practice box

Show one answer
SELECT SUM(CASE WHEN stock > 0 THEN 1 ELSE 0 END), SUM(CASE WHEN stock = 0 THEN 1 ELSE 0 END) FROM products;
SUM(CASE WHEN stock > 0 THEN 1 ELSE 0 END)SUM(CASE WHEN stock = 0 THEN 1 ELSE 0 END)
142

1 row

Exercise 3. Show each product's name and a column stock_note: none when stock is 0, low when it is below 25, and fine otherwise.

Solve it in the practice box

Show one answer
SELECT name, CASE WHEN stock = 0 THEN 'none' WHEN stock < 25 THEN 'low' ELSE 'fine' END AS stock_note FROM products;
namestock_note
Basmati Rice 5 kgfine
Toor Dal 1 kgfine
Sunflower Oil 1 Lfine
Tea 500 gnone
Notebook 200 pagesfine
Ball Pen pack of 10fine
Geometry Boxfine
School Baglow
LED Bulb 9 Wfine
Extension Boardfine
Earphonesnone
Power Banklow

16 rows

Showing the first 12 of 16 rows.

Examples on this page were run with SQLite 3.50.4 on 12 November 2026. The results shown are the real results.

This is an independent learning site. It is not connected with the makers of any database product. MySQL, PostgreSQL, SQL Server, Oracle and SQLite are named only so that you know where a piece of SQL works. The shop, its customers and its staff in the practice database are made up.