NSI Terminale • Programme français, lycées de Montréal

Fiche de révision : bases de données relationnelles et SQL

Le chapitre des bases de données est celui où l'erreur ne se voit pas : une jointure sans condition rend un tableau, un filtre placé au mauvais endroit rend un tableau, une moyenne de moyennes rend un nombre. Tout est plausible, et seul le compte des lignes permet de savoir si la requête a fait ce qu'on croyait.

Cette fiche liste les neuf erreurs qui reviennent dans les copies, avec la requête exacte à écrire à la place et ce que chacune coûte au barème.

Le fil du chapitre

Une requête SQL fausse ne plante presque jamais : elle rend un tableau plausible, et c'est le NOMBRE de lignes, pas leur contenu, qui trahit l'erreur.

Ce chapitre fait partie de NSI en Terminale

Avant ce chapitre

Cette fiche suppose ces notions acquises. Si une méthode ci-dessous reste opaque, c'est presque toujours l'une d'elles qui manque, pas la fiche.

Remonter plus loin : la chaîne complète (4 chapitres) ↓

Le chemin de remédiation, du plus ancien au plus proche. Un élève qui reprend ce chapitre de zéro le reprend dans cet ordre.

  1. 1Algorithmique et PythonSeconde, Mathématiques
  2. 2Python : types, contrôle, fonctions et tableauxPremière
  3. 3Traitement de données en tables et types construitsPremière
  4. 4Algorithmique et ScratchTroisième, Mathématiques

L'essentiel

Le vocabulaire qui est noté

  • Une relation a un DEGRÉ, son nombre d'attributs, et une CARDINALITÉ, son nombre de lignes. Chaque ligne est un fait.
  • Clé primaire : unique, non nulle et STABLE. Un identifiant artificiel vaut mieux qu'une donnée métier, qui peut changer.
  • Clé étrangère : sa valeur doit exister comme clé primaire dans une autre table. La contrainte pèse sur la table qui RÉFÉRENCE, pas sur celle qui est référencée.
  • Trois anomalies justifient la séparation en plusieurs tables : mise à jour, insertion, suppression. Les nommer vaut les points.

Une question « justifiez le choix de la clé primaire » attend trois mots : unique, non nulle, stable. Les écrire tous les trois rapporte plus que la meilleure des explications.

L'ordre d'exécution, qui n'est pas l'ordre d'écriture

  • FROM et les jointures d'abord, puis WHERE, puis GROUP BY, puis HAVING, puis SELECT, puis ORDER BY.
  • WHERE filtre des LIGNES, avant tout regroupement. HAVING filtre des GROUPES, après.
  • Un alias défini dans le SELECT n'existe pas encore quand le WHERE s'exécute : on ne peut pas y faire référence.
  • Un ORDER BY, lui, s'exécute après le SELECT : il peut donc utiliser un alias.
FROM60 lignesWHERE18 lignesGROUP BY5 groupesHAVING3 groupesSELECT3 lignesl'ordre d'exécution, jamais l'ordre d'écriture
Le compte des lignes chute à chaque étape : c'est ce compte, et non le contenu, qui dit si le filtre a été placé avant ou après le regroupement.

Écrire le compte des lignes après chaque étape, même approximatif, transforme une requête devinée en requête justifiée.

La valeur nulle n'est pas une valeur

  • NULL signifie « inconnue » : toute comparaison avec elle rend « inconnu », donc jamais vrai.
  • On écrit IS NULL et IS NOT NULL, jamais un signe égale ni un signe différent.
  • Une fonction d'agrégat IGNORE les valeurs nulles : une moyenne sur 30 lignes dont 4 nulles porte sur 26 valeurs.
  • COUNT d'une colonne compte les valeurs non nulles ; COUNT sans colonne compte toutes les lignes.
élèvenoteAna14BilalNULLCléa11WHERE note = NULL donne 0 ligneWHERE note IS NULL donne 1 ligne
La même colonne interrogée deux fois : la comparaison par égalité ne trouve jamais la ligne inconnue, seul IS NULL la ramène.

Les transactions

  • Atomicité : tout ou rien. Cohérence : les contraintes restent vérifiées. Isolation : les transactions concurrentes ne s'observent pas. Durabilité : ce qui est validé survit à la panne.
  • La mise à jour perdue est une violation d'ISOLATION : deux lectures, deux écritures, une modification effacée.
  • Le remède est le verrouillage, aussi court que possible : un verrou long garantit l'isolation et bloque tout le monde.
  • Une lecture suivie d'une écriture sur la même donnée forme une section critique, exactement comme en programmation concurrente.

Les pièges qui coûtent des points

Les erreurs ci-dessous sont celles que je corrige le plus souvent en séance. Chacune coûte des points sur une copie, même quand le raisonnement est juste.

1. Écrire une jointure sans condition

toute la question, et toutes celles qui réutilisent la requête

Ce qu'il ne faut pas écrire

« SELECT * FROM emprunt, livre : ça me donne les emprunts avec leur livre. »

Ce qu'il faut écrire

« Sans condition de jointure, on obtient le PRODUIT CARTÉSIEN : chaque emprunt apparié à chaque livre. J'écris JOIN livre ON emprunt.id_livre = livre.id. »

A : 4 lignesB : 3 lignessans condition : 12 lignesx
Quatre lignes appariées à trois lignes en donnent douze : le tableau obtenu est plausible, et seul son nombre de lignes révèle la condition manquante.

Pourquoi : Aucune erreur n'est levée : le résultat est un tableau de 4×3=124 \times 3 = 12 lignes là où on en attendait 4. Le symptôme est toujours le même, un nombre de lignes multiple de l'attendu.

2. Filtrer un agrégat dans le WHERE

2 points, et la requête est refusée par le moteur

Ce qu'il ne faut pas écrire

« SELECT classe, AVG(note) FROM eleve WHERE AVG(note) > 12 GROUP BY classe. »

Ce qu'il faut écrire

« Un agrégat ne peut pas être filtré avant d'exister : SELECT classe, AVG(note) FROM eleve GROUP BY classe HAVING AVG(note) > 12. »

Pourquoi : Le WHERE s'exécute avant le GROUP BY, donc avant que le moindre groupe existe. HAVING est le seul filtre qui vienne après, et donc le seul qui puisse parler d'un agrégat.

3. Comparer une valeur nulle avec un signe égale

1 point, et la requête renvoie zéro ligne sans erreur

Ce qu'il ne faut pas écrire

« SELECT * FROM eleve WHERE note = NULL pour trouver les notes manquantes. »

Ce qu'il faut écrire

« SELECT * FROM eleve WHERE note IS NULL : la comparaison par égalité avec une valeur inconnue rend « inconnu », donc aucune ligne. »

Pourquoi : NULL n'est pas une valeur mais l'absence de valeur : deux inconnues ne sont pas égales, elles sont indéterminées. C'est pour cela que SQL a inventé un opérateur dédié.

4. Oublier DISTINCT après une jointure

1 point, et la question « combien d'auteurs » devient fausse

Ce qu'il ne faut pas écrire

« SELECT nom FROM auteur JOIN livre ON ... WHERE annee > 2000 : voilà la liste des auteurs. »

Ce qu'il faut écrire

« Un auteur ayant trois livres apparaît trois fois : SELECT DISTINCT nom, dès que l'on projette sur une table après un filtre portant sur une table liée. »

Pourquoi : La jointure duplique les lignes de la table de gauche autant de fois qu'elles ont de correspondances à droite. La projection ne supprime pas ces doublons, elle les rend seulement invisibles.

5. Faire la moyenne des moyennes

2 points sur la question d'agrégation, et la conclusion du sujet en dépend

Ce qu'il ne faut pas écrire

« La classe A a 14 de moyenne sur 3 élèves et la classe B a 8 sur 1 élève, donc l'établissement est à 11. »

Ce qu'il faut écrire

« La moyenne pondérée s'écrit SUM(note) / COUNT(note), ou SUM(valeur * coefficient) / SUM(coefficient) : elle vaut ici (42+8)/4=12,5(42 + 8)/4 = 12{,}5. »

Pourquoi : Une moyenne est un rapport, et les rapports ne s'additionnent pas. La moyenne des moyennes ne serait juste que si tous les groupes avaient le même effectif.

6. Choisir une donnée métier comme clé primaire

1 point, et toute la question de conception qui suit

Ce qu'il ne faut pas écrire

« L'adresse de courriel identifie l'élève de façon unique, elle fera une bonne clé primaire. »

Ce qu'il faut écrire

« Une clé primaire doit être STABLE : un courriel change. Je crée un identifiant artificiel, et je mets une contrainte d'unicité sur le courriel. »

Pourquoi : Changer une clé primaire oblige à mettre à jour toutes les clés étrangères qui la référencent. L'identifiant artificiel n'a aucune raison de changer, c'est précisément sa qualité.

7. Croire que la contrainte de clé étrangère pèse sur la table référencée

1 point, et le schéma proposé est inversé

Ce qu'il ne faut pas écrire

« La table livre a une clé étrangère vers emprunt, donc on ne peut pas ajouter d'emprunt inconnu. »

Ce qu'il faut écrire

« La clé étrangère est portée par la table qui RÉFÉRENCE : c'est emprunt qui pointe vers livre, et c'est donc l'insertion dans emprunt qui est contrôlée. »

Pourquoi : Le sens de la référence décide de tout : quelle insertion est refusée, quelle suppression est bloquée. L'inverser rend le schéma incapable d'exprimer un livre sans emprunt.

8. Écrire un UPDATE ou un DELETE sans WHERE

2 points, et la question suivante décrit une base entièrement fausse

Ce qu'il ne faut pas écrire

« UPDATE livre SET disponible = 0 pour marquer ce livre comme sorti. »

Ce qu'il faut écrire

« Sans WHERE, la mise à jour touche TOUTE la table : UPDATE livre SET disponible = 0 WHERE id = 42. »

Pourquoi : SQL applique par défaut l'opération à toutes les lignes. C'est aussi l'accident réel le plus courant en base de données, et le sujet le teste presque toujours sous forme de question à corriger.

9. Croire que le SELECT s'exécute en premier

1 point, et la requête est refusée

Ce qu'il ne faut pas écrire

« SELECT AVG(note) AS moyenne FROM eleve WHERE moyenne > 12. »

Ce qu'il faut écrire

« L'alias moyenne est créé par le SELECT, qui s'exécute APRÈS le WHERE : je répète l'agrégat dans un HAVING, ou j'utilise une sous-requête. »

Pourquoi : L'ordre d'écriture d'une requête n'est pas son ordre d'exécution. Retenir la chaîne FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY résout à lui seul trois pièges du chapitre.

Quelle méthode choisir

WHERE ou HAVING

Ce sur quoi porte la condition : une colonne d'une ligne, ou une fonction d'agrégat.

  • Si la condition porte sur une colonne d'une ligne WHERE

    Exemple : WHERE annee > 2000

  • Si la condition contient COUNT, SUM, AVG, MIN ou MAX HAVING, après le GROUP BY

    Exemple : HAVING COUNT(*) >= 3

  • Si la condition porte sur un alias du SELECT répéter l'expression, ou passer par une sous-requête

    Exemple : HAVING AVG(note) > 12, jamais WHERE moyenne > 12

  • Si la condition relie deux tables condition de jointure dans le ON

    Exemple : JOIN livre ON emprunt.id_livre = livre.id

Une requête peut porter les deux : WHERE pour restreindre les lignes avant regroupement, HAVING pour ne garder que certains groupes. C'est même le cas le plus fréquent au bac.

Quelle requête pour quelle question

Le mot de l'énoncé : « combien », « pour chaque », « la liste des », « le plus ».

  • Si « combien de », sans découpage SELECT COUNT(*) avec un WHERE

    Exemple : combien d'emprunts en retard

  • Si « pour chaque X, le nombre ou le total de Y » GROUP BY X, avec COUNT ou SUM

    Exemple : le nombre d'emprunts par lecteur

  • Si « ceux qui ont au moins k » GROUP BY puis HAVING

    Exemple : les lecteurs ayant au moins trois emprunts

  • Si « la liste des X qui ont un Y tel que... » jointure, puis SELECT DISTINCT

    Exemple : les auteurs publiés après 2000

    sans DISTINCT, un auteur apparaît autant de fois qu'il a de livres

  • Si « la moyenne sur l'ensemble » SUM divisé par COUNT, jamais une moyenne de moyennes

    Exemple : (42+8)/4(42 + 8)/4, jamais (14+8)/2(14 + 8)/2

Si aucune branche ne s'applique, c'est une composition : une jointure, puis un regroupement, puis un filtre de groupe.

La rédaction attendue

Le correcteur coche des étapes. Les voici dans l'ordre, avec la phrase de conclusion qu'il attend mot pour mot.

Écrire une requête et la justifier

Quand l'utiliser : L'énoncé donne un schéma et demande une requête en français.

  1. 1 Repérer les tables nécessaires, et écrire une condition de jointure par table ajoutée : kk tables demandent k1k-1 conditions.
  2. 2 Écrire le filtre de lignes dans le WHERE, avec les colonnes des tables jointes.
  3. 3 Décider s'il y a un regroupement : le mot « par » ou « pour chaque » dans l'énoncé le signale.
  4. 4 Placer tout filtre portant sur un agrégat dans le HAVING, jamais dans le WHERE.
  5. 5 Projeter en dernier, en ajoutant DISTINCT si la jointure a pu dupliquer des lignes, puis trier si l'énoncé le demande.

Phrase de conclusion

« SELECT lecteur.nom, COUNT(*) AS nb FROM emprunt JOIN lecteur ON emprunt.id_lecteur = lecteur.id WHERE emprunt.rendu = 0 GROUP BY lecteur.nom HAVING COUNT(*) >= 3 ORDER BY nb DESC : une table jointe donc une condition de jointure, le filtre sur les lignes dans le WHERE et le filtre sur les groupes dans le HAVING. »

Le piège : Ajouter une table dans le FROM sans ajouter sa condition de jointure : le résultat est un produit cartésien silencieux.

Barème : 1 point par condition de jointure, 1 point pour le WHERE, 1 point pour le couple GROUP BY et HAVING, 1 point pour la projection.

Justifier la séparation en plusieurs tables

Quand l'utiliser : L'énoncé montre une table unique redondante et demande pourquoi la découper.

  1. 1 Montrer la redondance sur une ligne précise du tableau donné : la même information répétée.
  2. 2 Nommer l'anomalie de MISE À JOUR : corriger l'adresse d'un lecteur oblige à modifier toutes ses lignes, et une seule oubliée rend la base incohérente.
  3. 3 Nommer l'anomalie d'INSERTION : impossible d'enregistrer un lecteur qui n'a pas encore emprunté.
  4. 4 Nommer l'anomalie de SUPPRESSION : supprimer le dernier emprunt d'un lecteur efface aussi le lecteur.
  5. 5 Donner le découpage proposé, avec la clé primaire de chaque table et la clé étrangère qui les relie.

Phrase de conclusion

« La table unique répète l'adresse du lecteur à chaque emprunt, ce qui expose aux trois anomalies. On sépare en une table lecteur, de clé primaire id_lecteur, et une table emprunt, dont la clé étrangère id_lecteur référence la précédente. »

Le piège : Se contenter de dire « c'est redondant » : le barème attend les trois anomalies nommées, une par une.

Barème : 1 point par anomalie nommée et illustrée, 1 point pour le schéma corrigé.

Vérifier avant de rendre

Cinq minutes de vérification récupèrent plus de points qu'un exercice de plus commencé à la hâte.

L'exercice type décortiqué

La bibliothèque : les lecteurs qui ont au moins trois emprunts en cours

Le schéma comporte lecteur, de clé primaire id, et emprunt, d'attributs id, id_lecteur, id_livre et rendu, où rendu vaut 0 tant que le livre n'est pas revenu. La table emprunt compte 60 lignes.

Écrire la requête qui donne le nom de chaque lecteur ayant au moins trois emprunts en cours, avec ce nombre, du plus grand au plus petit.

Étape 1

FROM emprunt JOIN lecteur ON emprunt.id_lecteur = lecteur.id

Pourquoi

Deux tables, donc exactement une condition de jointure. L'écrire en premier évite le produit cartésien, qui donnerait ici 60 fois le nombre de lecteurs au lieu de 60 lignes.

Étape 2

WHERE emprunt.rendu = 0, ce qui ramène les 60 lignes à 18.

Pourquoi

Le filtre porte sur une colonne d'une ligne, donc il va dans le WHERE : filtrer avant le regroupement réduit le travail et, surtout, change le résultat, puisque les emprunts rendus ne doivent pas être comptés.

Étape 3

GROUP BY lecteur.nom : les 18 lignes deviennent 5 groupes.

Pourquoi

Le mot « chaque lecteur » de l'énoncé impose le regroupement. À partir d'ici, une ligne du résultat n'est plus un emprunt mais un lecteur, et c'est ce changement de nature que le compte des lignes rend visible.

Étape 4

HAVING COUNT(*) >= 3 : il reste 3 groupes.

Pourquoi

La condition porte sur un agrégat, donc elle ne peut pas être dans le WHERE, qui s'exécute avant l'existence du moindre groupe. C'est le point du barème que les copies perdent le plus souvent.

Étape 5

SELECT lecteur.nom, COUNT(*) AS nb, puis ORDER BY nb DESC.

Pourquoi

La projection vient en avant-dernier : l'alias nb créé ici est utilisable dans le ORDER BY, qui s'exécute après, mais il ne l'aurait pas été dans le WHERE.

Étape 6

Vérification : trois lignes en sortie, chacune avec un compte d'au moins 3, et la somme des comptes inférieure ou égale à 18.

Pourquoi

Ce contrôle ne refait pas la requête : il compare la sortie aux comptes annoncés à chaque étape. Une somme supérieure à 18 signalerait une jointure qui a dupliqué des emprunts.

Conclusion rédigée

« SELECT lecteur.nom, COUNT(*) AS nb FROM emprunt JOIN lecteur ON emprunt.id_lecteur = lecteur.id WHERE emprunt.rendu = 0 GROUP BY lecteur.nom HAVING COUNT(*) >= 3 ORDER BY nb DESC. Le WHERE filtre les emprunts non rendus, le HAVING ne garde que les lecteurs qui en ont au moins trois. »

L'erreur classique sur cet exercice : Écrire WHERE COUNT(*) >= 3 : la requête est refusée par le moteur, car l'agrégat n'existe pas encore au moment où le WHERE s'exécute.

À savoir par cœur

  • Ordre d'exécution : FROM, JOIN, WHERE, GROUP BY, HAVING, SELECT, ORDER BY.
  • WHERE filtre des LIGNES, HAVING filtre des GROUPES. Un agrégat ne va jamais dans un WHERE.
  • kk tables dans le FROM demandent k1k-1 conditions de jointure. Sinon, produit cartésien silencieux.
  • IS NULL et IS NOT NULL, jamais un signe égale : toute comparaison avec NULL rend « inconnu ».
  • DISTINCT dès qu'on projette après une jointure qui peut dupliquer.
  • Moyenne pondérée : SUM sur SUM, jamais une moyenne de moyennes.
  • Clé primaire : unique, non nulle, STABLE. Clé étrangère : portée par la table qui référence.
  • Trois anomalies à nommer : mise à jour, insertion, suppression.
  • ACID : atomicité, cohérence, isolation, durabilité. La mise à jour perdue viole l'isolation.

Questions fréquentes

Quelle est la différence entre WHERE et HAVING en SQL ?

WHERE filtre des lignes et s'exécute avant tout regroupement ; HAVING filtre des groupes et s'exécute après le GROUP BY. Une condition contenant COUNT, SUM, AVG, MIN ou MAX porte forcément sur un groupe, donc elle va dans le HAVING. Une même requête peut contenir les deux, et c'est même le cas le plus fréquent au baccalauréat.

Pourquoi ma jointure donne-t-elle trop de lignes ?

Presque toujours parce qu'une condition de jointure manque : sans elle, chaque ligne de la première table est appariée à chaque ligne de la seconde, ce qui donne le produit cartésien. Il faut une condition par table ajoutée, donc deux conditions pour trois tables. Le symptôme est un nombre de lignes multiple exact du nombre attendu.

Comment chercher les valeurs manquantes dans une base de données ?

Avec IS NULL, jamais avec un signe égale. La valeur nulle signifie inconnue, si bien que toute comparaison la concernant rend inconnu plutôt que vrai : une condition écrite avec un signe égale ne ramène donc aucune ligne, sans lever la moindre erreur. On écrit IS NOT NULL pour l'inverse.

Pourquoi un identifiant artificiel comme clé primaire ?

Parce qu'une clé primaire doit être stable autant qu'unique. Une donnée métier comme un courriel ou un nom peut changer, et la modifier obligerait à mettre à jour toutes les clés étrangères qui la référencent, avec le risque d'incohérence. Un identifiant artificiel n'a aucune raison de changer ; on peut toujours poser en plus une contrainte d'unicité sur le courriel.

Qu'est-ce qu'une mise à jour perdue dans une base de données ?

C'est le cas où deux transactions lisent la même valeur, la modifient chacune de leur côté, puis écrivent : la seconde écriture efface la première, dont l'effet disparaît sans trace. C'est une violation de l'isolation, la lettre I du sigle ACID. Le remède est de verrouiller la donnée pendant la lecture et l'écriture, sur une durée aussi courte que possible.

Passer à la pratique

Exercices corrigés : Bases de données relationnelles et langage SQL

Une méthode se prouve sur une copie, pas sur une fiche. La série du même chapitre reprend chacun de ces pièges dans un exercice, avec le corrigé rédigé étape par étape.

  • 15 exercices corrigés
  • 150 points
  • 255 minutes
Faire les exercices
Fiche précédente Structures de données : piles, files, arbres et graphes Fiche suivante Processus, réseaux et sécurisation

Voir aussi

Vous cherchez un tuteur en NSI à Montréal ?

Contactez-moi pour une première séance. On reprend les points de méthode qui font perdre des points en évaluation, puis on les met à l'épreuve sur des exercices du niveau réel de l'examen.

Site par Studio Squalli