SQL IN and BETWEEN: lists and ranges
When a WHERE condition starts piling up ORs (“Lyon or Marseille or Nantes…”) or framing a value (“between 10 and 50”), two operators make the query shorter and easier to read: IN and BETWEEN.
Syntax
WHERE column IN (value1, value2, …)
WHERE column BETWEEN minimum AND maximumHow it works
IN: one value from a list
ville IN ('Lyon', 'Marseille') is strictly equivalent to ville = 'Lyon' OR ville = 'Marseille'. The list can hold as many values as needed, and can even be replaced by a subquery that returns a list.
NOT IN does the opposite: it keeps the rows whose value matches none of the listed values.
BETWEEN: a range
prix BETWEEN 10 AND 50 is equivalent to prix >= 10 AND prix <= 50: both bounds are included. It also works with dates written as YYYY-MM-DD and with text (alphabetical order).
Diagram
| prenom | ville |
|---|---|
| Léa | Lyon |
| Hugo | Paris |
| Adam | Nantes |
| Zoé | Lille |
| prenom | ville |
|---|---|
| Léa | Lyon |
| Adam | Nantes |
Simplified diagram to show the idea. The examples below use the real SQL Quest practice database (a fictional online shop: see the full schema).
Glossary: the sample database uses French names
clients- customers
commandes- orders
lignes_commande- order lines
produits- products
fournisseurs- suppliers
employes- employees
prenom / nom- first name / last name (or name)
ville- city
pays- country
prix- price
prix_unitaire- unit price
quantite- quantity
categorie- category
date_commande- order date
poste- job title
departement- department
salaire- salary
statut- status: livrée = delivered, expédiée = shipped, en cours = in progress, annulée = cancelled
Examples with their results
1. IN with a list of cities
The customers from Lyon or Marseille.
SELECT prenom, nom, ville
FROM clients
WHERE ville IN ('Lyon', 'Marseille')
LIMIT 8; Try it in the sandbox| prenom | nom | ville |
|---|---|---|
| Léa | Laurent | Marseille |
| Alice | Faure | Lyon |
| Chloé | Fontaine | Lyon |
| Yanis | Chevalier | Marseille |
| Manon | Fontaine | Marseille |
| Sarah | Michel | Lyon |
| Lucas | Martin | Marseille |
| Louis | Moreau | Marseille |
8 rows in total
2. BETWEEN on prices
The products between €10 and €20, bounds included.
SELECT nom, prix
FROM produits
WHERE prix BETWEEN 10 AND 20; Try it in the sandbox| nom | prix |
|---|---|
| Clavier mécanique | 15 |
| Coussin | 10.86 |
| Vase céramique | 18.35 |
| Ballon de foot | 14.68 |
| Livre de cuisine | 18.67 |
5 rows in total
3. BETWEEN on dates
The orders placed in January 2025.
SELECT id, date_commande, statut
FROM commandes
WHERE date_commande BETWEEN '2025-01-01' AND '2025-01-31'
LIMIT 8; Try it in the sandbox| id | date_commande | statut |
|---|---|---|
| 72 | 2025-01-14 | en cours |
| 102 | 2025-01-16 | expédiée |
| 139 | 2025-01-09 | livrée |
| 225 | 2025-01-19 | expédiée |
| 248 | 2025-01-14 | livrée |
| 259 | 2025-01-01 | en cours |
| 273 | 2025-01-25 | expédiée |
| 367 | 2025-01-02 | livrée |
8 rows in total
4. NOT IN
The orders that are neither delivered (livrée) nor shipped (expédiée).
SELECT statut, COUNT(*) AS nb
FROM commandes
WHERE statut NOT IN ('livrée', 'expédiée')
GROUP BY statut; Try it in the sandbox| statut | nb |
|---|---|
| annulée | 92 |
| en cours | 109 |
2 rows in total
Common mistakes
Swapping the bounds of BETWEEN
The smallest value must come first. BETWEEN 50 AND 10 never returns anything.
SELECT * FROM produits WHERE prix BETWEEN 50 AND 10;SELECT * FROM produits WHERE prix BETWEEN 10 AND 50;Forgetting the parentheses of IN
The IN list must always be in parentheses, with commas.
SELECT * FROM clients WHERE ville IN 'Lyon', 'Lille';SELECT * FROM clients WHERE ville IN ('Lyon', 'Lille');NOT IN with a NULL value
If the list (or the subquery) of NOT IN contains a NULL, the condition is never true and the query returns nothing. Prefer NOT EXISTS with a subquery.
SELECT * FROM clients WHERE age NOT IN (20, NULL);SELECT * FROM clients WHERE age NOT IN (20) AND age IS NOT NULL;Exercises
Try writing the query yourself before opening the solution.
Challenge 1. Show the products of the “Livres” (books) and “Alimentation” (food) categories.
Show a solution
SELECT nom, categorie, prix FROM produits WHERE categorie IN ('Livres', 'Alimentation');| nom | categorie | prix |
|---|---|---|
| Roman policier | Livres | 8.03 |
| Guide SQL | Livres | 25.25 |
| BD aventure | Livres | 9.98 |
| Livre de cuisine | Livres | 18.67 |
| Manga tome 1 | Livres | 28.92 |
| Café en grains | Alimentation | 21.79 |
| Thé vert | Alimentation | 35.83 |
| Chocolat noir | Alimentation | 22.31 |
10 rows in total · 8 shown
Try it in the sandboxChallenge 2. Show the employees whose salary is between €3,000 and €4,000.
Show a solution
SELECT prenom, nom, salaire FROM employes WHERE salaire BETWEEN 3000 AND 4000;| prenom | nom | salaire |
|---|---|---|
| Nathan | Martin | 3,400 |
| Alice | Lefebvre | 3,800 |
| Hugo | Bonnet | 3,300 |
| Chloé | Blanc | 3,800 |
| Inès | Faure | 3,600 |
| Chloé | Roux | 3,600 |
| Léa | Garcia | 3,500 |
| Louis | Lambert | 3,900 |
8 rows in total
Try it in the sandboxThe interactive exercises are checked automatically, with a tutor who explains your mistakes. A short lesson stays next to the editor.
Frequently asked questions
Does BETWEEN include the bounds?
Yes. BETWEEN 10 AND 20 keeps 10 and 20. To exclude a bound, use > and < instead.
Is IN faster than a series of ORs?
The result is identical and most databases run it the same way. IN is mostly easier to read.
Can I put a query inside IN?
Yes: WHERE id IN (SELECT client_id FROM commandes) keeps the customers who placed at least one order. That is a subquery.