SQL exercises with solutions: UNION, INTERSECT, EXCEPT

SQL exercises with solutions: UNION, INTERSECT, EXCEPT

Updated on

UNION merges two results (no duplicates), INTERSECT keeps what they share, EXCEPT removes the second from the first. These 11 exercises range from level 4 to level 4; they use the syntax of SQLite, SpeedQL’s SQL engine.

Tip: Both queries must return the same number of columns.

Read the “UNION, INTERSECT, EXCEPT” card in the cheat sheet

Exercise 1 · level 4

Show the list, without duplicates, of all the members of both clubs (tables chess and music). Use UNION.

Table chess (4 rows)
member
Alice
Bob
Claire
David
Table music (4 rows)
member
Claire
David
Emma
Farid
Show the hint

Topics to use: UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM 
UNION SELECT 
FROM 
Show the solution
SELECT member
FROM chess
UNION SELECT member
FROM music;

Expected result (6 rows):

member
Alice
Bob
Claire
David
Emma
Farid

Exercise 2 · level 4

Show the members who belong to both clubs at the same time. Use INTERSECT.

Table chess (4 rows)
member
Alice
Bob
Claire
David
Table music (4 rows)
member
Claire
David
Emma
Farid
Show the hint

Topics to use: UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM 
INTERSECT SELECT 
FROM 
Show the solution
SELECT member
FROM chess
INTERSECT SELECT member
FROM music;

Expected result (2 rows):

member
Claire
David

Exercise 3 · level 4

Show the members of the chess club who are not in the music club. Use EXCEPT.

Table chess (4 rows)
member
Alice
Bob
Claire
David
Table music (4 rows)
member
Claire
David
Emma
Farid
Show the hint

Topics to use: UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM 
EXCEPT SELECT 
FROM 
Show the solution
SELECT member
FROM chess
EXCEPT SELECT member
FROM music;

Expected result (2 rows):

member
Alice
Bob

Exercise 4 · level 4

Show the genres in which every movie has a rating of at least 7: take the genres that have a movie rated at least 7, except those that have a movie rated below 7. Use EXCEPT.

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: WHERE (filters), UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM 
WHERE  >= 
EXCEPT SELECT 
FROM 
WHERE  < 
Show the solution
SELECT genre
FROM movies
WHERE rating >= 7
EXCEPT SELECT genre
FROM movies
WHERE rating < 7;

Expected result (2 rows):

genre
Drama
Sci-Fi

Exercise 5 · level 4

Show the title of the books borrowed both by a member from Paris and by a member from Lille. Use INTERSECT.

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
Table members (6 rows)
idnamecityjoined
1LenaLyon2022-01-15
2MarcParis2021-06-03
3NadiaLyon2023-03-20
4OscarLille2020-11-11
5PaulaParis2024-02-01
6QuentinNantes2023-09-09
Show the hint

Topics to use: WHERE (filters), JOIN, UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM  
JOIN   ON  = 
JOIN   ON  = 
WHERE  = 
INTERSECT SELECT 
FROM  
JOIN   ON  = 
JOIN   ON  = 
WHERE  = 
Show the solution
SELECT b.title
FROM books b
JOIN loans l ON l.book_id = b.id
JOIN members m ON m.id = l.member_id
WHERE m.city = 'Paris'
INTERSECT SELECT b.title
FROM books b
JOIN loans l ON l.book_id = b.id
JOIN members m ON m.id = l.member_id
WHERE m.city = 'Lille';

Expected result (1 row):

title
The Glass Hive

Exercise 6 · level 4

Show, without duplicates, the name of the teams that won at least one match, at home or away. Use UNION.

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: WHERE (filters), JOIN, UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM  
JOIN   ON  = 
WHERE  > 
UNION SELECT 
FROM  
JOIN   ON  = 
WHERE  > 
Show the solution
SELECT t.name
FROM teams t
JOIN matches m ON m.home_id = t.id
WHERE m.home_goals > m.away_goals
UNION SELECT t.name
FROM teams t
JOIN matches m ON m.away_id = t.id
WHERE m.away_goals > m.home_goals;

Expected result (4 rows):

name
Blue Owls
Gold Hawks
Grey Wolves
Red Foxes

Exercise 7 · level 4

Show the arrival cities served by both SkyJet and AirNova. Use INTERSECT.

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
Table airports (7 rows)
codecitycountry
CDGParisFrance
LYSLyonFrance
MADMadridSpain
LISLisbonPortugal
FCORomeItaly
BERBerlinGermany
NCENiceFrance
Show the hint

Topics to use: WHERE (filters), JOIN, UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM  
JOIN   ON  = 
WHERE  = 
INTERSECT SELECT 
FROM  
JOIN   ON  = 
WHERE  = 
Show the solution
SELECT a.city
FROM flights f
JOIN airports a ON a.code = f.dest
WHERE f.airline = 'SkyJet'
INTERSECT SELECT a.city
FROM flights f
JOIN airports a ON a.code = f.dest
WHERE f.airline = 'AirNova';

Expected result (2 rows):

city
Madrid
Paris

Exercise 8 · level 4

Show the days on which it rained both in Paris and in Lyon. Use INTERSECT.

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: WHERE (filters), UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM 
WHERE  =  AND  > 
INTERSECT SELECT 
FROM 
WHERE  =  AND  > 
Show the solution
SELECT day
FROM readings
WHERE city = 'Paris' AND rain_mm > 0
INTERSECT SELECT day
FROM readings
WHERE city = 'Lyon' AND rain_mm > 0;

Expected result (1 row):

day
2025-07-04

Exercise 9 · level 4

Using EXCEPT, show the countries of the guests, except those of the guests who stayed in a hotel in Paris.

Table guests (6 rows)
idnamecountry
1Ana SilvaPortugal
2Ben FordUSA
3Chen LiChina
4Dana WeissGermany
5Emma RoyFrance
6Farid NasserMorocco
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
Table rooms (10 rows)
idhotel_idtypeprice
11single70
21double95
32double130
42suite210
53single110
63double150
74double65
85double260
95suite480
105single190
Table hotels (5 rows)
idnamecitystars
1Seaside InnNice3
2Alpine LodgeAnnecy4
3City LoftParis4
4Old MillBordeaux2
5Grand PalaceParis5
Show the hint

Topics to use: WHERE (filters), JOIN, UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM 
EXCEPT SELECT 
FROM  
JOIN   ON  = 
JOIN   ON  = 
JOIN   ON  = 
WHERE  = 
Show the solution
SELECT country
FROM guests
EXCEPT SELECT g.country
FROM guests g
JOIN bookings b ON b.guest_id = g.id
JOIN rooms r ON r.id = b.room_id
JOIN hotels h ON h.id = r.hotel_id
WHERE h.city = 'Paris';

Expected result (2 rows):

country
Morocco
Portugal

Exercise 10 · level 4

Using INTERSECT, show the name of the customers who ordered both an Office product and a Kitchen product.

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), JOIN, UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM  
JOIN   ON  = 
JOIN   ON  = 
JOIN   ON  = 
WHERE  = 
INTERSECT SELECT 
FROM  
JOIN   ON  = 
JOIN   ON  = 
JOIN   ON  = 
WHERE  = 
Show the solution
SELECT c.name
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 p.category = 'Office'
INTERSECT SELECT c.name
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 p.category = 'Kitchen';

Expected result (2 rows):

name
Alba
Eva

Exercise 11 · level 4

Using UNION, show without duplicates the owners who received a salary (label 'salary') and those who have a savings account (kind 'savings').

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: WHERE (filters), JOIN, UNION, INTERSECT, EXCEPT.

Query structure:

SELECT 
FROM  
JOIN   ON  = 
WHERE  = 
UNION SELECT 
FROM 
WHERE  = 
Show the solution
SELECT a.owner
FROM accounts a
JOIN transactions t ON t.account_id = a.id
WHERE t.label = 'salary'
UNION SELECT owner
FROM accounts
WHERE kind = 'savings';

Expected result (4 rows):

owner
Alice
Bruno
Chloe
David

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 13 “UNION, INTERSECT, EXCEPT” questions