SQL exercises with solutions: CASE WHEN

SQL exercises with solutions: CASE WHEN

Updated on

CASE picks a value depending on conditions, row by row. These 15 exercises range from level 4 to level 7; they use the syntax of SQLite, SpeedQL’s SQL engine.

Tip: Conditions are tested in order: the first true one wins.

Read the “CASE” card in the cheat sheet

Exercise 1 · level 4

Show each employee's name and a column called level that is 'high' if their salary is at least 40000, otherwise 'low'. Use CASE.

Table employees (8 rows)
idnamedepartmentsalary
1AliceFinance32000
2BobIT41000
3ClaireFinance38000
4DavidHR29000
5EmmaIT50000
6FaridIT47000
7GaelleHR33000
8HugoFinance44000
Show the hint

Topics to use: CASE.

Query structure:

SELECT , CASE WHEN  >=  THEN  ELSE  END AS 
FROM 
Show the solution
SELECT name, CASE WHEN salary >= 40000 THEN 'high' ELSE 'low' END AS level
FROM employees;

Expected result (8 rows):

namelevel
Alicelow
Bobhigh
Clairelow
Davidlow
Emmahigh
Faridhigh
Gaellelow
Hugohigh

Exercise 2 · level 4

Show each invoice's id and a column called status that is 'paid' if the invoice has a payment date, otherwise 'unpaid'. Use CASE.

Table invoices (6 rows)
idclientissueddueamountpaid_on
1Acme2025-01-152025-02-1412002025-02-10
2Acme2025-03-012025-03-16800NULL
3Bolt2025-01-202025-03-064502025-03-01
4Bolt2025-02-252025-03-279502025-04-02
5Cyan2025-03-052025-05-04300NULL
6Cyan2024-12-102024-12-306002025-01-05
Show the hint

Topics to use: CASE, NULL, COALESCE.

Query structure:

SELECT , CASE WHEN  IS NULL THEN  ELSE  END AS 
FROM 
Show the solution
SELECT id, CASE WHEN paid_on IS NULL THEN 'unpaid' ELSE 'paid' END AS status
FROM invoices;

Expected result (6 rows):

idstatus
1paid
2unpaid
3paid
4paid
5unpaid
6paid

Exercise 3 · level 4

Show the title of each movie and a column called length that is 'short' if it lasts less than 100 minutes, 'medium' if it lasts at most 120 minutes, otherwise 'long'. Use CASE.

Table movies (10 rows)
idtitlegenreyeardurationratingdirector_id
1Night TrainThriller20151187.81
2Blue HarborDrama20181027.12
3Paper Moon CityComedy2012956.45
4Silent PeakDrama20201318.23
5Last SignalSci-Fi20191427.51
6Summer KeysComedy2016885.94
7Iron GardenSci-Fi202112583
8Dust and GoldWestern20141106.82
9The Quiet HourDrama2022977.45
10Deep CurrentThriller20171056.9NULL
Show the hint

Topics to use: CASE.

Query structure:

SELECT , CASE WHEN  <  THEN  WHEN  <=  THEN  ELSE  END AS 
FROM 
Show the solution
SELECT title, CASE WHEN duration < 100 THEN 'short' WHEN duration <= 120 THEN 'medium' ELSE 'long' END AS length
FROM movies;

Expected result (10 rows):

titlelength
Night Trainmedium
Blue Harbormedium
Paper Moon Cityshort
Silent Peaklong
Last Signallong
Summer Keysshort
Iron Gardenlong
Dust and Goldmedium
The Quiet Hourshort
Deep Currentmedium

Exercise 4 · level 4

Show the date of each match and a column called result that is 'home' if the home team wins, 'away' if the away team wins, otherwise 'draw' (columns: played_on, result). Use CASE.

Table matches (10 rows)
idplayed_onhome_idaway_idhome_goalsaway_goals
12025-08-021221
22025-08-033400
32025-08-095113
42025-08-102322
52025-08-164531
62025-08-171310
72025-08-232401
82025-08-243523
92025-08-304111
102025-08-315202
Show the hint

Topics to use: CASE.

Query structure:

SELECT , CASE WHEN  >  THEN  WHEN  <  THEN  ELSE  END AS 
FROM 
Show the solution
SELECT played_on, CASE WHEN home_goals > away_goals THEN 'home' WHEN home_goals < away_goals THEN 'away' ELSE 'draw' END AS result
FROM matches;

Expected result (10 rows):

played_onresult
2025-08-02home
2025-08-03draw
2025-08-09away
2025-08-10draw
2025-08-16home
2025-08-17home
2025-08-23away
2025-08-24away
2025-08-30draw
2025-08-31away

Exercise 5 · level 4

Show the id of each flight and a column called slot that is 'morning' if it departs before 12:00, 'afternoon' if it departs before 18:00, otherwise 'evening' (columns: id, slot). Use CASE.

Table flights (12 rows)
idairlineorigindestdepartsduration_minpriceseats_soldcapacity
1SkyJetCDGMAD2025-06-01 08:1012589150180
2AirNovaCDGLIS2025-06-01 11:40155120160170
3BlueWingLYSFCO2025-06-02 07:309575110150
4SkyJetMADCDG2025-06-02 18:2012095170180
5AirNovaBERCDG2025-06-03 09:05110105140160
6BlueWingNCEBER2025-06-03 13:5013014090150
7SkyJetCDGFCO2025-06-04 06:4513599175180
8AirNovaLISMAD2025-06-04 16:15756560120
9BlueWingFCOLYS2025-06-05 20:3010082130150
10SkyJetCDGBER2025-06-05 10:00105110120180
11AirNovaMADLIS2025-06-06 12:25807095120
12BlueWingLYSMAD2025-06-06 15:4011579100150
Show the hint

Topics to use: CASE, Text and dates.

Query structure:

SELECT , CASE WHEN STRFTIME(, ) <  THEN  WHEN STRFTIME(, ) <  THEN  ELSE  END AS 
FROM 
Show the solution
SELECT id, CASE WHEN strftime('%H:%M', departs) < '12:00' THEN 'morning' WHEN strftime('%H:%M', departs) < '18:00' THEN 'afternoon' ELSE 'evening' END AS slot
FROM flights;

Expected result (12 rows):

idslot
1morning
2morning
3morning
4evening
5morning
6afternoon
7morning
8afternoon
9evening
10morning
11afternoon
12afternoon

Exercise 6 · level 4

Show the name and city of each hotel, and a category column: 'luxury' for 5 stars, 'comfort' for 3 or 4 stars, 'budget' otherwise. Name the columns name, city and category.

Table hotels (5 rows)
idnamecitystars
1Seaside InnNice3
2Alpine LodgeAnnecy4
3City LoftParis4
4Old MillBordeaux2
5Grand PalaceParis5
Show the hint

Topics to use: CASE.

Query structure:

SELECT , , CASE WHEN  =  THEN  WHEN  >=  THEN  ELSE  END AS 
FROM 
Show the solution
SELECT name, city, CASE WHEN stars = 5 THEN 'luxury' WHEN stars >= 3 THEN 'comfort' ELSE 'budget' END AS category
FROM hotels;

Expected result (5 rows):

namecitycategory
Seaside InnNicecomfort
Alpine LodgeAnnecycomfort
City LoftPariscomfort
Old MillBordeauxbudget
Grand PalaceParisluxury

Exercise 7 · level 4

Show the name of each product and a size column: 'small' under 10, 'medium' from 10 to under 50, 'large' otherwise. Name the columns name and size.

Table products (7 rows)
idnamecategoryprice
1Desk LampHome35
2Coffee MugKitchen12
3NotebookOffice6
4Office ChairOffice149
5KettleKitchen45
6CushionHome22
7StaplerOffice9
Show the hint

Topics to use: CASE.

Query structure:

SELECT , CASE WHEN  <  THEN  WHEN  <  THEN  ELSE  END AS 
FROM 
Show the solution
SELECT name, CASE WHEN price < 10 THEN 'small' WHEN price < 50 THEN 'medium' ELSE 'large' END AS size
FROM products;

Expected result (7 rows):

namesize
Desk Lampmedium
Coffee Mugmedium
Notebooksmall
Office Chairlarge
Kettlemedium
Cushionmedium
Staplersmall

Exercise 8 · level 4

Show the id and amount of each transaction, and a direction column: 'credit' if the amount is positive, 'debit' otherwise. Name the columns id, amount and direction.

Table transactions (14 rows)
idaccount_idmade_onamountlabel
112025-01-022500salary
212025-01-05-60groceries
312025-01-12-800rent
422025-01-15500transfer
532025-01-031900salary
632025-01-20-120groceries
732025-02-01-950rent
842025-01-252100salary
942025-02-03-45restaurant
1012025-02-022600salary
1112025-02-06-75groceries
1252025-02-1030interest
1322025-02-15500transfer
1442025-02-18-600rent
Show the hint

Topics to use: CASE.

Query structure:

SELECT , , CASE WHEN  >  THEN  ELSE  END AS 
FROM 
Show the solution
SELECT id, amount, CASE WHEN amount > 0 THEN 'credit' ELSE 'debit' END AS direction
FROM transactions;

Expected result (14 rows):

idamountdirection
12500credit
2-60debit
3-800debit
4500credit
51900credit
6-120debit
7-950debit
82100credit
9-45debit
102600credit
11-75debit
1230credit
13500credit
14-600debit

Exercise 9 · level 7

For each department, show the number of employees paid at least 45000 in a column called high and the number of those paid less than 45000 in a column called low (columns: department, high, low).

Table staff (10 rows)
idnamedepartmentsalarymanager_idhired
1AliceFinance52000NULL2015-03-01
2BobIT4100012018-06-15
3ClaireFinance3800012019-01-10
4DavidHR2900012020-09-01
5EmmaIT5000022017-11-20
6FaridIT4700022021-02-14
7GaelleHR3300042022-05-30
8HugoFinance4400032016-08-08
9IrisIT4700052023-01-09
10JulesFinance4400032024-03-18
Show the hint

Topics to use: COUNT, SUM, AVG, MIN, MAX, GROUP BY, CASE.

Query structure:

SELECT , SUM( >= ) AS , SUM( < ) AS 
FROM 
GROUP BY 
Show the solution
SELECT department, SUM(salary >= 45000) AS high, SUM(salary < 45000) AS low
FROM staff
GROUP BY department;

Expected result (3 rows):

departmenthighlow
Finance13
HR02
IT31

Exercise 10 · level 7

Show one row per release decade (2010 for 2010 to 2019, 2020 for 2020 to 2029), with the number of Drama movies in a column called drama and the number of the other movies in a column called other (columns: decade, drama, other).

Table movies (10 rows)
idtitlegenreyeardurationratingdirector_id
1Night TrainThriller20151187.81
2Blue HarborDrama20181027.12
3Paper Moon CityComedy2012956.45
4Silent PeakDrama20201318.23
5Last SignalSci-Fi20191427.51
6Summer KeysComedy2016885.94
7Iron GardenSci-Fi202112583
8Dust and GoldWestern20141106.82
9The Quiet HourDrama2022977.45
10Deep CurrentThriller20171056.9NULL
Show the hint

Topics to use: COUNT, SUM, AVG, MIN, MAX, GROUP BY, CASE.

Query structure:

SELECT ( / ) *  AS , SUM(CASE WHEN  =  THEN  ELSE  END) AS , SUM(CASE WHEN  <>  THEN  ELSE  END) AS 
FROM 
GROUP BY 
Show the solution
SELECT (year / 10) * 10 AS decade, SUM(CASE WHEN genre = 'Drama' THEN 1 ELSE 0 END) AS drama, SUM(CASE WHEN genre <> 'Drama' THEN 1 ELSE 0 END) AS other
FROM movies
GROUP BY decade;

Expected result (2 rows):

decadedramaother
201016
202021

Exercise 11 · level 7

Show one row per team that played at home (columns: name, wins, draws, losses), with its number of home wins, draws and losses.

Table teams (5 rows)
idnamecityfounded
1Red FoxesLyon1950
2Blue OwlsParis1962
3Green BullsLille1971
4Gold HawksNantes1988
5Grey WolvesParis1990
Table matches (10 rows)
idplayed_onhome_idaway_idhome_goalsaway_goals
12025-08-021221
22025-08-033400
32025-08-095113
42025-08-102322
52025-08-164531
62025-08-171310
72025-08-232401
82025-08-243523
92025-08-304111
102025-08-315202
Show the hint

Topics to use: COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CASE.

Query structure:

SELECT , SUM(CASE WHEN  >  THEN  ELSE  END) AS , SUM(CASE WHEN  =  THEN  ELSE  END) AS , SUM(CASE WHEN  <  THEN  ELSE  END) AS 
FROM  
JOIN   ON  = 
GROUP BY 
Show the solution
SELECT t.name, SUM(CASE WHEN m.home_goals > m.away_goals THEN 1 ELSE 0 END) AS wins, SUM(CASE WHEN m.home_goals = m.away_goals THEN 1 ELSE 0 END) AS draws, SUM(CASE WHEN m.home_goals < m.away_goals THEN 1 ELSE 0 END) AS losses
FROM teams t
JOIN matches m ON m.home_id = t.id
GROUP BY t.id;

Expected result (5 rows):

namewinsdrawslosses
Red Foxes200
Blue Owls011
Green Bulls011
Gold Hawks110
Grey Wolves002

Exercise 12 · level 7

Show one row per departure day (columns: day, skyjet, airnova, bluewing) with the number of flights of each airline that day (YYYY-MM-DD format for day).

Table flights (12 rows)
idairlineorigindestdepartsduration_minpriceseats_soldcapacity
1SkyJetCDGMAD2025-06-01 08:1012589150180
2AirNovaCDGLIS2025-06-01 11:40155120160170
3BlueWingLYSFCO2025-06-02 07:309575110150
4SkyJetMADCDG2025-06-02 18:2012095170180
5AirNovaBERCDG2025-06-03 09:05110105140160
6BlueWingNCEBER2025-06-03 13:5013014090150
7SkyJetCDGFCO2025-06-04 06:4513599175180
8AirNovaLISMAD2025-06-04 16:15756560120
9BlueWingFCOLYS2025-06-05 20:3010082130150
10SkyJetCDGBER2025-06-05 10:00105110120180
11AirNovaMADLIS2025-06-06 12:25807095120
12BlueWingLYSMAD2025-06-06 15:4011579100150
Show the hint

Topics to use: COUNT, SUM, AVG, MIN, MAX, GROUP BY, CASE, Text and dates.

Query structure:

SELECT DATE() AS , SUM(CASE WHEN  =  THEN  ELSE  END) AS , SUM(CASE WHEN  =  THEN  ELSE  END) AS , SUM(CASE WHEN  =  THEN  ELSE  END) AS 
FROM 
GROUP BY 
Show the solution
SELECT date(departs) AS day, SUM(CASE WHEN airline = 'SkyJet' THEN 1 ELSE 0 END) AS skyjet, SUM(CASE WHEN airline = 'AirNova' THEN 1 ELSE 0 END) AS airnova, SUM(CASE WHEN airline = 'BlueWing' THEN 1 ELSE 0 END) AS bluewing
FROM flights
GROUP BY day;

Expected result (6 rows):

dayskyjetairnovabluewing
2025-06-01110
2025-06-02101
2025-06-03011
2025-06-04110
2025-06-05101
2025-06-06011

Exercise 13 · level 7

For each hotel, show its name and the number of single, double and suite rooms in three columns (0 if there are none).

Table hotels (5 rows)
idnamecitystars
1Seaside InnNice3
2Alpine LodgeAnnecy4
3City LoftParis4
4Old MillBordeaux2
5Grand PalaceParis5
Table rooms (10 rows)
idhotel_idtypeprice
11single70
21double95
32double130
42suite210
53single110
63double150
74double65
85double260
95suite480
105single190
Show the hint

Topics to use: COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CASE.

Query structure:

SELECT , SUM(CASE WHEN  =  THEN  ELSE  END), SUM(CASE WHEN  =  THEN  ELSE  END), SUM(CASE WHEN  =  THEN  ELSE  END)
FROM  
LEFT JOIN   ON  = 
GROUP BY 
Show the solution
SELECT h.name, SUM(CASE WHEN r.type = 'single' THEN 1 ELSE 0 END), SUM(CASE WHEN r.type = 'double' THEN 1 ELSE 0 END), SUM(CASE WHEN r.type = 'suite' THEN 1 ELSE 0 END)
FROM hotels h
LEFT JOIN rooms r ON r.hotel_id = h.id
GROUP BY h.id;

Expected result (5 rows):

nameSUM(CASE WHEN r.type = 'single' THEN 1 ELSE 0 END)SUM(CASE WHEN r.type = 'double' THEN 1 ELSE 0 END)SUM(CASE WHEN r.type = 'suite' THEN 1 ELSE 0 END)
Seaside Inn110
Alpine Lodge011
City Loft110
Old Mill010
Grand Palace111

Exercise 14 · level 7

For each customer with orders that are not cancelled, show their name and the amount spent in January, February and March 2025 in three columns (0 if nothing).

Table customers (6 rows)
idnamecitysignup
1AlbaParis2024-01-10
2BorisLyon2024-02-15
3CarlaParis2024-03-01
4DenisNantes2024-05-20
5EvaLyon2024-06-30
6FabioLille2024-08-08
Table orders (8 rows)
idcustomer_idorder_datestatus
112025-01-05shipped
222025-01-12shipped
312025-02-03paid
432025-02-10cancelled
542025-02-20shipped
652025-03-02shipped
722025-03-15paid
832025-03-28shipped
Table order_items (14 rows)
order_idproduct_idqty
111
122
241
335
321
451
562
511
624
633
741
761
852
821
Table products (7 rows)
idnamecategoryprice
1Desk LampHome35
2Coffee MugKitchen12
3NotebookOffice6
4Office ChairOffice149
5KettleKitchen45
6CushionHome22
7StaplerOffice9
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CASE, Text and dates.

Query structure:

SELECT , SUM(CASE WHEN  LIKE  THEN  *  ELSE  END), SUM(CASE WHEN  LIKE  THEN  *  ELSE  END), SUM(CASE WHEN  LIKE  THEN  *  ELSE  END)
FROM  
JOIN   ON  = 
JOIN   ON  = 
JOIN   ON  = 
WHERE  <> 
GROUP BY 
Show the solution
SELECT c.name, SUM(CASE WHEN o.order_date LIKE '2025-01%' THEN oi.qty * p.price ELSE 0 END), SUM(CASE WHEN o.order_date LIKE '2025-02%' THEN oi.qty * p.price ELSE 0 END), SUM(CASE WHEN o.order_date LIKE '2025-03%' THEN oi.qty * p.price ELSE 0 END)
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
WHERE o.status <> 'cancelled'
GROUP BY c.id;

Expected result (5 rows):

nameSUM(CASE WHEN o.order_date LIKE '2025-01%' THEN oi.qty * p.price ELSE 0 END)SUM(CASE WHEN o.order_date LIKE '2025-02%' THEN oi.qty * p.price ELSE 0 END)SUM(CASE WHEN o.order_date LIKE '2025-03%' THEN oi.qty * p.price ELSE 0 END)
Alba59420
Boris1490171
Carla00102
Denis0790
Eva0066

Exercise 15 · level 7

For each account, show its id, its owner, the total credits, the total debits (as a positive number) and the balance, in separate columns (0 without transactions).

Table accounts (6 rows)
idownercityopenedkind
1AliceParis2021-03-01current
2AliceParis2022-06-15savings
3BrunoLyon2020-09-10current
4ChloeLyon2023-01-20current
5DavidNice2019-11-05savings
6EmmaNice2024-04-01current
Table transactions (14 rows)
idaccount_idmade_onamountlabel
112025-01-022500salary
212025-01-05-60groceries
312025-01-12-800rent
422025-01-15500transfer
532025-01-031900salary
632025-01-20-120groceries
732025-02-01-950rent
842025-01-252100salary
942025-02-03-45restaurant
1012025-02-022600salary
1112025-02-06-75groceries
1252025-02-1030interest
1322025-02-15500transfer
1442025-02-18-600rent
Show the hint

Topics to use: COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, CASE, NULL, COALESCE.

Query structure:

SELECT , , COALESCE(SUM(CASE WHEN  >  THEN  END), ), COALESCE(-SUM(CASE WHEN  <  THEN  END), ), COALESCE(SUM(), )
FROM  
LEFT JOIN   ON  = 
GROUP BY 
Show the solution
SELECT a.id, a.owner, COALESCE(SUM(CASE WHEN t.amount > 0 THEN t.amount END), 0), COALESCE(-SUM(CASE WHEN t.amount < 0 THEN t.amount END), 0), COALESCE(SUM(t.amount), 0)
FROM accounts a
LEFT JOIN transactions t ON t.account_id = a.id
GROUP BY a.id;

Expected result (6 rows):

idownerCOALESCE(SUM(CASE WHEN t.amount > 0 THEN t.amount END), 0)COALESCE(-SUM(CASE WHEN t.amount < 0 THEN t.amount END), 0)COALESCE(SUM(t.amount), 0)
1Alice51009354165
2Alice100001000
3Bruno19001070830
4Chloe21006451455
5David30030
6Emma000

Practice with automatic checking

In SpeedQL, you write your query and it is checked straight away, on these tables and then on a hidden dataset.

Practice on the 25 “CASE” questions