Aller au contenu

~/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.

Les slides

SQL essentials for managers

Séance 3, jeudi 7 janvier 2027. Data Analytics for Business, Université Catholique de Lille, M2 International Management, 2026-2027. Les slides de ce cours sont en anglais.

Ouvrir les slides (mot de passe donné par l'enseignant)

Le deck lui-même est chiffré pour les élèves du cours ; cette page le résume pour tous.

La page du cours

Plan du deck

  1. 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.
  2. 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.
  3. 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.
  4. À vous de jouer. Trois exercices en binôme, du texte à trous au classement sur trois tables, sans assistant IA.
  5. 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.
  6. 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.
  7. 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.

Retour au sommaire.

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.

Tableau 1 : Granularité des principales tables du cas
TableUne ligne, c'estLignes par commande
ordersune commandeexactement une
customersla fiche client d'une commandeexactement une
order_itemsun article d'une commandeune ou plusieurs
paymentsun paiement d'une commandeun ou plusieurs
reviewsun avis sur une commandezéro, un ou plusieurs

Retour au sommaire.

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

  1. 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.
  2. WHERE. On ne garde que les articles à 100 ou plus.
  3. GROUP BY. On forme un groupe par État du vendeur.
  4. SELECT. Dans chaque groupe, on compte les lignes et l'on fait la moyenne des frais de port.
  5. 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.

Retour au sommaire.

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.

Tableau 2 : Données d'illustration du support : trois commandes, des avis pour deux d'entre elles
CommandeA un avisINNER JOINLEFT JOIN
o1ouigardéegardée
o2nonperduegardée, colonnes d'avis à NULL
o3ouigardéegardée
Lignes renvoyées23

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.

Retour au sommaire.

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).

nlignes=narticles×npaiements=2×2=4n_{\text{lignes}} = n_{\text{articles}} \times n_{\text{paiements}} = 2 \times 2 = 4
(1)

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;
Tableau 3 : Lire les deux comptages après une jointure
Ce que l'on voitCe que cela signifie
Les deux comptages inchangésToujours une ligne par commande : on peut additionner
Plus de lignes, autant de commandesDes lignes se sont multipliées : les sommes sont gonflées
Moins de commandes distinctesDes 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;

Retour au sommaire.

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

  1. Granularité. Que représente une ligne avant et après chaque jointure ? On compare le nombre de lignes au nombre de clés distinctes.
  2. 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.
  3. 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.
  4. 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.

Retour au sommaire.

À retenir

  1. 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.
  2. 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.
  3. 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.
  4. On résume les tables à plusieurs lignes par clé avant de les joindre entre elles.
  5. Avec un LEFT JOIN, les conditions sur la table de droite vont dans ON, pas dans WHERE.
  6. 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.

  1. Séance 1Des données à la décision
  2. Séance 2Visualisation de données et récit
  3. Séance 3L'essentiel du SQL pour managers
  4. Séance 4L'analyse prédictive pour décideurs
  5. Séance 5IA générative, RGPD et AI Act
  6. Séance 6Présenter une analyse à des décideurs

Toutes les fiches, en liste de lecture

À 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 : .