~/teaching · data analytics · séance 3, jeudi 7 janvier 2027
L'essentiel du SQL pour managers : lire une requête, contrôler une jointure, auditer le SQL écrit par une IA
Commencer la lecture par FROM, se demander ce qu'est une ligne, compter avant et après chaque jointure.
Résumé
C'est la troisième séance de Data Analytics for Business, un cours du M2 International Management de l'Université Catholique de Lille (2026-2027), dont les supports sont en anglais. Elle suit la séance sur les graphiques et la mise en récit et reprend le même cas, l'historique de commandes ouvert d'Olist, une place de marché brésilienne, cette fois interrogé directement dans DuckDB, un moteur de base de données gratuit qui tourne dans le navigateur.
Il ne s'agit pas de faire des managers des analystes. Il s'agit de leur permettre de lire une requête (query), de juger si elle répond bien à la question posée et de vérifier un chiffre avant qu'il n'arrive sur une diapositive signée de leur nom. La séance va des tables et des clés aux six clauses d'une requête, puis aux jointures, où naissent la plupart des erreurs silencieuses, et se termine sur un réflexe qui compte chaque mois davantage : auditer le SQL écrit par un assistant IA avant de croire son résultat.
À la fin de la séance, on sait lire et écrire SELECT, WHERE, GROUP BY et JOIN sur plusieurs tables, expliquer comment une jointure peut dupliquer ou perdre des lignes sans bruit, et lancer les quelques contrôles qui le détectent. La suite, l'après-midi même, est l'analyse prédictive pour décideurs.
Plan du deck
- Retour en classe, et pourquoi le SQL. Un court rappel des séances précédentes, puis quatre raisons pour un manager de savoir lire une requête.
- Comment les tables s'emboîtent. Clés primaires et étrangères, ce que représente une ligne de chaque table, et un piège caché dans la table des clients.
- Démonstration en direct dans DuckDB. Du tableau croisé dynamique à la requête, puis trois questions du cas tapées et exécutées en direct.
- À vous de jouer. Trois exercices en binôme, du texte à trous au classement sur trois tables, sans assistant IA.
- Les jointures, et comment elles mentent. INNER et LEFT JOIN, des lignes qui se multiplient, des lignes qui disparaissent, et le comptage qui repère les deux.
- Trouver le bug. Des requêtes du genre de celles qu'écrit un assistant, chacune avec une erreur classique, et une grille en quatre points.
- Synthèse. Quatre réflexes à garder et un constat SQL vérifié à ajouter au dossier du groupe.
Pourquoi un manager lit du SQL
La plupart des systèmes de gestion rangent leurs données dans des tables, et le SQL est la langue qui leur pose des questions précises.
Ventes, stocks, livraisons, dossiers RH : ces données vivent en général dans des tables relationnelles. Un manager n'a pas besoin d'écrire couramment du SQL, mais savoir le lire rapporte de quatre façons. On peut vérifier un chiffre avant de le présenter. On peut demander avec précision : quelles lignes, quel filtre, compté comment. On n'attend plus dans la file d'un analyste pour un simple comptage. Et l'on peut auditer ce qu'écrit un assistant IA, qui produit désormais du SQL plausible en quelques secondes.
C'est cette dernière raison qui structure la séance. Une requête qui tourne n'est pas forcément une requête juste : les erreurs dangereuses donnent un résultat d'apparence raisonnable. Apprendre à lire une requête, en commençant par la bonne clause, est ce qui permet de les remarquer.
Remarque (L'outil). La séance utilise DuckDB dans le navigateur (shell.duckdb.org) : rien à installer, un script de préparation crée les neuf tables du cas, et SHOW TABLES, DESCRIBE et LIMIT 5 suffisent pour faire le tour avant d'écrire quoi que ce soit.
Clés et granularité : comment les tables s'emboîtent
Avant de compter quoi que ce soit, il faut savoir ce que représente une ligne de chaque table et quelle colonne la relie aux autres.
Une clé primaire (primary key) identifie chaque ligne d'une table une seule fois, comme order_id dans la table des commandes. Une clé étrangère (foreign key) est une colonne qui pointe vers une ligne d'une autre table, comme l'identifiant client inscrit sur chaque commande. Une jointure ne fait rien d'autre que suivre ce pointeur.
La granularité (grain) d'une table, c'est ce que représente une de ses lignes. Dans le cas Olist, orders compte une ligne par commande et se trouve au centre du schéma. Trois tables peuvent contenir plusieurs lignes pour une même commande : les articles, les paiements et les avis. C'est là que les jointures dérapent. La table de géolocalisation se joint sur un préfixe de code postal présent sur de nombreuses lignes ; il faut donc la ramener à une ligne par préfixe avant toute jointure.
Le support signale un piège dans la table des clients : dans ce jeu de données, l'identifiant client porté par une commande désigne la fiche client de cette commande, tandis que la personne derrière plusieurs commandes a un identifiant distinct. Dès que la question porte sur des personnes, il faut vérifier quelle colonne désigne vraiment une personne.
| Table | Une ligne, c'est | Lignes par commande |
|---|---|---|
| orders | une commande | exactement une |
| customers | la fiche client d'une commande | exactement une |
| order_items | un article d'une commande | une ou plusieurs |
| payments | un paiement d'une commande | un ou plusieurs |
| reviews | un avis sur une commande | zéro, un ou plusieurs |
Anatomie d'une requête, et l'ordre dans lequel elle s'exécute
Une requête, c'est un tableau croisé dynamique mis par écrit : choisir la table, filtrer, regrouper, mesurer, trier.
Chaque choix fait à la souris dans un tableau croisé dynamique (pivot table) porte un nom en SQL. Les données source, c'est FROM, le filtre WHERE, les lignes du tableau GROUP BY, les valeurs sont des agrégats (aggregates) comme COUNT, SUM ou AVG placés dans SELECT, et le tri ORDER BY. La différence : le SQL garde chaque choix sous forme de texte, qu'un collègue peut relancer le mois suivant et relire ligne à ligne, alors qu'un tableau construit à la souris ne laisse aucune trace à auditer.
Une requête compte jusqu'à six clauses, toujours écrites dans le même ordre. Seules SELECT et FROM sont obligatoires. Les valeurs texte se mettent entre apostrophes ; entre guillemets doubles, DuckDB cherche une colonne de ce nom. Voici un exemple dans l'esprit des démonstrations, écrit pour ce résumé.
SELECT s.seller_state,
COUNT(*) AS n_items,
ROUND(AVG(i.freight_value), 2) AS avg_freight
FROM order_items AS i
JOIN sellers AS s ON s.seller_id = i.seller_id
WHERE i.price >= 100
GROUP BY s.seller_state
ORDER BY avg_freight DESC
LIMIT 5;
Comment la lire : dans l'ordre où travaille la base, pas dans l'ordre d'écriture
- FROM et les jointures. On prend les articles commandés et l'on rattache à chacun son vendeur. C'est ce qui décide quelles lignes existent.
- WHERE. On ne garde que les articles à 100 ou plus.
- GROUP BY. On forme un groupe par État du vendeur.
- SELECT. Dans chaque groupe, on compte les lignes et l'on fait la moyenne des frais de port.
- ORDER BY, puis LIMIT. On trie par cette moyenne, la plus forte d'abord, et l'on garde cinq lignes.
Cet ordre logique est le principe de lecture central du support : pour comprendre la requête de quelqu'un d'autre, et surtout celle d'un assistant, on commence par FROM et les jointures, jamais par SELECT. Le moteur peut organiser le travail réel autrement, mais le résultat se comporte comme s'il suivait cet ordre.
Les démonstrations répondent ensuite à trois questions du cas : les commandes par statut, les livraisons en retard parmi les commandes passées en 2017, et les commandes par État du client. La deuxième réécrit la règle de retard de la première séance avec CASE WHEN, qui inscrit une étiquette sur chaque ligne, et EXTRACT, qui extrait l'année d'une date. Elle filtre à la fois sur le statut et sur la présence d'une date de livraison, car ce sont deux colonnes distinctes et rien ne garantit qu'elles concordent. La troisième est une première jointure : l'État se trouve dans la table des clients, pas dans celle des commandes.
INNER JOIN et LEFT JOIN : c'est la question qui tranche
La jointure interne ne garde que les lignes qui trouvent un partenaire ; la jointure gauche garde toutes celles de la première table.
Un JOIN simple est une jointure interne (INNER JOIN) : une ligne ne survit que si elle trouve une correspondance des deux côtés. Une jointure externe gauche (LEFT JOIN) garde toutes les lignes de la table écrite après FROM et remplit les colonnes de l'autre table avec NULL quand il n'y a pas de correspondance. Aucune n'est meilleure en général. Une note moyenne sur les commandes ayant reçu un avis se calcule très bien avec une jointure interne ; la part des commandes qui ont reçu un avis exige une jointure gauche, sinon les commandes sans avis n'entrent même pas dans le dénominateur.
| Commande | A un avis | INNER JOIN | LEFT JOIN |
|---|---|---|---|
| o1 | oui | gardée | gardée |
| o2 | non | perdue | gardée, colonnes d'avis à NULL |
| o3 | oui | gardée | gardée |
| Lignes renvoyées | 2 | 3 |
Une seconde façon, plus sournoise, de perdre des lignes tient aux filtres. Après une jointure gauche, une condition sur la table de droite placée dans WHERE est testée sur chaque ligne ; une valeur manquante (NULL) échoue à toute comparaison, et les lignes sans partenaire disparaissent : la jointure gauche est redevenue, sans bruit, une jointure interne. Placée dans ON, la même condition décide seulement quelles lignes partenaires sont rattachées, et toutes les lignes de la première table restent dans le résultat.
Remarque (Règle pratique). Avec un LEFT JOIN, les conditions sur la table de droite vont dans ON, pas dans WHERE.
Quand une jointure multiplie les lignes
Joindre deux tables qui ont chacune plusieurs lignes par commande associe chaque ligne à toutes les autres, et les sommes gonflent sans bruit.
Prenons une commande avec deux articles et deux paiements, et joignons les articles aux paiements sur l'identifiant de commande. Le moteur associe chaque article à chaque paiement : la commande occupe désormais quatre lignes et chaque paiement apparaît deux fois. Toute somme des paiements sur ces lignes est trop élevée, et rien ne prévient. C'est la démultiplication des lignes (fan-out).
L'erreur est difficile à voir parce que la plupart des commandes n'ont qu'un article et un paiement : seule une partie du total est gonflée. Le résultat est faux sans être absurde, et c'est précisément pour cela que de tels chiffres finissent dans des rapports.
Compter avant, compter après
Le contrôle le moins coûteux de la séance prend dix secondes. Dans la table des commandes, le nombre de lignes est égal au nombre de commandes distinctes. On relance les deux mêmes comptages après la jointure, et l'on compare.
SELECT COUNT(*) AS n_rows,
COUNT(DISTINCT o.order_id) AS n_orders
FROM orders AS o
JOIN payments AS p ON p.order_id = o.order_id;
| Ce que l'on voit | Ce que cela signifie |
|---|---|
| Les deux comptages inchangés | Toujours une ligne par commande : on peut additionner |
| Plus de lignes, autant de commandes | Des lignes se sont multipliées : les sommes sont gonflées |
| Moins de commandes distinctes | Des commandes sans partenaire ont été perdues |
Les deux alarmes peuvent sonner dans la même jointure. Le remède, quand on a besoin ensemble de deux tables à plusieurs lignes par commande, est de résumer, puis joindre : ramener d'abord chaque table à une ligne par commande, puis joindre les résumés, qui se correspondent un pour un. WITH donne un nom à chaque petite requête pour l'utiliser comme une table, ce qu'on appelle une expression de table commune (CTE). Un manager n'a pas à l'écrire couramment, mais il doit la reconnaître, et la demander quand on joint articles et paiements sans précaution.
WITH per_order_items AS ( SELECT order_id, COUNT(*) AS items FROM order_items GROUP BY order_id ), per_order_score AS ( SELECT order_id, AVG(review_score) AS score FROM reviews GROUP BY order_id ) SELECT i.items, COUNT(*) AS n_orders, ROUND(AVG(s.score), 2) AS avg_score FROM per_order_items AS i JOIN per_order_score AS s ON s.order_id = i.order_id GROUP BY i.items ORDER BY i.items;
Auditer le SQL écrit par un assistant IA
Les erreurs qui comptent empêchent rarement une requête de tourner : le résultat d'un assistant se vérifie, il ne suffit pas de l'exécuter.
La dernière partie pratique montre trois courtes requêtes du genre de celles qu'un assistant écrit à partir d'une demande floue, chacune cachant une erreur classique. En binôme, les étudiants disent quelle ligne est fausse et si l'erreur pousse le résultat vers le haut ou vers le bas ; décider du sens oblige à comprendre l'erreur plutôt qu'à seulement la repérer. Le support généralise ensuite la leçon en quatre contrôles, à mener dans cet ordre.
Quatre contrôles avant de se fier à une requête
- Granularité. Que représente une ligne avant et après chaque jointure ? On compare le nombre de lignes au nombre de clés distinctes.
- Valeurs manquantes et filtres. On regarde les dates vides, le statut des enregistrements et la période couverte ; on donne aux valeurs manquantes une étiquette à part pour voir combien il y en a.
- Un total déjà connu. On recalcule un total avec le moins de tables possible ; si la requête complexe ne tombe pas d'accord, on s'arrête et l'on cherche pourquoi.
- Faire expliquer. On demande à l'assistant d'expliquer chaque ligne et d'énoncer ses hypothèses, puis l'on vérifie ces affirmations dans les données, car une explication n'est pas une preuve.
Remarque (Protéger les données). On décrit les tables et l'on colle la requête, jamais les données. De vraies fiches clients n'ont rien à faire dans un prompt.
La séance se termine en demandant à chaque groupe d'ajouter à son dossier un constat auquel une requête répond mieux qu'un tableau croisé, énoncé en une phrase avec son chiffre et son périmètre, et de garder la requête et ses comptages de lignes, car quelqu'un demandera comment le chiffre a été obtenu.
À retenir
- On lit une requête à partir de FROM et de ses jointures, car ce sont elles qui décident quelles lignes existent avant tout comptage.
- On se demande ce qu'est une ligne avant de compter : un article n'est pas une commande, et une fiche n'est pas forcément une personne.
- On compte les lignes et les clés distinctes avant et après chaque jointure ; plus de lignes signifie des sommes gonflées, moins de clés des enregistrements perdus.
- On résume les tables à plusieurs lignes par clé avant de les joindre entre elles.
- Avec un LEFT JOIN, les conditions sur la table de droite vont dans ON, pas dans WHERE.
- Une requête qui tourne n'est pas encore une requête juste : on confronte le SQL écrit par une IA à un total déjà connu.
Glossaire
- clé primaire (primary key)
- Colonne dont la valeur identifie chaque ligne d'une table une seule fois.
- clé étrangère (foreign key)
- Colonne qui pointe vers une ligne d'une autre table, le chemin que suit une jointure.
- granularité (grain)
- Ce que représente une ligne d'une table ou d'un résultat : une commande, un article, un paiement.
- requête (query)
- Question posée à une base de données, écrite en SQL, que chacun peut relancer et relire.
- agrégats (aggregates)
- Fonctions comme COUNT, SUM ou AVG qui réduisent les lignes d'un groupe à une seule valeur.
- jointure interne (INNER JOIN)
- Jointure qui ne garde que les lignes ayant une correspondance des deux côtés.
- jointure externe gauche (LEFT JOIN)
- Jointure qui garde toutes les lignes de la première table et met NULL quand il manque la correspondance.
- valeur manquante (NULL)
- Absence de valeur ; toute comparaison avec elle est inconnue, ni vraie ni fausse.
- démultiplication des lignes (fan-out)
- Multiplication des lignes quand une jointure rencontre plusieurs correspondances de chaque côté.
- expression de table commune (CTE)
- Sous-requête nommée, déclarée avec WITH puis utilisée comme une table.
- tableau croisé dynamique (pivot table)
- Synthèse de tableur construite à la souris ; le SQL écrit les mêmes choix sous forme de texte.
Les autres fiches du cours
Un résumé public par deck, dans l'ordre du cours.
Précédent : Visualisation de données et récitSuivant : L'analyse prédictive pour décideurs
- Séance 1Des données à la décision
- Séance 2Visualisation de données et récit
- Séance 3L'essentiel du SQL pour managers
- Séance 4L'analyse prédictive pour décideurs
- Séance 5IA générative, RGPD et AI Act
- Séance 6Présenter une analyse à des décideurs
À propos de ces fiches
Écrites par l'enseignant à partir des slides du deck, comme un résumé public : les idées clés, les formules et un exemple calculé. Les exercices, leurs corrigés, les contrôles et la notation restent dans le deck, pour les élèves du cours.
Révision : .