SQL exercises with solutions: HAVING

SQL exercises with solutions: HAVING

Updated on

HAVING filters groups after GROUP BY (WHERE filters rows before). These 12 exercises range from level 2 to level 3; they use the syntax of SQLite, SpeedQL’s SQL engine.

Tip: A condition on COUNT, SUM or AVG goes in HAVING, never in WHERE.

Read the “HAVING” card in the cheat sheet

Exercise 1 · level 2

Show the departments that have more than 2 employees, with their number of employees.

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

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

Query structure:

SELECT , COUNT(*)
FROM 
GROUP BY 
HAVING COUNT(*) > 
Show the solution
SELECT department, COUNT(*)
FROM employees
GROUP BY department
HAVING COUNT(*) > 2;

Expected result (2 rows):

departmentCOUNT(*)
Finance3
IT3

Exercise 2 · level 3

Show the name and the total order amount of the customers whose total is above 160.

Table customers (4 rows)
idnamecity
1AliceParis
2BrunoLyon
3ChloeParis
4DylanNantes
Table orders (6 rows)
idcustomer_idamount
11120
2180
32200
4350
5375
6330
Show the hint

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

Query structure:

SELECT , SUM()
FROM 
INNER JOIN  ON  = 
GROUP BY 
HAVING SUM() > 
Show the solution
SELECT customers.name, SUM(orders.amount)
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY customers.id
HAVING SUM(orders.amount) > 160;

Expected result (2 rows):

nameSUM(orders.amount)
Alice200
Bruno200

Exercise 3 · level 3

Show each city whose total order amount is above 250, with that total.

Table customers (4 rows)
idnamecity
1AliceParis
2BrunoLyon
3ChloeParis
4DylanNantes
Table orders (6 rows)
idcustomer_idamount
11120
2180
32200
4350
5375
6330
Show the hint

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

Query structure:

SELECT , SUM()
FROM 
INNER JOIN  ON  = 
GROUP BY 
HAVING SUM() > 
Show the solution
SELECT customers.city, SUM(orders.amount)
FROM customers
INNER JOIN orders ON customers.id = orders.customer_id
GROUP BY customers.city
HAVING SUM(orders.amount) > 250;

Expected result (1 row):

citySUM(orders.amount)
Paris355

Exercise 4 · level 3

Show the name of the directors who made at least 2 movies, with their number of movies and the average rating of their movies.

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
Table directors (6 rows)
idnamecountry
1Nora EllisUK
2Paulo ReisBrazil
3Kenji MoriJapan
4Anna BergSweden
5Luc MartinFrance
6Sara DiazSpain
Show the hint

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

Query structure:

SELECT , COUNT(*), AVG()
FROM  
JOIN   ON  = 
GROUP BY 
HAVING COUNT(*) >= 
Show the solution
SELECT d.name, COUNT(*), AVG(m.rating)
FROM directors d
JOIN movies m ON m.director_id = d.id
GROUP BY d.id
HAVING COUNT(*) >= 2;

Expected result (4 rows):

nameCOUNT(*)AVG(m.rating)
Nora Ellis27.65
Paulo Reis26.949999999999999
Kenji Mori28.1
Luc Martin26.9

Exercise 5 · level 3

Show the title of the books borrowed at least twice, with their number of loans.

Table books (10 rows)
idtitleauthor_idgenrepagesyear
1Cold River1Novel3202011
2Salt Roads2Travel2102016
3The Glass Hive3Sci-Fi4122019
4Winter Ledger1Crime2882014
5Desert Letters2Novel3562020
6Small Engines4Sci-Fi1982022
7Harbor Lights5Novel4452008
8Night Garden3Crime3012017
9Paper Birds5Poetry962012
10Open Maps4Travel2402021
Table loans (12 rows)
idbook_idmember_idloan_datereturn_date
1112025-01-052025-01-19
2322025-01-102025-02-02
3512025-02-012025-02-10
4732025-02-03NULL
5342025-02-152025-03-01
6222025-03-022025-03-30
7852025-03-05NULL
8132025-03-102025-03-18
9612025-03-202025-04-15
10352025-04-01NULL
11942025-04-052025-04-12
121022025-04-082025-04-20
Show the hint

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

Query structure:

SELECT , COUNT(*)
FROM  
JOIN   ON  = 
GROUP BY 
HAVING COUNT(*) >= 
Show the solution
SELECT b.title, COUNT(*)
FROM books b
JOIN loans l ON l.book_id = b.id
GROUP BY b.id
HAVING COUNT(*) >= 2;

Expected result (2 rows):

titleCOUNT(*)
Cold River2
The Glass Hive3

Exercise 6 · level 3

For each team, show its name and the number of goals scored at home, only for the teams that scored at least 3 goals at home.

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, HAVING.

Query structure:

SELECT , SUM()
FROM  
JOIN   ON  = 
GROUP BY 
HAVING SUM() >= 
Show the solution
SELECT t.name, SUM(m.home_goals)
FROM teams t
JOIN matches m ON m.home_id = t.id
GROUP BY t.id
HAVING SUM(m.home_goals) >= 3;

Expected result (2 rows):

nameSUM(m.home_goals)
Red Foxes3
Gold Hawks4

Exercise 7 · level 3

For each departure airport, show its code and the total number of seats sold, only for the airports with more than 250 seats sold in total.

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, HAVING.

Query structure:

SELECT , SUM()
FROM 
GROUP BY 
HAVING SUM() > 
Show the solution
SELECT origin, SUM(seats_sold)
FROM flights
GROUP BY origin
HAVING SUM(seats_sold) > 250;

Expected result (2 rows):

originSUM(seats_sold)
CDG605
MAD265

Exercise 8 · level 3

For each city, show the city and the total rain, only for the cities that received at least 5 mm in total.

Table readings (15 rows)
idcitydaytemp_maxtemp_minrain_mm
1Paris2025-07-0124150
2Paris2025-07-0227170
3Paris2025-07-0322164.5
4Paris2025-07-04191412
5Paris2025-07-0523130
6Lyon2025-07-0126160
7Lyon2025-07-0229180
8Lyon2025-07-0331190
9Lyon2025-07-0424178.5
10Lyon2025-07-0522153
11Marseille2025-07-0130210
12Marseille2025-07-0232220
13Marseille2025-07-0333230
14Marseille2025-07-0429210
15Marseille2025-07-0528200
Show the hint

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

Query structure:

SELECT , SUM()
FROM 
GROUP BY 
HAVING SUM() >= 
Show the solution
SELECT city, SUM(rain_mm)
FROM readings
GROUP BY city
HAVING SUM(rain_mm) >= 5;

Expected result (2 rows):

citySUM(rain_mm)
Lyon11.5
Paris16.5

Exercise 9 · level 3

Show the name of the hotels with at least 3 bookings (all rooms together), with their number of bookings.

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
Table bookings (11 rows)
idroom_idguest_idcheck_incheck_out
1212025-07-012025-07-04
2522025-07-022025-07-05
3832025-07-032025-07-06
4342025-07-052025-07-12
5652025-07-062025-07-08
6122025-07-082025-07-10
7932025-07-102025-07-11
8752025-07-112025-07-15
9412025-07-142025-07-16
10642025-07-152025-07-19
11652025-07-162025-07-18
Show the hint

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

Query structure:

SELECT , COUNT(*)
FROM  
JOIN   ON  = 
JOIN   ON  = 
GROUP BY 
HAVING COUNT(*) >= 
Show the solution
SELECT h.name, COUNT(*)
FROM hotels h
JOIN rooms r ON r.hotel_id = h.id
JOIN bookings b ON b.room_id = r.id
GROUP BY h.id
HAVING COUNT(*) >= 3;

Expected result (1 row):

nameCOUNT(*)
City Loft4

Exercise 10 · level 3

Show the name of the products ordered in at least 3 units in total, with the total quantity.

Table products (7 rows)
idnamecategoryprice
1Desk LampHome35
2Coffee MugKitchen12
3NotebookOffice6
4Office ChairOffice149
5KettleKitchen45
6CushionHome22
7StaplerOffice9
Table order_items (14 rows)
order_idproduct_idqty
111
122
241
335
321
451
562
511
624
633
741
761
852
821
Show the hint

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

Query structure:

SELECT , SUM()
FROM  
JOIN   ON  = 
GROUP BY 
HAVING SUM() >= 
Show the solution
SELECT p.name, SUM(oi.qty)
FROM products p
JOIN order_items oi ON oi.product_id = p.id
GROUP BY p.id
HAVING SUM(oi.qty) >= 3;

Expected result (4 rows):

nameSUM(oi.qty)
Coffee Mug8
Notebook8
Kettle3
Cushion3

Exercise 11 · level 3

Show the name of the projects with at least 2 finished tasks ('done'), with that number of tasks.

Table projects (4 rows)
idnameclientbudgetdeadline
1AtlasAcme200002025-06-30
2BeaconBolt120002025-05-15
3CometAcme80002025-04-30
4DeltaCyan150002025-07-31
Table tasks (10 rows)
idproject_iddev_idtitlehoursstatusdone_on
111Landing page12done2025-03-10
213Data model20done2025-03-20
312Login8doingNULL
424ETL job16done2025-04-02
525CI pipeline6done2025-03-15
631Dashboard14todoNULL
733Report10done2025-04-25
842API18doingNULL
945Monitoring9todoNULL
1044Forecast22done2025-05-05
Show the hint

Topics to use: WHERE (filters), COUNT, SUM, AVG, MIN, MAX, GROUP BY, JOIN, HAVING.

Query structure:

SELECT , COUNT(*)
FROM  
JOIN   ON  = 
WHERE  = 
GROUP BY 
HAVING COUNT(*) >= 
Show the solution
SELECT p.name, COUNT(*)
FROM projects p
JOIN tasks t ON t.project_id = p.id
WHERE t.status = 'done'
GROUP BY p.id
HAVING COUNT(*) >= 2;

Expected result (2 rows):

nameCOUNT(*)
Atlas2
Beacon2

Exercise 12 · level 3

For each label that appears at least 3 times, show the label, the number of transactions and their sum.

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, HAVING.

Query structure:

SELECT , COUNT(*), SUM()
FROM 
GROUP BY 
HAVING COUNT(*) >= 
Show the solution
SELECT label, COUNT(*), SUM(amount)
FROM transactions
GROUP BY label
HAVING COUNT(*) >= 3;

Expected result (3 rows):

labelCOUNT(*)SUM(amount)
groceries3-255
rent3-2350
salary49100

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 20 “HAVING” questions