Le problème : « les clients actifs de l'Est qui n'ont pas d'email »
Le marketing débarque avec une demande tordue : « Je veux les clients de Besançon ou Dijon, inscrits entre janvier et mars, dont le nom commence par un M, et qui n'ont pas renseigné de téléphone. »
Une seule condition WHERE prix > 20 ne suffit plus. Il faut combiner plusieurs conditions avec précision. C'est tout l'art de la clause WHERE avancée. On travaille sur la table clients :
id | nom | ville | telephone | inscrit_le
---+--------------+-----------+-------------+-----------
1 | Marie Dupont | Besançon | 0601020304 | 2026-01-15
2 | Karim Benali | Dijon | NULL | 2026-02-03
3 | Léa Martin | Lyon | 0708091011 | 2026-03-21
Comparer et combiner : =, <, >, AND, OR, NOT
Les opérateurs de comparaison de base : = (égal), <> ou != (différent), <, >, <=, >=.
Pour combiner plusieurs conditions, on utilise AND (toutes vraies), OR (au moins une vraie) et NOT (l'inverse) :
SELECT nom, ville
FROM clients
WHERE ville = 'Besançon' AND telephone IS NOT NULL;
Les parenthèses quand vous mélangez AND et OR. WHERE a = 1 OR a = 2 AND b = 3 ne fait pas ce que vous croyez : AND est prioritaire sur OR. Mettez des parenthèses explicites : WHERE (a = 1 OR a = 2) AND b = 3.
LIKE, IN, BETWEEN, IS NULL
LIKE cherche un motif dans du texte. Le caractère % remplace « n'importe quelle suite de caractères », _ remplace exactement un caractère :
On veut récupérer les clients qui n'ont pas renseigné d'email (colonne email contenant NULL). Quelqu'un écrit SELECT * FROM clients WHERE email = NULL. Avant de dérouler : cette requête renvoie-t-elle les clients sans email, et que renvoie-t-elle réellement ?
Voir la réponse
Non, cette requête ne renvoie aucune ligne, jamais. En SQL, NULL signifie « valeur inconnue/absente » : toute comparaison avec NULL via =, !=, < ou > produit le résultat NULL (ni vrai ni faux), et la ligne n'est donc jamais retenue. Même NULL = NULL n'est pas vrai. Pour tester l'absence de valeur, il faut utiliser les opérateurs dédiés : WHERE email IS NULL (et IS NOT NULL pour l'inverse). C'est l'un des pièges les plus fréquents en SQL : un = NULL qui renvoie silencieusement zéro résultat, sans erreur.
SELECT nom
FROM clients
WHERE nom LIKE 'M%'; -- les noms commençant par M
IN teste l'appartenance à une liste, bien plus lisible qu'une cascade de OR :
SELECT nom, ville
FROM clients
WHERE ville IN ('Besançon', 'Dijon');
BETWEEN teste un intervalle (bornes incluses), idéal pour les dates et les nombres :
SELECT nom, inscrit_le
FROM clients
WHERE inscrit_le BETWEEN '2026-01-01' AND '2026-03-31';
NULL n'est pas une valeur ordinaire. telephone = NULL ne marche jamais, même si la valeur est vide. NULL veut dire « inconnu », et rien n'est égal à l'inconnu. Utilisez IS NULL ou IS NOT NULL :
SELECT nom FROM clients WHERE telephone IS NULL;
Trier sur plusieurs colonnes
ORDER BY accepte plusieurs colonnes, séparées par des virgules. Le tri se fait de gauche à droite : d'abord par la première colonne, puis la deuxième départage les égalités.
SELECT nom, ville, inscrit_le
FROM clients
ORDER BY ville ASC, inscrit_le DESC;
Ici, les clients sont d'abord groupés par ville (de A à Z), et à l'intérieur de chaque ville, du plus récemment inscrit au plus ancien.
Sans ORDER BY, l'ordre des lignes n'est jamais garanti. Même si une requête semble toujours renvoyer les lignes dans le même ordre (ordre d'insertion, par exemple), ce n'est qu'un effet de bord du moteur : il peut changer après un index, une mise à jour ou une autre version. Si l'ordre compte, écrivez-le explicitement avec ORDER BY, jamais en supposant un ordre « naturel ».
La requête finale du marketing combine tout :
SELECT nom, ville
FROM clients
WHERE ville IN ('Besançon', 'Dijon')
AND inscrit_le BETWEEN '2026-01-01' AND '2026-03-31'
AND nom LIKE 'M%'
AND telephone IS NULL
ORDER BY ville, nom;
The problem: "active eastern customers with no email"
Marketing shows up with a tricky request: "I want customers from Besancon or Dijon, registered between January and March, whose name starts with an M, and who have no phone number."
A single WHERE prix > 20 condition is no longer enough. You need to combine several conditions precisely. This is the whole art of the advanced WHERE clause. We work on the clients table:
id | nom | ville | telephone | inscrit_le
---+--------------+-----------+-------------+-----------
1 | Marie Dupont | Besancon | 0601020304 | 2026-01-15
2 | Karim Benali | Dijon | NULL | 2026-02-03
3 | Lea Martin | Lyon | 0708091011 | 2026-03-21
Compare and combine: =, <, >, AND, OR, NOT
The basic comparison operators: = (equal), <> or != (not equal), <, >, <=, >=.
To combine conditions, use AND (all true), OR (at least one true) and NOT (the opposite):
SELECT nom, ville
FROM clients
WHERE ville = 'Besancon' AND telephone IS NOT NULL;
Watch out for parentheses when mixing AND and OR. WHERE a = 1 OR a = 2 AND b = 3 does not do what you think: AND has priority over OR. Use explicit parentheses: WHERE (a = 1 OR a = 2) AND b = 3.
LIKE, IN, BETWEEN, IS NULL
LIKE searches for a pattern in text. The % character replaces "any sequence of characters", _ replaces exactly one character:
We want to retrieve customers who have no email address (column email containing NULL). Someone writes SELECT * FROM clients WHERE email = NULL. Before you scroll: does this query return customers with no email, and what does it actually return?
See the answer
No, this query returns no rows at all, ever. In SQL, NULL means "unknown/absent value": any comparison with NULL using =, !=, < or > produces the result NULL (neither true nor false), so the row is never kept. Even NULL = NULL is not true. To test for the absence of a value, you must use the dedicated operators: WHERE email IS NULL (and IS NOT NULL for the opposite). This is one of the most common traps in SQL: a = NULL that silently returns zero results, with no error.
SELECT nom
FROM clients
WHERE nom LIKE 'M%'; -- names starting with M
IN tests membership in a list, far more readable than a cascade of OR:
SELECT nom, ville
FROM clients
WHERE ville IN ('Besancon', 'Dijon');
BETWEEN tests a range (bounds included), ideal for dates and numbers:
SELECT nom, inscrit_le
FROM clients
WHERE inscrit_le BETWEEN '2026-01-01' AND '2026-03-31';
NULL is not an ordinary value. telephone = NULL never works, even if the value is empty. NULL means "unknown", and nothing equals the unknown. Use IS NULL or IS NOT NULL:
SELECT nom FROM clients WHERE telephone IS NULL;
Sorting on several columns
ORDER BY accepts several columns, separated by commas. Sorting goes left to right: first by the first column, then the second breaks ties.
SELECT nom, ville, inscrit_le
FROM clients
ORDER BY ville ASC, inscrit_le DESC;
Here, customers are first grouped by city (A to Z), and within each city, from most recently registered to oldest.
Without ORDER BY, the order of rows is never guaranteed. Even if a query seems to always return rows in the same order (insertion order, for example), that is just a side effect of the engine: it can change after an index, an update or another version. If order matters, write it explicitly with ORDER BY, never by assuming a "natural" order.
The final marketing query combines everything:
SELECT nom, ville
FROM clients
WHERE ville IN ('Besancon', 'Dijon')
AND inscrit_le BETWEEN '2026-01-01' AND '2026-03-31'
AND nom LIKE 'M%'
AND telephone IS NULL
ORDER BY ville, nom;
À vous d'essayer, la base est déjà remplie. Avant de cliquer sur Exécuter, prédisez : combien de lignes ressortent, lesquelles et dans quel ordre, sachant qu'on garde Dijon avec un montant supérieur à 100, trié par montant décroissant ? Lancez ensuite et comparez.
CREATE TABLE commandes (id INTEGER, client TEXT, ville TEXT, montant REAL);
INSERT INTO commandes VALUES (1, 'Alice Durand', 'Dijon', 120.50);
INSERT INTO commandes VALUES (2, 'Marc Petit', 'Lyon', 45.00);
INSERT INTO commandes VALUES (3, 'Léa Martin', 'Dijon', 230.90);
INSERT INTO commandes VALUES (4, 'Paul Roux', 'Lyon', 89.00);
SELECT client, montant
FROM commandes
WHERE ville = 'Dijon' AND montant > 100
ORDER BY montant DESC;
🎯 Pratique
S'entraîner (clique pour ouvrir) :
✨ Prompt IA
Faites traduire une demande métier en SQL par l'IA, puis vérifiez les pièges (NULL, parenthèses AND/OR) :
Table clients (id, nom, ville, telephone, inscrit_le). Écris une requête SQL pour les clients de Besançon ou Dijon, inscrits entre le 1er janvier et le 31 mars 2026, dont le nom commence par M et qui n'ont pas de téléphone. Trie par ville puis par nom. Attention : gère correctement le test du téléphone absent (NULL) et mets les parenthèses nécessaires entre les AND et OR. Explique pourquoi.
💬 Ré-explique sans regarder
Sans relire la réponse de l'IA : pourquoi WHERE telephone = NULL ne renvoie-t-il jamais la moindre ligne, et qu'écris-tu à la place ?
NULL signifie « valeur inconnue », pas « vide ». En SQL, toute comparaison avec NULL via = ou <> renvoie UNKNOWN, jamais TRUE : la ligne est donc exclue. On teste l'absence de valeur avec IS NULL (et la présence avec IS NOT NULL), les seuls opérateurs conçus pour ça.Écrivez une requête qui renvoie les clients de Dijon OU Lyon (utilisez IN) dont le téléphone est renseigné (utilisez IS NOT NULL), triés par nom. Pensez à WHERE, IN, IS NOT NULL et ORDER BY.
⚖️ Juge le code de l'IA
Tu as demandé « les clients de Dijon ou Lyon dont le montant dépasse 100 ». L'IA propose ce code. Ton rôle de relecteur : l'accepter tel quel ou le rejeter, et dire pourquoi.
SELECT client, ville, montant
FROM commandes
WHERE ville = 'Dijon' OR ville = 'Lyon' AND montant > 100;
AND est prioritaire sur OR, donc la base lit ceci comme ville = 'Dijon' OR (ville = 'Lyon' AND montant > 100) : tous les clients de Dijon ressortent, même ceux à 10 €, alors que le filtre montant ne s'applique qu'à Lyon. Le réflexe pro : parenthéser l'alternative de villes, WHERE (ville = 'Dijon' OR ville = 'Lyon') AND montant > 100 (ou mieux, ville IN ('Dijon','Lyon')).🧠 Rappel libre
Sans remonter dans la leçon : que filtre WHERE nom LIKE 'M%', et dans ORDER BY ville ASC, inscrit_le DESC que fait la deuxième colonne ?
LIKE 'M%' garde les noms qui commencent par M : le % remplace n'importe quelle suite de caractères (alors que '%M' filtrerait ceux qui finissent par M). Dans ORDER BY ville ASC, inscrit_le DESC, le tri se fait d'abord par ville de A à Z ; la deuxième colonne, inscrit_le DESC, ne sert qu'à départager les lignes d'une même ville, du plus récent au plus ancien.Filtrer et trier, c'est répondre ligne par ligne. La prochaine étape change d'échelle : compter, totaliser et faire des moyennes par groupe avec COUNT, SUM, AVG, GROUP BY et HAVING.
Leçon 4 : Agréger et regrouper →