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

Exercices corrigés de NSI : bases de données relationnelles et SQL

Voici une série d'exercices corrigés de NSI pour la classe de Terminale, sur les bases de données relationnelles et le langage SQL du programme français. Elle s'adresse aux élèves des lycées français de Montréal, le Lycée Marie de France et le Collège Stanislas.

Seize exercices couvrent le modèle relationnel, les domaines et la clé primaire, la clé étrangère et l'intégrité référentielle, les anomalies de mise à jour, d'insertion et de suppression, les requêtes SELECT avec WHERE, DISTINCT et ORDER BY, les jointures, les fonctions d'agrégation, les commandes INSERT, UPDATE et DELETE, les transactions et les propriétés ACID. À la fin, vous savez justifier le choix d'une clé primaire (exercice 1), prévoir ce que la base refuse au nom de l'intégrité référentielle (exercice 2), écrire une jointure à deux puis à trois tables (exercice 5), mesurer les dégâts d'un UPDATE sans WHERE (exercice 7), reconnaître une mise à jour perdue sur un chronogramme (exercice 9), concevoir la base d'une bibliothèque (exercice 10) et protéger un programme Python contre l'injection SQL par une requête paramétrée (exercice 13). Trois exercices ont la forme de l'exercice de bases de données de l'écrit du baccalauréat, où le schéma, les requêtes d'interrogation et les requêtes de mise à jour se suivent sur la même base : l'exercice 5, des notes d'une classe jusqu'aux deux insertions dans l'ordre qu'impose la clé étrangère ; l'exercice 10, la bibliothèque, du schéma au retour d'un livre et à l'emprunt que les contraintes acceptent à tort ; l'exercice 16, les vélos en libre-service, des agrégats à la fermeture d'une station.

Le fil de la série : une base relationnelle ne stocke pas des tableaux, elle enregistre des FAITS. Chaque ligne affirme quelque chose, et tout le schéma, domaines, clés primaires, clés étrangères, n'existe que pour empêcher d'y écrire un mensonge. Les trois anomalies de la normalisation et les quatre propriétés d'une transaction découlent de cette seule idée.

Trois pièges sont désignés nommément dans le corrigé : oublier la condition de jointure, ce qui produit un produit cartésien sans le moindre message d'erreur (exercice 5) ; écrire une fonction d'agrégation dans un WHERE, ou la mêler à une colonne ordinaire (exercices 6 et 16) ; et comparer une valeur nulle avec un signe égale (exercice 4).

Ce que le programme exige, et ce qu'il n'exige pas. Pour interroger une base, le programme de Terminale donne SELECT, FROM, WHERE et JOIN, avec DISTINCT, ORDER BY et les fonctions d'agrégation, « sans utiliser les clauses GROUP BY et HAVING » ; pour la modifier, INSERT, UPDATE et DELETE. Les questions qui emploient GROUP BY, HAVING, la jointure externe, les sous-requêtes, l'auto-jointure récursive ou la création des tables restent dans la série, étiquetées comme approfondissements, et l'exercice 16 montre comment répondre aux mêmes questions avec les seules clauses exigibles.

Série autocorrigée Tape tes réponses et vérifie-les question par question : la page te dit juste ou faux avant d'ouvrir la correction. Avec un compte, chaque bonne réponse du premier coup rapporte des points, et tes réponses justes restent remplies quand tu reviens.

Ce chapitre fait partie de NSI en Terminale
Avant de commencer Fiche de révision : les pièges et la méthode de ce chapitre

Avant ce chapitre

Ces notions sont supposées acquises ici. Si le premier exercice résiste, le blocage vient presque toujours de l'une d'elles, pas du chapitre lui-même.

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. 1 Algorithmique et Scratch Troisième, Mathématiques
  2. 2 Algorithmique et Python Seconde, Mathématiques
  3. 3 Python : types, contrôle, fonctions et tableaux Première
  4. 4 Traitement de données en tables et types construits Première

Rappel de cours

  • • 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.
  • • Clé étrangère : sa valeur doit exister comme clé primaire ailleurs. La contrainte pèse sur la table qui référence.
  • • Trois anomalies justifient la séparation en tables : mise à jour, insertion, suppression.
  • • Ordre d'exécution des clauses du programme : FROM et JOIN, puis WHERE, puis SELECT, puis ORDER BY. WHERE filtre des LIGNES ; une fonction d'agrégation ne s'y écrit jamais. Hors programme, GROUP BY et HAVING s'intercalent après WHERE, HAVING filtrant des groupes.
  • • Au programme : COUNT, SUM, AVG, MIN et MAX s'appliquent aux lignes retenues par le WHERE, sans GROUP BY ni HAVING, et rendent UNE ligne. SUM, AVG, MIN, MAX et COUNT(colonne) ignorent les valeurs nulles ; COUNT(*) compte les lignes. « Pour chaque » valeur : une requête par valeur, contrôlée par la somme.
  • • Une jointure sans condition donne le produit cartésien, sans erreur : le symptôme est un nombre de lignes multiple de l'attendu.
  • • DISTINCT s'impose dès qu'on projette sur une table après un filtre portant sur une table liée.
  • • La valeur nulle signifie « inconnue » : toute comparaison avec elle est inconnue. On écrit IS NULL, jamais un signe égale.
  • • Une moyenne pondérée s'écrit SUM(valeur * coefficient) / SUM(coefficient), jamais une moyenne de moyennes.
  • • ACID : atomicité, cohérence, isolation, durabilité. La mise à jour perdue est une violation d'isolation, corrigée par verrouillage.

Partie A : Les bases (/50)

Exercice 1 : Le modèle relationnel : relation, attribut, clé primaire

Une base relationnelle ne range pas des tableaux : elle enregistre des FAITS. Chaque ligne affirme quelque chose, et le rôle du schéma est d'empêcher qu'on y écrive un mensonge.

idnomclasse
1Dupont1G3
2Nadeau1G1
3Ferrand1G3
4OuelletTG2
  • a) Donnez le nom de la relation, la liste de ses attributs, son degré et sa cardinalité. Que signifie exactement une ligne de cette table ?
  • b) Qu'est-ce qu'un domaine ? Proposez un domaine pour chacun des trois attributs et dites ce que le système refusera d'écrire.
  • c) Définissez la clé primaire. Pourquoi l'attribut nom serait-il un mauvais choix ici, alors qu'il ne contient aucun doublon dans cet extrait ?
  • d) Deux lignes identiques peuvent-elles coexister dans une relation ? Justifiez à partir de la définition mathématique d'une relation, puis dites ce que fait un système réel.
  • e) Qu'est-ce qu'une clé candidate ? Une clé primaire peut-elle porter sur deux attributs à la fois ? Donnez un exemple dans une table de notes.

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Degré 3, cardinalité 4 ; une ligne est un fait
  • b) Domaine : type et contraintes
  • c) Clé primaire unique, stable, courte
  • d) Relation ensembliste : pas de doublon
  • e) Clé candidate ; clé composée
a)

La relation s'appelle eleve. Ses attributs sont id, nom et classe. Son degré, c'est-à-dire son nombre d'attributs, vaut 3. Sa cardinalité, c'est-à-dire son nombre de lignes, vaut 4. Une ligne affirme un fait : « il existe un élève, d'identifiant 1, nommé Dupont, inscrit en 1G3 ». Cette lecture en termes de fait n'est pas une image : c'est elle qui justifie que l'ordre des lignes soit sans importance et qu'un doublon soit dépourvu de sens.

b)

Un domaine est l'ensemble des valeurs autorisées pour un attribut, autrement dit son type accompagné de ses contraintes. Ici : id est un entier strictement positif ; nom est une chaîne de caractères non vide, d'au plus une centaine de caractères ; classe est une chaîne appartenant à une liste fermée de codes de classes de l'établissement. Le système refusera alors d'écrire un identifiant négatif, un nom vide, ou une classe inexistante comme « 1G9 ». Sans domaine déclaré, ces trois erreurs entrent dans la base et n'en ressortent jamais.

c)

La clé primaire est un attribut, ou un groupe d'attributs, dont la valeur identifie de façon unique et non nulle chaque ligne de la relation. L'attribut nom serait un mauvais choix pour trois raisons. Un, l'unicité n'est pas garantie dans le temps : deux homonymes finiront par arriver, et la base deviendrait alors incapable d'enregistrer le second. Deux, la valeur peut CHANGER, par mariage ou correction d'orthographe, et il faudrait alors mettre à jour toutes les tables qui la référencent. Trois, elle est longue, donc coûteuse à indexer et à recopier. Une clé primaire doit être unique, stable et courte, ce qu'un identifiant artificiel garantit par construction.

d)

Non, pas selon la définition mathématique : une relation est un ENSEMBLE de n-uplets, et un ensemble ne contient pas deux fois le même élément. Deux lignes identiques n'apporteraient d'ailleurs aucune information, puisqu'elles affirmeraient exactement le même fait. Dans un système réel, l'absence de doublon n'est garantie que si une clé primaire ou une contrainte d'unicité est déclarée : sans elle, la plupart des moteurs acceptent d'insérer deux lignes identiques, ce qui fausse ensuite tout comptage. Déclarer la clé primaire n'est donc pas une formalité, c'est ce qui rend la table conforme au modèle.

e)

Une clé candidate est un ensemble d'attributs qui pourrait servir de clé primaire, c'est-à-dire qui identifie de façon unique chaque ligne et dont aucune partie stricte n'a cette propriété. Une relation peut en avoir plusieurs, et l'on en choisit une comme clé primaire. Oui, une clé primaire peut porter sur plusieurs attributs : dans une table de notes, le couple formé de eleve_id et devoir_id est une clé primaire naturelle si l'on décide qu'un élève ne peut avoir qu'une seule note par devoir. On parle alors de clé COMPOSÉE, et elle exprime une règle de gestion, pas seulement une contrainte technique.

Coche ici les exercices faits ou à revoir : un compte gratuit, sans mot de passe, retient tes coches et tes réponses justes d'une visite à l'autre, te dit quel chapitre attaquer ensuite et te permet de demander l'exercice qui te manque. Crée ton espace, un courriel suffit.

Exercice 2 : Clé étrangère et intégrité référentielle

Le schéma montre deux relations reliées par une clé étrangère. C'est ce lien, et lui seul, qui empêche la base de contenir la note d'un élève qui n'existe pas.

elevenoteidnomclasseideleve_iddevoir_idvaleurclé étrangère
  • a) Définissez une clé étrangère. Laquelle des deux tables porte la contrainte, et sur quelle table s'appuie-t-elle ?
  • b) On tente d'insérer dans note la ligne d'identifiant 107, avec eleve_id valant 9, devoir_id valant 10 et valeur 13. Que se passe-t-il et pourquoi ?
  • c) On tente de supprimer l'élève d'identifiant 1, qui possède deux notes. Décrivez les trois comportements possibles du système et dites lequel choisir dans un carnet de notes.
  • d) Peut-on avoir une clé étrangère qui pointe vers la même table ? Donnez un exemple et dites quelle structure de données on obtient alors.
  • e) Un élève propose de recopier le nom de l'élève dans la table note, « pour éviter la jointure ». Donnez trois arguments contre cette proposition.

Tape tes réponses, la page te dit juste ou faux 0/5

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) note.eleve_id référence eleve.id
  • b) Élève 9 inexistant : refus
  • c) Refus, cascade, mise à nul : choisir le refus
  • d) Clé réflexive : graphe ou arbre
  • e) Recopier le nom : redondance et anomalies
a)

Une clé étrangère est un attribut, ou un groupe d'attributs, d'une relation, dont la valeur doit obligatoirement correspondre à une valeur de clé primaire existante dans une autre relation, ou bien être nulle si la conception l'autorise. C'est la table note qui porte la contrainte, par son attribut eleve_id ; elle s'appuie sur la table eleve, par sa clé primaire id. Le sens de la flèche compte : la contrainte pèse sur celui qui référence, jamais sur celui qui est référencé.

b)

L'insertion est REFUSÉE. Aucun élève d'identifiant 9 n'existe dans la table eleve, donc la ligne violerait l'intégrité référentielle : elle affirmerait la note d'un élève inexistant. Le système lève une erreur explicite et n'écrit rien. C'est exactement le service qu'on attend d'une base de données : une donnée incohérente est rejetée AU MOMENT de l'écriture, et non découverte six mois plus tard par un calcul de moyenne qui tombe faux.

c)

Trois comportements possibles, tous prévus par la norme. Le REFUS : la suppression est rejetée tant que des notes référencent l'élève, ce qui oblige à traiter les notes d'abord. La SUPPRESSION EN CASCADE : les notes de l'élève sont supprimées automatiquement en même temps que lui. La MISE À NUL : eleve_id passe à la valeur nulle dans les notes concernées, qui deviennent orphelines mais subsistent. Dans un carnet de notes, il faut choisir le REFUS : une suppression en cascade ferait disparaître silencieusement des évaluations, et une mise à nul produirait des notes qui n'appartiennent à personne, donc inexploitables. Le refus force à décider explicitement, ce qui est exactement ce qu'on veut sur des données scolaires.

d)

Oui, c'est ce qu'on appelle une clé étrangère réflexive. Exemple : dans une table eleve, un attribut tuteur_id qui référence l'identifiant d'un autre élève de la même table, dans un système de tutorat entre pairs. On obtient alors un GRAPHE ORIENTÉ dont les sommets sont les lignes de la table, ou un arbre si l'on ajoute la contrainte qu'aucun cycle n'est permis et qu'un seul élément n'a pas de tuteur. C'est ainsi qu'on représente une hiérarchie, une arborescence de catégories ou un fil de discussion en réponses imbriquées.

e)

Trois arguments. La REDONDANCE : le nom serait stocké autant de fois qu'il y a de notes, ce qui gaspille de l'espace et surtout multiplie les occasions d'incohérence. L'ANOMALIE DE MISE À JOUR : corriger une faute d'orthographe dans un nom obligerait à modifier toutes les lignes de notes, et en oublier une seule crée deux élèves là où il n'y en a qu'un. La PERTE DE LA CONTRAINTE : un nom recopié n'est plus vérifié par personne, alors qu'une clé étrangère est contrôlée à chaque écriture. On peut ajouter un quatrième argument, purement pratique : la jointure que l'on cherchait à éviter coûte, sur une clé indexée, à peine plus qu'une lecture directe, alors que la dénormalisation coûte pour toujours.

Exercice 3 : Pourquoi séparer en plusieurs tables

La figure oppose deux façons d'enregistrer les mêmes faits : une seule table où tout est recopié, et deux tables reliées. Le choix n'est pas esthétique, il décide de ce que la base pourra affirmer.

nomclassevaleurDupont1G312Dupont1G315Dupont1G39Nadeau1G114une seule tableidnomclasse1Dupont1G32Nadeau1G1eleveeleve_idvaleur11211519214note
  • a) Repérez la redondance dans la table de gauche. Combien de fois l'information « Dupont est en 1G3 » y est-elle écrite ?
  • b) On veut corriger la classe de Dupont, qui passe en 1G4. Décrivez l'opération dans chacune des deux conceptions et le risque propre à la première.
  • c) On veut inscrire un nouvel élève qui n'a encore aucune note. Que se passe-t-il dans chacune des deux conceptions ? Comment s'appelle ce type de problème ?
  • d) On supprime la dernière note de Nadeau. Que perd-on dans chacune des deux conceptions ?
  • e) Énoncez, à partir de ces trois observations, la règle générale de séparation. Que doit contenir une table, et une seule ?

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Fait écrit trois fois
  • b) Anomalie de mise à jour
  • c) Anomalie d'insertion
  • d) Anomalie de suppression
  • e) Un fait, un seul endroit
a)

La table de gauche répète le couple Dupont et 1G3 sur trois lignes, une par note. L'information « Dupont est en 1G3 » y est donc écrite TROIS fois, alors qu'il s'agit d'un seul fait. Dans la conception de droite, elle n'est écrite qu'une fois, dans la table des élèves, et les trois notes ne portent que l'identifiant.

b)

Dans la conception de droite, on modifie UNE ligne de la table des élèves, et c'est fini : les trois notes suivent automatiquement, puisqu'elles ne stockent que l'identifiant. Dans celle de gauche, il faut modifier les trois lignes concernées. Le risque propre à la première conception est l'ANOMALIE DE MISE À JOUR : si l'une des trois lignes est oubliée, la base affirme simultanément que Dupont est en 1G3 et en 1G4. Elle contient alors une contradiction, et aucune requête ne pourra plus trancher, puisque les deux réponses y sont écrites noir sur blanc.

c)

Dans la conception de droite, on insère simplement une ligne dans la table des élèves, sans aucune note : c'est parfaitement légitime. Dans celle de gauche, une ligne exige une valeur de note ; il faudrait inventer une note fictive, ou laisser une valeur nulle qui faussera tous les calculs de moyenne. On appelle cela une ANOMALIE D'INSERTION : la conception interdit d'enregistrer un fait, l'inscription de l'élève, parce qu'un autre fait sans rapport, sa première note, n'existe pas encore.

d)

Dans la conception de droite, on perd la note et rien d'autre : Nadeau reste inscrit en 1G1. Dans celle de gauche, la suppression de sa seule ligne fait disparaître du même coup l'existence de Nadeau et son appartenance à la 1G1 : on perd un fait qu'on ne voulait pas supprimer. C'est l'ANOMALIE DE SUPPRESSION, la troisième du trio classique.

e)

La règle : une table doit décrire UNE SEULE sorte de chose, et chacun de ses attributs doit dépendre de la clé primaire entière et de rien d'autre. Ici, la classe dépend de l'élève et non de la note : elle n'a donc rien à faire dans une table de notes. Formulée autrement, chaque fait doit être enregistré à un seul endroit ; dès qu'une information est recopiée, elle peut se contredire, et une base qui se contredit n'est plus une source de vérité. C'est cette exigence qu'on appelle la normalisation, et les trois anomalies des questions b, c et d en sont la justification complète.

Exercice 4 : SQL : sélectionner, filtrer, ordonner

Les deux tables de la base servent d'appui à tous les exercices qui suivent. La table note ci-dessous référence les élèves de l'exercice 1 et trois devoirs, 10 en mathématiques, 11 en physique et 12 en NSI.

Approfondissement pour la seconde moitié de la question d) : l'opérateur LIKE ne figure pas dans la liste des clauses du programme de Terminale (SELECT, FROM, WHERE, JOIN, avec DISTINCT, ORDER BY et les fonctions d'agrégation, pour l'interrogation ; INSERT, UPDATE, DELETE pour la mise à jour). Seule la comparaison WHERE classe = '1G3' est exigible.

ideleve_iddevoir_idvaleur
10011012
10111115
1022109
10331014
10431217
1054118
10641211
  • a) Écrivez la requête qui donne le nom des élèves de la classe 1G3, et donnez son résultat.
  • b) Écrivez la requête qui donne toutes les notes strictement supérieures à 12, triées par valeur décroissante. Donnez le résultat.
  • c) Écrivez la requête qui donne les identifiants des élèves ayant au moins une note, sans répétition. Donnez le résultat et expliquez le mot-clé employé.
  • d) Quelle différence y a-t-il entre WHERE valeur > 12 et WHERE valeur >= 12 sur ces données ? Entre WHERE classe = '1G3' et WHERE classe LIKE '1G%' ?
  • e) Une requête renvoie une valeur nulle dans une colonne. Expliquez pourquoi la condition WHERE valeur <> 12 ne ramène PAS les lignes dont la valeur est nulle, et donnez la bonne écriture.

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) WHERE classe = '1G3' : 2 lignes
  • b) ORDER BY valeur DESC : 17, 15, 14
  • c) DISTINCT : 4 identifiants
  • d) 3 contre 4 lignes ; LIKE : 3 élèves
  • e) IS NULL, jamais = NULL
a)

La requête : SELECT nom FROM eleve WHERE classe = '1G3'. Le résultat contient deux lignes, Dupont et Ferrand. Noter que le résultat d'une requête est lui-même une relation, ici de degré 1 et de cardinalité 2 : c'est ce qui permet de composer les requêtes entre elles.

b)

La requête : SELECT valeur FROM note WHERE valeur > 12 ORDER BY valeur DESC. Les notes strictement supérieures à 12 sont 15, 14 et 17. Triées par ordre décroissant : 17, 15, 14. Sans la clause ORDER BY, l'ordre des lignes n'est pas garanti : il dépend du plan d'exécution choisi par le moteur et peut changer d'une exécution à l'autre, ce qui est une source classique de tests instables.

c)

La requête : SELECT DISTINCT eleve_id FROM note. Le résultat est 1, 2, 3, 4, soit quatre lignes, alors que la table en compte sept. Le mot-clé DISTINCT élimine les doublons du résultat : sans lui, l'identifiant 1 apparaîtrait deux fois et les identifiants 3 et 4 également. Il agit APRÈS la projection, ce qui est important : projeter d'abord peut créer des doublons qui n'existaient pas dans la table de départ, exactement comme au chapitre des données en tables.

d)

Sur ces données, WHERE valeur > 12 ramène 15, 14 et 17, soit trois lignes ; WHERE valeur >= 12 ramène en plus la note 12, soit quatre lignes. La différence porte donc exactement sur les lignes dont la valeur vaut la borne. Pour la classe : WHERE classe = '1G3' ramène les seuls élèves de 1G3, soit Dupont et Ferrand ; WHERE classe LIKE '1G%' ramène tous ceux dont le code commence par 1G, donc aussi Nadeau en 1G1, soit trois élèves. Le signe pour cent remplace n'importe quelle suite de caractères, y compris vide.

e)

Parce qu'en SQL, la valeur nulle ne signifie pas « une valeur particulière » mais « valeur inconnue ». Toute comparaison avec une valeur inconnue donne un résultat inconnu, ni vrai ni faux, et la clause WHERE ne retient que ce qui est VRAI : les lignes à valeur nulle sont donc écartées, y compris par une condition de différence. C'est la logique à trois valeurs de SQL, et elle surprend toujours. La bonne écriture est WHERE valeur <> 12 OR valeur IS NULL, ou plus simplement WHERE valeur IS NULL quand on cherche précisément ces lignes. On retient qu'on ne compare jamais une valeur nulle avec le signe égale : on utilise IS NULL et IS NOT NULL.

Exercice 5 : SQL : les jointures

Une jointure est la seule façon de faire parler ensemble deux tables reliées par une clé étrangère. C'est aussi la question la plus fréquente du baccalauréat sur ce chapitre.

Approfondissement, au-delà du programme, pour la question d) : le programme de Terminale demande la jointure JOIN ... ON ..., pas la jointure externe LEFT JOIN, qui n'est pas exigible. Les questions a), b), c) et e) sont au programme. La figure ci-dessous rappelle les deux tables et la clé étrangère qui les relie.

Pour les questions f) à i), toutes au programme, la table devoir contient trois lignes : le devoir 10, de matière 'maths' et de coefficient 4 ; le devoir 11, 'physique', coefficient 3 ; le devoir 12, 'NSI', coefficient 4. Sa clé primaire est id, et l'attribut devoir_id de la table note est une clé étrangère vers devoir.id.

idnomclasse1Dupont1G32Nadeau1G13Ferrand1G34OuelletTG2eleveideleve_iddevoir_idvaleur100110121011111510221091033101410431217105411810641211note
  • a) Écrivez la requête qui donne le nom de l'élève et la valeur de chacune de ses notes. Donnez le résultat complet, sept lignes.
  • b) Écrivez la requête qui donne le nom des élèves ayant obtenu une note strictement supérieure à 13. Donnez le résultat et expliquez comment éviter les doublons.
  • c) Expliquez ce que produit la même requête si l'on oublie la condition de jointure. Combien de lignes obtient-on ? Comment s'appelle ce résultat ?
  • d) Écrivez la requête qui donne le nom de TOUS les élèves accompagné de leurs notes, y compris ceux qui n'en ont aucune. Nommez le type de jointure employé et dites ce qui apparaît pour un élève sans note.
  • e) On ajoute la table devoir, d'attributs id, matiere et coefficient. Écrivez la requête à trois tables qui donne le nom de l'élève, la matière et la valeur, pour les seules notes de mathématiques.
  • f) Écrivez la requête qui donne le nombre de notes, la moyenne et la meilleure note des élèves de la classe 1G3. Donnez les trois valeurs.
  • g) Écrivez la requête qui donne le nom de l'élève, la matière et la valeur de chaque note de physique ou de NSI, de la plus haute à la plus basse. Donnez le résultat. Que renverrait la même requête avec AND à la place de OR ?
  • h) Écrivez la requête qui donne le nombre de notes de mathématiques obtenues par des élèves de 1G3, et leur moyenne. Donnez les deux valeurs.
  • i) Le professeur de physique fait passer un nouveau devoir, d'identifiant 13 et de coefficient 3 ; Nadeau y obtient 16, et cette note reçoit l'identifiant 107. Écrivez les deux commandes INSERT dans l'ordre où il faut les exécuter, et justifiez cet ordre. Combien de lignes renvoie ensuite la requête de la question g) ?

Tape tes réponses, la page te dit juste ou faux 0/16

a)
b)
c)
d)
e)
f)
g)
h)
i)
Voir la correction

Réponses

  • a) Jointure : 7 lignes
  • b) DISTINCT : Dupont et Ferrand
  • c) Sans condition : 28 lignes
  • d) LEFT JOIN, valeur nulle
  • e) Trois tables : 3 lignes
  • f) JOIN eleve, WHERE eleve.classe = '1G3' : 44 notes, moyenne 14,514{,}5, maximum 1717
  • g) Deux jointures, OR, ORDER BY note.valeur DESC : 44 lignes ; avec AND, aucune
  • h) WHERE eleve.classe = '1G3' AND devoir.matiere = 'maths' : 22 notes, moyenne 1313
  • i) Le devoir 13 d'abord (clé étrangère devoir_id), puis la note 107 ; g) renvoie alors 55 lignes
a)

La requête : SELECT eleve.nom, note.valeur FROM note JOIN eleve ON note.eleve_id = eleve.id. Le résultat, sept lignes : Dupont 12, Dupont 15, Nadeau 9, Ferrand 14, Ferrand 17, Ouellet 8, Ouellet 11. Chaque ligne de note trouve exactement un élève, puisque eleve_id est une clé étrangère non nulle vers une clé primaire : le nombre de lignes du résultat est donc égal au nombre de lignes de note.

b)

La requête : SELECT DISTINCT eleve.nom FROM note JOIN eleve ON note.eleve_id = eleve.id WHERE note.valeur > 13. Les notes strictement supérieures à 13 sont 15, appartenant à Dupont, 14 et 17, appartenant toutes deux à Ferrand. Sans DISTINCT, Ferrand apparaîtrait DEUX fois, une par note retenue. Le résultat correct est donc Dupont et Ferrand, deux lignes. Le réflexe à prendre : dès que la question porte sur les élèves et que le filtre porte sur les notes, il faut se demander si un élève peut satisfaire le filtre plusieurs fois.

c)

Sans condition de jointure, le moteur associe CHAQUE ligne de note à CHAQUE ligne de eleve, sans aucun rapport entre elles. On obtient 7×4=287 \times 4 = 28 lignes, dont 21 sont fausses : elles attribuent la note d'un élève à un autre. Ce résultat s'appelle le PRODUIT CARTÉSIEN. Il est particulièrement dangereux parce qu'il ne provoque aucune erreur : la requête s'exécute, renvoie des lignes plausibles, et les totaux calculés ensuite sont multipliés par quatre. Le symptôme à connaître est un nombre de lignes anormalement grand, souvent un multiple exact du nombre attendu.

d)

La requête : SELECT eleve.nom, note.valeur FROM eleve LEFT JOIN note ON note.eleve_id = eleve.id. Le type de jointure est la jointure EXTERNE gauche : elle conserve toutes les lignes de la table de gauche, ici eleve, même celles qui n'ont aucune correspondance à droite. Pour un élève sans note, la colonne valeur contient la valeur NULLE, et il faut alors se souvenir de la question e de l'exercice 4 : un calcul de moyenne ignorera cette ligne, mais un comptage de lignes, lui, la comptera. C'est exactement la différence entre COUNT(*) et COUNT(valeur).

e)

La requête : SELECT eleve.nom, devoir.matiere, note.valeur FROM note JOIN eleve ON note.eleve_id = eleve.id JOIN devoir ON note.devoir_id = devoir.id WHERE devoir.matiere = 'maths'. Le résultat contient trois lignes, les notes du devoir 10 : Dupont maths 12, Nadeau maths 9, Ferrand maths 14. Le principe se généralise : on part de la table centrale, celle qui porte les clés étrangères, et on ajoute une jointure par table à rejoindre, chacune avec sa propre condition. La table note est ici la table d'ASSOCIATION entre élèves et devoirs.

f)

La classe est une colonne de eleve, la valeur une colonne de note : il faut une jointure. La requête : SELECT COUNT(*), AVG(note.valeur), MAX(note.valeur) FROM note JOIN eleve ON note.eleve_id = eleve.id WHERE eleve.classe = '1G3'.

La jointure associe à chaque note son élève ; le WHERE garde les élèves de 1G3, Dupont et Ferrand, et leurs notes 12, 15, 14 et 17.

Les agrégats portent sur ces quatre lignes : COUNT(*) vaut 44, AVG vaut 12+15+14+174=584=14,5\frac{12+15+14+17}{4}=\frac{58}{4}=14{,}5 et MAX vaut 1717.

Contrôle : la moyenne 14,514{,}5 est comprise entre la plus basse, 1212, et la plus haute, 1717.

Piège : écrire SELECT AVG(valeur) FROM note WHERE classe = '1G3'. La table note n'a pas de colonne classe : la requête est refusée, et seule la jointure donne accès à la classe.

g)

La matière est dans devoir, le nom dans eleve : on part de note, la table qui porte les deux clés étrangères, et l'on fait deux jointures. La requête : SELECT eleve.nom, devoir.matiere, note.valeur FROM note JOIN eleve ON note.eleve_id = eleve.id JOIN devoir ON note.devoir_id = devoir.id WHERE devoir.matiere = 'physique' OR devoir.matiere = 'NSI' ORDER BY note.valeur DESC.

Le résultat, quatre lignes : Ferrand, NSI, 17 ; Dupont, physique, 15 ; Ouellet, NSI, 11 ; Ouellet, physique, 8.

Contrôle : la table compte 77 notes, dont 33 de mathématiques (question e) ; il en reste bien 7−3=47-3=4.

Avec AND, la condition demanderait qu'une même note soit à la fois de physique et de NSI : aucune ligne ne la vérifie, et le résultat est vide, sans message d'erreur.

Piège : traduire « physique ou NSI » par AND parce que la phrase dit « les notes de physique et de NSI ». Le WHERE teste UNE ligne à la fois : chaque note n'a qu'une matière.

h)

Deux conditions qui doivent être vraies ensemble, cette fois : AND. La requête : SELECT COUNT(*), AVG(note.valeur) FROM note JOIN eleve ON note.eleve_id = eleve.id JOIN devoir ON note.devoir_id = devoir.id WHERE eleve.classe = '1G3' AND devoir.matiere = 'maths'.

Les notes de mathématiques sont celles du devoir 10 : Dupont 12, Nadeau 9 et Ferrand 14. Nadeau est en 1G1 : il reste Dupont et Ferrand.

COUNT(*) vaut 22 et AVG vaut 12+142=13\frac{12+14}{2}=13.

Contrôle : la question f) donnait 44 notes pour la classe 1G3 ; les deux autres, 15 et 17, sont de physique et de NSI.

i)

L'ordre est imposé par la clé étrangère : note.devoir_id doit désigner un devoir qui EXISTE déjà dans la table devoir.

On insère donc d'abord le devoir : INSERT INTO devoir (id, matiere, coefficient) VALUES (13, 'physique', 3).

Puis la note : INSERT INTO note (id, eleve_id, devoir_id, valeur) VALUES (107, 2, 13, 16). Nadeau a l'identifiant 2, qui existe dans eleve : la seconde clé étrangère est satisfaite aussi.

Dans l'ordre inverse, la première commande serait refusée au nom de l'intégrité référentielle, comme à l'exercice 2 : aucun devoir 13 n'existerait encore.

La requête de g) renvoie ensuite cinq lignes : Ferrand, NSI, 17 ; Nadeau, physique, 16 ; Dupont, physique, 15 ; Ouellet, NSI, 11 ; Ouellet, physique, 8.

Piège : chercher Nadeau par son nom dans la commande INSERT. La table note ne connaît que eleve_id : c'est l'identifiant 2 qu'on écrit.

Partie B : Problèmes et raisonnement (/50)

Exercice 6 : SQL : agrégats, regroupements et filtres sur les groupes

Un agrégat remplace plusieurs lignes par une valeur. C'est là que SQL devient un outil de calcul, et c'est là que les copies confondent deux clauses qui n'ont rien à voir, WHERE et HAVING.

Approfondissement, hors programme, pour les questions b), c) et d) : le programme de Terminale autorise les fonctions d'agrégation COUNT, SUM, AVG, MIN et MAX « sans utiliser les clauses GROUP BY et HAVING », qu'il exclut en toutes lettres. Les questions a) et e), qui agrègent sans regrouper, sont au programme. L'exercice 16 montre comment obtenir un résultat « pour chaque » valeur avec les seules clauses exigibles : une requête par valeur, filtrée dans le WHERE. La figure ci-dessous rappelle les trois tables de la base et leurs clés étrangères.

idnomclasse1Dupont1G32Nadeau1G13Ferrand1G34OuelletTG2eleveidmatierecoefficient10maths411physique312NSI4devoirideleve_iddevoir_idvaleur100110121011111510221091033101410431217105411810641211note
  • a) Écrivez la requête qui donne le nombre de notes, la note minimale, la note maximale et la moyenne générale. Calculez les quatre valeurs.
  • b) Écrivez la requête qui donne, pour chaque élève, son nom et sa moyenne. Calculez les quatre moyennes au centième près.
  • c) Écrivez la requête qui ne garde que les élèves dont la moyenne atteint 12. Quelle clause faut-il employer, et pourquoi WHERE ne convient-il pas ?
  • d) Écrivez la requête qui donne, pour chaque matière, le nombre de notes et la moyenne. Calculez les valeurs pour les trois matières.
  • e) On veut la moyenne générale PONDÉRÉE par les coefficients, sachant que maths vaut 4, physique 3 et NSI 4. Écrivez la requête et calculez le résultat. Comparez à la moyenne de la question a et expliquez l'écart.

Tape tes réponses, la page te dit juste ou faux 0/15

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 7 notes, min 8, max 17, moyenne 12,2912{,}29
  • b) 13,5013{,}50 ; 9,009{,}00 ; 15,5015{,}50 ; 9,509{,}50
  • c) HAVING filtre les groupes
  • d) Maths 11,6711{,}67, physique 11,5011{,}50, NSI 14,0014{,}00
  • e) 321/26≈12,35321 / 26 \approx 12{,}35
a)

La requête : SELECT COUNT(*), MIN(valeur), MAX(valeur), AVG(valeur) FROM note. Les sept notes sont 12, 15, 9, 14, 17, 8 et 11. Le compte vaut 7, le minimum 8, le maximum 17, et la moyenne 86/7≈12,2986 / 7 \approx 12{,}29. Attention à COUNT : COUNT(*) compte les LIGNES, COUNT(valeur) compte les valeurs non nulles, et les deux diffèrent dès qu'une valeur nulle existe.

b)

La requête : SELECT eleve.nom, AVG(note.valeur) FROM note JOIN eleve ON note.eleve_id = eleve.id GROUP BY eleve.id, eleve.nom. Les moyennes : Dupont, notes 12 et 15, moyenne 27/2=13,5027 / 2 = 13{,}50. Nadeau, note 9 seule, moyenne 9,009{,}00. Ferrand, notes 14 et 17, moyenne 31/2=15,5031 / 2 = 15{,}50. Ouellet, notes 8 et 11, moyenne 19/2=9,5019 / 2 = 9{,}50. Il faut regrouper sur l'identifiant et non sur le seul nom : deux homonymes seraient sinon fondus dans un même groupe, ce qui est précisément le défaut que la clé primaire servait à éviter.

c)

La requête : la même, complétée par HAVING AVG(note.valeur) >= 12. Le résultat contient Dupont, moyenne 13,50, et Ferrand, moyenne 15,50. La clause WHERE ne convient pas parce qu'elle s'applique AVANT le regroupement, donc à des lignes individuelles, alors que la moyenne n'existe qu'après regroupement. Écrire WHERE AVG(...) provoque une erreur de syntaxe dans tout moteur correct. L'ordre logique d'exécution est immuable : FROM, puis JOIN, puis WHERE, puis GROUP BY, puis HAVING, puis SELECT, puis ORDER BY. WHERE filtre des lignes, HAVING filtre des groupes.

d)

La requête : SELECT devoir.matiere, COUNT(*), AVG(note.valeur) FROM note JOIN devoir ON note.devoir_id = devoir.id GROUP BY devoir.matiere. Mathématiques, devoir 10 : notes 12, 9 et 14, soit 3 notes et une moyenne de 35/3≈11,6735 / 3 \approx 11{,}67. Physique, devoir 11 : notes 15 et 8, soit 2 notes et une moyenne de 23/2=11,5023 / 2 = 11{,}50. NSI, devoir 12 : notes 17 et 11, soit 2 notes et une moyenne de 28/2=14,0028 / 2 = 14{,}00. On vérifie le total des effectifs : 3+2+2=73 + 2 + 2 = 7, ce qui correspond bien au nombre de notes.

e)

La requête : SELECT SUM(note.valeur * devoir.coefficient) / SUM(devoir.coefficient) FROM note JOIN devoir ON note.devoir_id = devoir.id. Le numérateur vaut 12×4+15×3+9×4+14×4+17×4+8×3+11×4=48+45+36+56+68+24+44=32112 \times 4 + 15 \times 3 + 9 \times 4 + 14 \times 4 + 17 \times 4 + 8 \times 3 + 11 \times 4 = 48 + 45 + 36 + 56 + 68 + 24 + 44 = 321. Le dénominateur vaut 4+3+4+4+4+3+4=264 + 3 + 4 + 4 + 4 + 3 + 4 = 26. La moyenne pondérée vaut donc 321/26≈12,35321 / 26 \approx 12{,}35, contre 12,2912{,}29 pour la moyenne simple. L'écart est faible mais réel, et il vient de ce que les matières à coefficient 4 portent ici des notes légèrement meilleures. Le point de méthode est essentiel : on ne calcule JAMAIS une moyenne pondérée en moyennant des moyennes, on somme les produits et on divise une seule fois, exactement comme au chapitre des données en tables.

Exercice 7 : Écrire dans la base : insertion, mise à jour, suppression

Lire une base ne l'abîme jamais. Écrire dedans, si. Les trois commandes d'écriture sont simples, et c'est précisément ce qui les rend dangereuses.

  • a) Écrivez la commande qui inscrit l'élève Tremblay en TG2, d'identifiant 5. Que se passe-t-il si l'identifiant 5 existe déjà ?
  • b) Écrivez la commande qui remplace par 13 la note d'identifiant 102. Écrivez ensuite celle qui augmente d'un point toutes les notes du devoir 11, et donnez les nouvelles valeurs.
  • c) Un élève oublie la clause WHERE dans la commande de la question b. Décrivez précisément le résultat sur les sept notes, et dites pourquoi aucun message d'erreur n'apparaît.
  • d) Écrivez la commande qui supprime toutes les notes inférieures à 10. Combien de lignes sont supprimées ? Quelle vérification faire AVANT de l'exécuter ?
  • e) Citez quatre précautions à prendre avant toute écriture sur une base en service, et dites laquelle protège contre l'erreur de la question c.

Tape tes réponses, la page te dit juste ou faux 0/7

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) INSERT refusé si la clé existe
  • b) UPDATE : 16 et 9
  • c) Sans WHERE : 7 lignes modifiées
  • d) DELETE : 2 lignes, SELECT d'abord
  • e) Sauvegarde, SELECT, transaction, droits
a)

La commande : INSERT INTO eleve (id, nom, classe) VALUES (5, 'Tremblay', 'TG2'). Si l'identifiant 5 existe déjà, l'insertion est REFUSÉE et une erreur de violation de contrainte d'unicité est levée : la clé primaire garantit qu'aucune ligne existante ne sera écrasée. C'est un comportement voulu, et c'est ce qui distingue une insertion d'une mise à jour. Certains moteurs proposent une variante qui met à jour la ligne si elle existe, mais elle doit être demandée explicitement, jamais subie.

b)

Première commande : UPDATE note SET valeur = 13 WHERE id = 102. Seconde commande : UPDATE note SET valeur = valeur + 1 WHERE devoir_id = 11. Les notes du devoir 11 sont 15, pour Dupont, et 8, pour Ouellet : elles deviennent 16 et 9. Noter l'écriture valeur = valeur + 1, qui lit et écrit le même attribut dans la même commande : c'est parfaitement légal, le moteur évaluant le membre droit sur la valeur d'avant la mise à jour.

c)

Sans clause WHERE, la mise à jour porte sur TOUTES les lignes de la table. Les sept notes 12, 15, 9, 14, 17, 8 et 11 deviennent 13, 16, 10, 15, 18, 9 et 12. Aucun message d'erreur n'apparaît parce que la commande est parfaitement valide : l'absence de WHERE signifie « toutes les lignes », ce qui est un usage légitime dans d'autres contextes. Le moteur exécute donc exactement ce qui lui a été demandé, et il annonce même le succès en indiquant sept lignes modifiées. Ce nombre est d'ailleurs le seul signal disponible : voir sept là où l'on attendait deux est ce qui doit alerter.

d)

La commande : DELETE FROM note WHERE valeur < 10. Les notes strictement inférieures à 10 sont 9 et 8, donc DEUX lignes sont supprimées. La vérification à faire avant est de remplacer DELETE par SELECT, en gardant exactement la même clause WHERE : SELECT * FROM note WHERE valeur < 10 affiche les lignes qui seront détruites, sans rien détruire. Cette habitude, qui coûte cinq secondes, est la plus rentable de tout le chapitre : elle transforme une opération irréversible en une opération vérifiée.

e)

Quatre précautions. Un, SAUVEGARDER la base avant toute opération d'écriture en masse. Deux, ÉCRIRE LA REQUÊTE EN SELECT D'ABORD, avec le même filtre, pour voir ce qui sera touché. Trois, travailler dans une TRANSACTION, afin de pouvoir tout annuler si le nombre de lignes affectées ne correspond pas à l'attendu. Quatre, limiter les DROITS du compte utilisé : un compte de lecture seule pour les requêtes d'analyse rend impossible toute écriture accidentelle. C'est la deuxième qui protège directement de l'erreur de la question c, puisqu'elle aurait affiché sept lignes au lieu de deux avant toute modification ; la troisième aurait permis de réparer après coup.

Exercice 8 : Cinq affirmations à corriger

Chacune des cinq affirmations suivantes est FAUSSE. Dites pourquoi et donnez l'énoncé correct.

L'affirmation 2 porte sur la clause HAVING, que le programme de Terminale exclut avec GROUP BY : elle reste ici comme approfondissement. Ce qui est exigible dans sa correction : WHERE filtre des lignes, et une fonction d'agrégation ne s'écrit jamais dans un WHERE.

  • 1) « L'attribut nom peut servir de clé primaire tant qu'il n'y a pas de doublon. »
  • 2) « WHERE et HAVING font la même chose, HAVING servant seulement après un GROUP BY. »
  • 3) « Une jointure sans condition renvoie une erreur. »
  • 4) « WHERE valeur <> 12 ramène toutes les lignes dont la valeur n'est pas 12. »
  • 5) « Recopier le nom de l'élève dans la table des notes évite une jointure, donc c'est plus rapide. »

Tape tes réponses, la page te dit juste ou faux 0/5

1)
2)
3)
4)
5)
Voir la correction

Réponses

  • 1) Clé primaire stable
  • 2) WHERE avant, HAVING après
  • 3) Produit cartésien silencieux
  • 4) Les nulls sont écartés
  • 5) Dénormaliser coûte plus
1)

FAUX. Une clé primaire doit être unique, non nulle et STABLE. L'absence actuelle de doublon ne garantit rien pour l'avenir : deux homonymes finiront par arriver et la base refusera alors d'enregistrer le second. De plus un nom change, et toutes les clés étrangères qui le référenceraient devraient être mises à jour. Énoncé correct : une clé primaire doit être un identifiant artificiel, stable et sans signification métier, sauf clé naturelle réellement immuable.

2)

FAUX, elles ne s'appliquent pas au même objet. WHERE filtre des LIGNES, avant tout regroupement, et ne peut donc pas porter sur un agrégat. HAVING filtre des GROUPES, après regroupement, et porte précisément sur des agrégats. Énoncé correct : WHERE s'exécute avant GROUP BY et HAVING après ; écrire WHERE AVG(...) est une erreur de syntaxe, et écrire HAVING sur une colonne non agrégée est au mieux inutile.

3)

FAUX, et c'est bien le problème. Le moteur produit le PRODUIT CARTÉSIEN, c'est-à-dire toutes les associations possibles : 7×4=287 \times 4 = 28 lignes ici, dont 21 sont fausses. Aucune erreur n'est signalée. Énoncé correct : une jointure sans condition est légale et renvoie le produit cartésien ; le symptôme est un nombre de lignes anormalement grand, souvent un multiple exact du nombre attendu.

4)

FAUX pour les lignes dont la valeur est NULLE. En SQL, la valeur nulle signifie « inconnue », et toute comparaison avec elle donne un résultat inconnu, que la clause WHERE écarte. Énoncé correct : WHERE valeur <> 12 ramène les lignes dont la valeur est connue et différente de 12 ; pour inclure les valeurs nulles il faut écrire WHERE valeur <> 12 OR valeur IS NULL.

5)

FAUX sur les deux points. Sur le fond, cette dénormalisation crée les trois anomalies de l'exercice 3 : mise à jour, insertion et suppression, et elle supprime la contrainte d'intégrité qui vérifiait le lien. Sur la performance, une jointure sur une clé primaire indexée coûte à peine plus qu'une lecture directe, alors que le nom recopié alourdit chaque ligne et donc chaque lecture. Énoncé correct : on ne dénormalise que sur mesure démontrée, jamais par principe, et jamais sur des données susceptibles de changer.

Exercice 9 : Transactions : ce que garantit une base de données

Le chronogramme montre deux transactions qui manipulent le même solde, initialement de 100. Chacune est correcte prise isolément, et pourtant le résultat final est faux.

T1T2lit le solde : 100lit le solde : 100écrit 100 - 30 = 70écrit 100 - 50 = 50validevalidetemps
  • a) Suivez le chronogramme et donnez le solde final. Quel devrait-il être ? Quelle mise à jour a été perdue, et pourquoi ?
  • b) Définissez une transaction. Énoncez les quatre propriétés qu'un système de gestion de base de données garantit, et donnez pour chacune une phrase d'explication.
  • c) Laquelle des quatre propriétés est violée sur le chronogramme ? Décrivez le mécanisme qui l'empêche.
  • d) Un virement retire 200 d'un compte et les ajoute à un autre. La machine tombe en panne entre les deux écritures. Quelle propriété protège la base, et que trouve-t-on au redémarrage ?
  • e) Deux transactions attendent chacune une ressource détenue par l'autre. Décrivez la situation, nommez-la, et citez deux façons de la traiter.

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Solde 50 au lieu de 20 : mise à jour perdue
  • b) ACID
  • c) Isolation violée : verrouillage
  • d) Atomicité : virement annulé
  • e) Interblocage : détection ou prévention
a)

T1 lit 100, T2 lit 100, T1 écrit 100−30=70100 - 30 = 70, T2 écrit 100−50=50100 - 50 = 50, puis les deux valident. Le solde final vaut 50, puisque l'écriture de T2 recouvre celle de T1. Il devrait valoir 100−30−50=20100 - 30 - 50 = 20. La mise à jour perdue est celle de T1 : T2 avait lu le solde AVANT que T1 ne l'écrive, donc son calcul reposait sur une valeur devenue périmée, et son écriture a effacé le travail de T1 sans même s'en apercevoir. Ce défaut porte un nom, la mise à jour perdue, et il ne se produit que si les deux transactions se chevauchent.

b)

Une transaction est un groupe d'opérations traité comme une SEULE opération indivisible du point de vue de la base. Les quatre propriétés garanties sont l'atomicité, la cohérence, l'isolation et la durabilité. ATOMICITÉ : la transaction est exécutée entièrement ou pas du tout, jamais à moitié. COHÉRENCE : elle fait passer la base d'un état vérifiant toutes les contraintes à un autre état les vérifiant également. ISOLATION : son déroulement ne doit pas être perturbé par les transactions concurrentes, et le résultat doit être le même que si elles s'étaient exécutées l'une après l'autre. DURABILITÉ : une fois validée, elle survit à toute panne, y compris une coupure de courant immédiate.

c)

C'est l'ISOLATION qui est violée : le résultat obtenu, 50, n'est celui d'aucune exécution où T1 et T2 se suivraient. En les exécutant l'une puis l'autre, dans un ordre ou dans l'autre, on obtiendrait toujours 20. Le mécanisme qui l'empêche est le VERROUILLAGE : lorsqu'une transaction lit une donnée qu'elle compte modifier, elle pose un verrou dessus, et toute autre transaction qui tente d'y accéder est mise en attente jusqu'à la validation de la première. T2 aurait alors attendu, puis lu 70 au lieu de 100, et écrit 20. Une variante répandue, plus rapide, laisse les deux avancer mais vérifie à la validation que la donnée n'a pas changé depuis la lecture, et rejoue la transaction perdante.

d)

C'est l'ATOMICITÉ qui protège la base, secondée par la durabilité. La transaction n'ayant pas été validée au moment de la panne, elle est ANNULÉE dans son intégralité : au redémarrage, le système relit son journal, constate qu'une transaction est restée incomplète, et défait ses écritures partielles. On retrouve donc les deux comptes exactement dans leur état d'avant le virement : les 200 n'ont ni disparu ni été dupliqués. Sans cette garantie, une panne au mauvais moment ferait s'évaporer de l'argent, ce qui est la raison historique pour laquelle les bases de données existent.

e)

La situation : T1 détient un verrou sur la ressource A et demande B, tandis que T2 détient un verrou sur B et demande A. Aucune des deux ne peut avancer, et aucune ne relâchera son verrou : elles attendront indéfiniment. Cela s'appelle un INTERBLOCAGE. Deux façons de le traiter. La DÉTECTION : le système construit le graphe des attentes, y cherche un cycle, et lorsqu'il en trouve un, choisit une victime dont il annule la transaction pour libérer ses verrous ; la transaction annulée est ensuite rejouée. La PRÉVENTION : on impose à toutes les transactions de demander leurs verrous dans un ordre total fixé à l'avance, par exemple par ordre croissant d'identifiant de ressource ; aucun cycle ne peut alors se former, mais la contrainte est lourde à respecter dans un programme réel.

Exercice 10 : Problème : la base de la bibliothèque du lycée

Le schéma donne les quatre relations d'une base de gestion de prêts. Un livre est identifié par son ISBN ; la bibliothèque peut en posséder plusieurs exemplaires ; un emprunt concerne un exemplaire précis et un élève.

Approfondissement pour la question d) : le classement des livres par nombre d'emprunts demande GROUP BY, que le programme de Terminale exclut, et LIMIT, qui ne figure pas dans sa liste. Avec les clauses exigibles, on compte les emprunts d'UN livre donné, par SELECT COUNT(*) sur les deux jointures et un WHERE livre.isbn = ... ; les questions a), b), c) et e) sont au programme.

Pour les questions f) à i), toutes au programme, les requêtes emploient les attributs livre.isbn et livre.titre, exemplaire.id et exemplaire.isbn, eleve.id et eleve.nom, et ceux de la table emprunt, dans cet ordre : id, exemplaire_id, eleve_id, date_sortie, date_retour. On travaille sur un extrait de la base. Élèves : Roy (identifiant 31), Gagnon (32), Lambert (33) et Bouchard (34). Livres : Germinal, dont la bibliothèque possède les exemplaires 501 et 502, et Candide, l'exemplaire 503 ; aucun autre exemplaire n'est enregistré. Emprunts : (810, 501, 31, '2026-09-08', '2026-09-22'), (811, 502, 33, '2026-09-15', '2026-09-29'), (812, 501, 32, '2026-10-01', NULL), (813, 503, 34, '2026-10-05', NULL) et (814, 502, 31, '2026-10-09', NULL).

elevelivreexemplaireemprunt
  • a) Donnez les attributs raisonnables de chacune des quatre relations, en soulignant par écrit quelle est la clé primaire et quelles sont les clés étrangères.
  • b) Pourquoi la table exemplaire existe-t-elle ? Que se passerait-il si l'on faisait pointer emprunt directement sur livre ?
  • c) Écrivez la requête qui donne le titre des livres actuellement empruntés, avec le nom de l'emprunteur. On considère qu'un emprunt en cours a une date de retour nulle.
  • d) Écrivez la requête qui donne les cinq livres les plus empruntés, avec leur nombre d'emprunts. Précisez les clauses employées et leur ordre.
  • e) La bibliothèque compte 4 200 livres, 6 800 exemplaires, 1 100 élèves et 52 000 emprunts sur dix ans. Estimez le nombre de lignes examinées par la requête de la question d selon qu'un index existe ou non sur la colonne de jointure, et concluez.
  • f) Gagnon rapporte le 20 octobre 2026 l'exemplaire de l'emprunt 812. Écrivez la commande qui enregistre ce retour. Combien de lignes renvoie ensuite la requête de la question c) sur cet extrait ?
  • g) Lambert veut emprunter Candide le 21 octobre 2026. Un élève propose INSERT INTO emprunt VALUES (815, 503, 33, '2026-10-21', NULL). La base accepte-t-elle cette commande ? Écrivez la requête qu'il aurait fallu exécuter avant, et donnez son résultat. Que se passe-t-il si l'on tape 507 au lieu de 503 ?
  • h) Écrivez la requête qui donne le nombre total d'emprunts du livre Germinal, et donnez son résultat sur l'extrait. Combien de jointures faut-il si l'on connaît l'ISBN du livre plutôt que son titre ?
  • i) Écrivez la requête qui donne le nom des élèves ayant emprunté Germinal, sans doublon et par ordre alphabétique. Donnez le résultat et expliquez pourquoi il compte moins de lignes que le nombre trouvé en h).

Tape tes réponses, la page te dit juste ou faux 0/17

a)
b)
c)
d)
e)
f)
g)
h)
i)
Voir la correction

Réponses

  • a) Quatre relations, emprunt associe
  • b) Exemplaire distinct du livre
  • c) Trois jointures, IS NULL
  • d) GROUP BY, ORDER BY, LIMIT 5
  • e) 353,6353{,}6 millions contre 676 000676\ 000 : indexer
  • f) UPDATE emprunt SET date_retour = '2026-10-20' WHERE id = 812 ; 22 emprunts en cours
  • g) Acceptée, alors que l'exemplaire 503 est sorti (contrôle : 11) ; avec 507, refusée par la clé étrangère
  • h) Deux jointures, WHERE livre.titre = 'Germinal' : 44 emprunts ; une seule jointure avec l'ISBN
  • i) DISTINCT et ORDER BY eleve.nom : Gagnon, Lambert, Roy
a)

La relation livre : isbn, clé primaire ; titre ; auteur ; annee. La relation exemplaire : id, clé primaire ; isbn, clé étrangère vers livre ; etat ; date_achat. La relation eleve : id, clé primaire ; nom ; prenom ; classe. La relation emprunt : id, clé primaire ; exemplaire_id, clé étrangère vers exemplaire ; eleve_id, clé étrangère vers eleve ; date_sortie ; date_retour, qui peut être nulle. La table emprunt porte donc deux clés étrangères : c'est une table d'ASSOCIATION entre deux entités, comme la table note l'était entre élèves et devoirs.

b)

La table exemplaire existe parce qu'un livre et un exemplaire ne sont pas la même chose : le livre est une oeuvre, décrite par son titre et son auteur ; l'exemplaire est un objet physique, qui a un état, une date d'achat, et qui peut être perdu ou abîmé indépendamment des autres. Si emprunt pointait directement sur livre, la base ne pourrait plus dire QUEL exemplaire est sorti : elle serait incapable de gérer deux emprunts simultanés du même titre, ni de savoir lequel des trois exemplaires n'est jamais revenu, ni de retirer un exemplaire du circuit sans toucher aux autres. On perdrait aussi la possibilité de compter les exemplaires disponibles, qui est pourtant la question la plus fréquente d'un usager.

c)

La requête : SELECT livre.titre, eleve.nom FROM emprunt JOIN exemplaire ON emprunt.exemplaire_id = exemplaire.id JOIN livre ON exemplaire.isbn = livre.isbn JOIN eleve ON emprunt.eleve_id = eleve.id WHERE emprunt.date_retour IS NULL. Il faut trois jointures parce que le titre est à deux tables de distance de l'emprunt, en passant par l'exemplaire. Et il faut écrire IS NULL, jamais un signe égale suivi de NULL, pour la raison vue à l'exercice 4.

d)

La requête : SELECT livre.titre, COUNT(*) AS n FROM emprunt JOIN exemplaire ON emprunt.exemplaire_id = exemplaire.id JOIN livre ON exemplaire.isbn = livre.isbn GROUP BY livre.isbn, livre.titre ORDER BY n DESC LIMIT 5. Les clauses s'exécutent dans cet ordre : FROM et les jointures rassemblent les lignes ; GROUP BY les regroupe par livre ; COUNT compte chaque groupe ; ORDER BY trie les groupes par effectif décroissant ; LIMIT ne garde que les cinq premiers. Le regroupement porte sur l'ISBN et non sur le seul titre, car deux livres différents peuvent porter le même titre.

e)

Sans index sur exemplaire.id, chaque ligne d'emprunt oblige à parcourir toute la table exemplaire pour trouver sa correspondance : cela fait 52 000×6 800=353 600 00052\ 000 \times 6\ 800 = 353\ 600\ 000 lignes examinées, plus 52 000×4 200=218 400 00052\ 000 \times 4\ 200 = 218\ 400\ 000 pour la seconde jointure. Avec un index, chaque recherche coûte de l'ordre de log⁡2(6 800)≈12,7\log_{2}(6\ 800) \approx 12{,}7 comparaisons, donc environ 52 000×13=676 00052\ 000 \times 13 = 676\ 000 opérations, soit environ 523523 fois moins. En pratique, la clé primaire étant automatiquement indexée par tout moteur, la version efficace est celle que l'on obtient par défaut ; le piège survient quand la jointure porte sur une colonne NON indexée, ce qui est précisément le cas des clés étrangères, que la plupart des moteurs n'indexent PAS automatiquement. La conclusion pratique est donc simple : indexer toutes les clés étrangères, systématiquement.

f)

Un retour ne crée pas de ligne : il complète l'emprunt existant. La commande : UPDATE emprunt SET date_retour = '2026-10-20' WHERE id = 812.

Avant, trois emprunts avaient une date de retour nulle : 812, 813 et 814. Après, il en reste deux, 813 et 814 : la requête de c) renvoie 22 lignes, Candide avec Bouchard et Germinal avec Roy.

Piège : écrire WHERE exemplaire_id = 501. L'exemplaire 501 a déjà servi à l'emprunt 810 : la commande modifierait DEUX lignes et écraserait la date de retour du 22 septembre. Seule la clé primaire id désigne à coup sûr une seule ligne.

g)

La base ACCEPTE la commande : l'identifiant 815 est neuf, ce qui respecte la clé primaire ; l'exemplaire 503 et l'élève 33 existent, ce qui respecte les deux clés étrangères.

Pourtant l'exemplaire 503 n'est pas rentré : l'emprunt 813 de Bouchard a une date de retour nulle. Le schéma n'exprime pas la règle « un exemplaire n'est prêté qu'à un élève à la fois » ; c'est au programme qui écrit dans la base de la vérifier.

La requête à exécuter avant : SELECT COUNT(*) FROM emprunt WHERE exemplaire_id = 503 AND date_retour IS NULL. Elle renvoie 11 : l'exemplaire est sorti, on ne l'enregistre pas.

Avec 507, la commande est REFUSÉE : aucun exemplaire d'identifiant 507 n'existe, et la clé étrangère exemplaire_id viole l'intégrité référentielle. Rien n'est écrit.

Piège : croire que les contraintes d'intégrité garantissent toutes les règles de la bibliothèque. Elles garantissent que la ligne désigne des objets qui existent, pas que la situation décrite soit possible.

h)

Le titre est dans livre, l'emprunt pointe vers exemplaire : deux jointures. La requête : SELECT COUNT(*) FROM emprunt JOIN exemplaire ON emprunt.exemplaire_id = exemplaire.id JOIN livre ON exemplaire.isbn = livre.isbn WHERE livre.titre = 'Germinal'.

Les exemplaires de Germinal sont 501 et 502 ; ils apparaissent dans les emprunts 810, 811, 812 et 814. Le résultat est 44. Le retour enregistré en f) n'y change rien : on compte tous les emprunts, rendus ou non.

Si l'on connaît l'ISBN, la table livre devient inutile, car exemplaire porte déjà l'ISBN : une seule jointure, emprunt JOIN exemplaire ON emprunt.exemplaire_id = exemplaire.id, et le WHERE porte directement sur exemplaire.isbn.

Remarque : le filtre sur le titre suppose qu'un seul livre s'appelle Germinal. Deux éditions différentes ont deux ISBN, comme le notait le corrigé de d) ; le filtre sur l'ISBN est le seul sûr.

i)

La requête : SELECT DISTINCT eleve.nom FROM emprunt JOIN exemplaire ON emprunt.exemplaire_id = exemplaire.id JOIN livre ON exemplaire.isbn = livre.isbn JOIN eleve ON emprunt.eleve_id = eleve.id WHERE livre.titre = 'Germinal' ORDER BY eleve.nom.

Les quatre emprunts de h) sont ceux des élèves 31, 33, 32 et 31, soit Roy, Lambert, Gagnon et Roy. DISTINCT supprime le second Roy, et ORDER BY range les noms : Gagnon, Lambert, Roy, trois lignes.

Il y a moins de lignes qu'en h) parce que h) compte des EMPRUNTS et i) des ÉLÈVES : Roy a emprunté Germinal deux fois, en septembre puis en octobre.

Piège : oublier DISTINCT. La requête renvoie alors quatre lignes, dont Roy deux fois, et le nombre d'élèves annoncé est faux.

eleveidnomprenomclasseempruntid#exemplaire_id#eleve_iddate_sortiedate_retourexemplaireid#isbnetatdate_achatlivreisbntitreauteurannee

Partie C : les classiques (/50)

Exercice 11 : Les sous-requêtes

Une requête peut en contenir une autre, entre parenthèses, dont le résultat sert de valeur ou d'ensemble de valeurs. On reprend les tables eleve de l'exercice 1 et note de l'exercice 4.

On rappelle que la moyenne des sept notes vaut 867≈12,29\frac{86}{7} \approx 12{,}29.

Approfondissement, au-delà du programme : les sous-requêtes, et avec elles IN, NOT IN et EXISTS, ne figurent pas dans la liste des clauses du programme de Terminale (SELECT, FROM, WHERE, JOIN, avec DISTINCT, ORDER BY et les fonctions d'agrégation, pour l'interrogation ; INSERT, UPDATE, DELETE pour la mise à jour). Tout l'exercice est donc un approfondissement ; la question b) montre d'ailleurs qu'une jointure, elle au programme, fait le même travail qu'un IN.

  • a) Que renvoie SELECT valeur FROM note WHERE valeur > (SELECT AVG(valeur) FROM note) ? Pourquoi ne peut-on pas écrire directement WHERE valeur > AVG(valeur) ?
  • b) Que renvoie SELECT nom FROM eleve WHERE id IN (SELECT eleve_id FROM note WHERE devoir_id = 12) ? Réécrivez cette requête avec une jointure.
  • c) Écrivez la requête qui donne le nom des élèves qui n'ont PAS de note au devoir 10, et donnez son résultat.
  • d) Écrivez la requête qui donne le nom de l'élève ayant obtenu la meilleure note au devoir 10, sans écrire la valeur de cette note dans la requête. Donnez le résultat.
  • e) On insère dans note une ligne dont eleve_id est nul. La requête de la question c) ne renvoie alors plus aucune ligne, alors qu'aucun élève n'a changé. Expliquez, et corrigez la requête.

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Notes au-dessus de la moyenne : 15, 14, 17
  • b) IN : Ferrand et Ouellet
  • c) NOT IN : Ouellet
  • d) Meilleure note du devoir 10 : Ferrand
  • e) NOT IN avec NULL : aucune ligne
a)

La sous-requête calcule la moyenne, 12,2912{,}29, puis la requête principale garde les notes qui la dépassent : 15, 14 et 17, soit 3 lignes. On ne peut pas écrire WHERE valeur > AVG(valeur), parce que WHERE examine les lignes une par une, avant tout calcul d'agrégat : la moyenne n'existe pas encore à ce moment. La sous-requête la calcule à part, et son résultat devient une simple constante pour la requête principale.

b)

La sous-requête renvoie les identifiants des élèves notés au devoir 12, soit 3 et 4. La requête renvoie donc Ferrand et Ouellet. Avec une jointure : SELECT DISTINCT eleve.nom FROM eleve JOIN note ON note.eleve_id = eleve.id WHERE note.devoir_id = 12. Le DISTINCT est indispensable dans la version avec jointure si un élève peut avoir plusieurs notes au même devoir ; la version avec IN, elle, ne produit jamais de doublon, puisqu'elle ne fait que tester l'appartenance de chaque élève à un ensemble.

c)

SELECT nom FROM eleve WHERE id NOT IN (SELECT eleve_id FROM note WHERE devoir_id = 10). Les élèves notés au devoir 10 sont 1, 2 et 3 : le résultat est Ouellet, seul. Une jointure ne permet pas d'écrire aussi simplement cette requête, parce qu'elle associe des lignes qui EXISTENT, alors qu'on cherche ici une absence.

d)

SELECT eleve.nom FROM eleve JOIN note ON note.eleve_id = eleve.id WHERE note.devoir_id = 10 AND note.valeur = (SELECT MAX(valeur) FROM note WHERE devoir_id = 10). Les notes du devoir 10 sont 12, 9 et 14 : le maximum vaut 14 et le résultat est Ferrand. En cas d'égalité au maximum, la requête renverrait tous les élèves concernés, ce qui est le comportement correct, alors qu'un tri suivi de LIMIT 1 en choisirait un arbitrairement.

e)

Si le devoir 10 porte une ligne à eleve_id nul, la sous-requête renvoie 1, 2, 3 et NULL. La condition id NOT IN (1, 2, 3, NULL) signifie id <> 1 AND id <> 2 AND id <> 3 AND id <> NULL ; or la dernière comparaison donne toujours un résultat INCONNU, donc la conjonction n'est jamais vraie, et aucune ligne n'est retenue. C'est la logique à trois valeurs de l'exercice 4, dans sa forme la plus traîtresse. Correction : exclure les nulls dans la sous-requête, en ajoutant AND eleve_id IS NOT NULL, ou employer NOT EXISTS, qui n'a pas ce défaut.

Exercice 12 : Créer les tables

Avant de lire ou d'écrire, il faut créer les tables et déclarer leurs contraintes. Un élève a écrit le script suivant pour la base des exercices précédents.

Approfondissement : le programme de Terminale demande de construire des requêtes d'interrogation et de mise à jour (SELECT, INSERT, UPDATE, DELETE), pas de créer ni de supprimer des tables ; CREATE TABLE et DROP TABLE ne sont pas exigibles. Ce qui l'est ici : reconnaître les contraintes d'intégrité de domaine, de relation et de référence, et dire quelle insertion chacune refuse, comme aux questions a) et c).

CREATE TABLE note (
    id INTEGER PRIMARY KEY,
    eleve_id INTEGER NOT NULL REFERENCES eleve(id),
    devoir_id INTEGER NOT NULL REFERENCES devoir(id),
    valeur REAL CHECK (valeur >= 0 AND valeur <= 20)
);

CREATE TABLE eleve (
    id INTEGER PRIMARY KEY,
    nom TEXT NOT NULL,
    classe TEXT NOT NULL
);

CREATE TABLE devoir (
    id INTEGER PRIMARY KEY,
    matiere TEXT NOT NULL,
    coefficient INTEGER CHECK (coefficient > 0)
);
  • a) Quelle règle impose chacune des contraintes PRIMARY KEY, NOT NULL, REFERENCES et CHECK ? Combien de contraintes REFERENCES le script contient-il ?
  • b) Le script échoue sur un moteur qui vérifie les références dès la création. Pourquoi ? Dans quel ordre faut-il créer les tables ?
  • c) Les tables étant créées, dites pour chacune des quatre commandes suivantes si elle est acceptée ou refusée, et par quelle contrainte :(1)INSERT INTO note VALUES (200, 1, 10, 21)(2)INSERT INTO note VALUES (201, 1, 10, NULL)(3)INSERT INTO devoir VALUES (13, 'SVT', 0)(4)INSERT INTO eleve VALUES (5, 'Martin', NULL).
  • d) On veut supprimer les trois tables avec DROP TABLE. Dans quel ordre, et pourquoi ?
  • e) On veut interdire qu'un élève ait deux notes au même devoir. Quelle contrainte faut-il ajouter à la table note ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Quatre contraintes, 2 REFERENCES
  • b) Créer eleve et devoir avant note
  • c) (1), (3), (4) refusées ; (2) acceptée
  • d) Supprimer note en premier
  • e) UNIQUE (eleve_id, devoir_id)
a)

PRIMARY KEY impose que l'attribut soit unique et non nul : il identifie la ligne. NOT NULL interdit la valeur nulle. REFERENCES déclare une clé étrangère : la valeur doit exister comme clé primaire dans la table désignée. CHECK impose une condition sur la valeur, ici une note entre 0 et 20 et un coefficient strictement positif. Le script contient 2 contraintes REFERENCES, toutes deux dans la table note.

b)

La table note est créée en PREMIER alors qu'elle référence eleve et devoir, qui n'existent pas encore : le moteur ne peut pas vérifier des références vers des tables inconnues. Il faut créer d'abord les tables référencées, eleve et devoir, dans un ordre quelconque entre elles, puis la table note qui s'appuie sur elles. La règle générale : on crée une table après toutes celles qu'elle référence.

c)

(1) REFUSÉE par CHECK, puisque 21 dépasse 20. (2) ACCEPTÉE, et c'est le piège : la condition CHECK appliquée à une valeur nulle donne un résultat inconnu, et une contrainte CHECK ne rejette que ce qui est FAUX ; comme valeur n'est pas déclarée NOT NULL, la ligne entre. (3) REFUSÉE par CHECK, puisque le coefficient 0 n'est pas strictement positif. (4) REFUSÉE par NOT NULL sur l'attribut classe. Trois commandes sur quatre sont donc refusées ; pour refuser aussi la deuxième, il faudrait ajouter NOT NULL à valeur.

d)

Il faut supprimer d'abord note, puis eleve et devoir, dans l'ordre inverse de la création. Supprimer eleve en premier laisserait des lignes de note référencer une table disparue, ce que l'intégrité référentielle interdit : le moteur refuse la suppression tant qu'une clé étrangère pointe vers la table.

e)

Une contrainte d'unicité sur le couple : UNIQUE (eleve_id, devoir_id). Elle n'empêche pas un élève d'avoir plusieurs notes ni un devoir d'en avoir plusieurs, mais elle interdit que le même COUPLE apparaisse deux fois. On aurait aussi pu faire de ce couple la clé primaire de note, à la place de id, comme dans la clé composée de l'exercice 1.

Exercice 13 : L'injection SQL et les requêtes paramétrées

Un site du lycée affiche les notes d'un élève dont on saisit le nom. Le programme Python ci-dessous interroge la base des exercices précédents avec le module sqlite3. La méthode fetchall renvoie la liste des lignes du résultat, chaque ligne étant un tuple.

import sqlite3

connexion = sqlite3.connect('lycee.db')
curseur = connexion.cursor()

def notes_de(nom):
    requete = "SELECT valeur FROM note JOIN eleve ON note.eleve_id = eleve.id WHERE eleve.nom = '" + nom + "'"
    curseur.execute(requete)
    return curseur.fetchall()
  • a) Que renvoie notes_de('Ferrand') ? Donnez la valeur Python exacte.
  • b) Un visiteur saisit le texte x' OR '1'='1. Écrivez la requête réellement exécutée. Combien de lignes renvoie-t-elle, et pourquoi ?
  • c) Comment s'appelle cette attaque ? Quel est le principe de la faille, en une phrase ?
  • d) On remplace les deux lignes de la fonction par curseur.execute('SELECT valeur FROM note JOIN eleve ON note.eleve_id = eleve.id WHERE eleve.nom = ?', (nom,)). Que renvoie maintenant la saisie de la question b), et pourquoi l'attaque échoue-t-elle ?
  • e) Un autre programme exécute un INSERT avec ce même module, sans erreur, mais la nouvelle ligne n'apparaît jamais pour les autres utilisateurs de la base. Quelle instruction manque-t-il, et quelle propriété des transactions est en jeu ?

Tape tes réponses, la page te dit juste ou faux 0/5

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) [(14,), (17,)]
  • b) '1'='1' toujours vrai : 7 lignes
  • c) Injection SQL
  • d) Requête paramétrée : liste vide
  • e) commit manquant
a)

Ferrand a les notes 14 et 17 : la fonction renvoie [(14,), (17,)], une liste de deux tuples à un élément. La virgule dans (14,) est ce qui distingue un tuple à un élément d'un simple nombre entre parenthèses.

b)

La concaténation produit : SELECT valeur FROM note JOIN eleve ON note.eleve_id = eleve.id WHERE eleve.nom = 'x' OR '1'='1'. La condition se lit eleve.nom = 'x' OU '1' = '1' ; la seconde partie est toujours vraie, donc la condition est vraie pour toutes les lignes, et la requête renvoie les 7 notes de la base, celles de tous les élèves.

c)

C'est une INJECTION SQL. Le principe de la faille : le texte saisi par l'utilisateur est collé dans la requête avant son analyse, si bien qu'une apostrophe bien placée ferme la chaîne et transforme la suite de la saisie en code SQL exécuté par le moteur. Selon les droits du compte, la même technique permet de lire d'autres tables, de modifier des notes ou de supprimer des données.

d)

La requête renvoie [], la liste vide : aucun élève ne s'appelle littéralement x' OR '1'='1. Avec le point d'interrogation, la requête est analysée par le moteur AVANT de recevoir la valeur, et la valeur est ensuite transmise comme une donnée, jamais relue comme du code : les apostrophes qu'elle contient ne sont plus que des caractères du nom cherché. C'est la seule protection fiable ; filtrer les apostrophes à la main laisse toujours passer une variante.

e)

Il manque connexion.commit(). Le module ouvre implicitement une transaction avant une écriture, et tant qu'elle n'est pas validée, ses modifications ne sont visibles que de la connexion qui les a faites ; si le programme se termine sans valider, elles sont annulées. C'est la DURABILITÉ, liée à l'atomicité, de l'exercice 9 : seule une transaction validée est garantie et partagée.

Exercice 14 : Interroger la base d'un club de sport

Un club gère ses adhérents, ses activités et les inscriptions. La table adherent contient Lemieux, 16 ans, d'identifiant 1 ; Garnier, 17 ans, 2 ; Roy, 15 ans, 3 ; Diallo, 16 ans, 4 ; Morin, 18 ans, 5. La table activite contient l'escalade le mardi, d'identifiant 1 ; la natation le jeudi, 2 ; le judo le mardi, 3 ; les échecs le samedi, 4.

La table inscription, d'attributs adherent_id et activite_id, est donnée ci-dessous.

Approfondissement pour les questions c), d) et e) : c) emploie une jointure externe LEFT JOIN et un GROUP BY, d) un GROUP BY avec HAVING, e) une sous-requête ; aucune de ces constructions n'est exigible en Terminale, où l'on dispose de SELECT, FROM, WHERE, JOIN, avec DISTINCT, ORDER BY et les fonctions d'agrégation, pour l'interrogation ; INSERT, UPDATE, DELETE pour la mise à jour, sans GROUP BY ni HAVING. Les questions a) et b) sont au programme. La figure ci-dessous représente les tables adherent et activite, reliées à la table inscription.

adherent_id11223445
activite_id12123132
idnomage1Lemieux162Garnier173Roy154Diallo165Morin18adherentidnomjour1escalademardi2natationjeudi3judomardi4échecssamediactiviteadherent_idactivite_idinscription
  • a) Quelle clé primaire donner à la table inscription ? Combien de lignes contient-elle ?
  • b) Écrivez la requête qui donne le nom des adhérents inscrits à l'escalade, et son résultat.
  • c) Écrivez la requête qui donne, pour CHAQUE activité, le nombre d'inscrits, y compris les activités sans inscrit. Donnez le résultat. Que donnerait COUNT(*) à la place de COUNT(inscription.adherent_id) pour les échecs ?
  • d) Le club veut repérer les adhérents inscrits à deux activités le même jour. Écrivez la requête et donnez son résultat.
  • e) On veut l'âge moyen des adhérents inscrits à l'escalade OU à la natation, chacun compté une fois. Calculez-le. Que donnerait une moyenne calculée directement sur les lignes de la jointure, et pourquoi est-ce faux ?

Tape tes réponses, la page te dit juste ou faux 0/10

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Clé composée, 8 lignes
  • b) Lemieux, Garnier, Diallo
  • c) LEFT JOIN et COUNT(colonne) : échecs 0
  • d) GROUP BY adhérent et jour : Diallo
  • e) 16,7516{,}75 ans, et non 16,6716{,}67
a)

Le couple (adherent_id, activite_id) : un adhérent peut suivre plusieurs activités et une activité accueillir plusieurs adhérents, mais le même couple ne doit apparaître qu'une fois. C'est une clé composée, formée de deux clés étrangères, typique d'une table d'association. La table contient 8 lignes.

b)

SELECT adherent.nom FROM adherent JOIN inscription ON inscription.adherent_id = adherent.id JOIN activite ON inscription.activite_id = activite.id WHERE activite.nom = 'escalade'. Les adhérents 1, 2 et 4 sont inscrits à l'activité 1 : le résultat est Lemieux, Garnier et Diallo.

c)

SELECT activite.nom, COUNT(inscription.adherent_id) FROM activite LEFT JOIN inscription ON inscription.activite_id = activite.id GROUP BY activite.id, activite.nom. Résultat : escalade 3, natation 3, judo 2, échecs 0. La jointure externe conserve les échecs, avec une colonne adherent_id nulle ; COUNT(inscription.adherent_id) ne compte que les valeurs non nulles, d'où 0. COUNT(*) compterait la LIGNE, et donnerait 1 aux échecs, un inscrit fantôme. Une jointure interne, elle, ferait disparaître les échecs du résultat.

d)

SELECT adherent.nom, activite.jour FROM inscription JOIN adherent ON inscription.adherent_id = adherent.id JOIN activite ON inscription.activite_id = activite.id GROUP BY adherent.id, adherent.nom, activite.jour HAVING COUNT(*) > 1. Le mardi porte l'escalade et le judo ; seul l'adhérent 4 est inscrit aux deux. Le résultat est Diallo, mardi. On regroupe par adhérent ET par jour : un regroupement par adhérent seul signalerait aussi Lemieux et Garnier, inscrits à deux activités mais pas le même jour.

e)

Les inscrits à l'escalade sont 1, 2 et 4, ceux de la natation 1, 2 et 5 : l'ensemble des adhérents concernés est {1, 2, 4, 5}, d'âges 16, 17, 16 et 18, d'où une moyenne de 674=16,75\frac{67}{4} = 16{,}75 ans. Requête correcte : SELECT AVG(age) FROM adherent WHERE id IN (SELECT adherent_id FROM inscription WHERE activite_id IN (1, 2)). Une moyenne calculée sur les lignes de la jointure compterait Lemieux et Garnier DEUX fois, une par activité : 16+17+16+16+17+186=1006≈16,67\frac{16 + 17 + 16 + 16 + 17 + 18}{6} = \frac{100}{6} \approx 16{,}67 ans. Le résultat paraît plausible, ce qui rend l'erreur invisible : c'est la même duplication qu'à l'exercice 5, cette fois cachée dans un agrégat.

Exercice 15 : Une table qui se référence elle-même

La table personnel d'un lycée enregistre, pour chaque membre, son responsable direct par une clé étrangère responsable_id qui pointe vers la même table. La directrice n'a pas de responsable.

Approfondissement pour une partie de l'exercice : la jointure externe LEFT JOIN de la fin de la question b), le GROUP BY de la question c) et la requête récursive évoquée en e) ne sont pas exigibles en Terminale. L'auto-jointure elle-même, avec ses alias, n'est qu'une jointure JOIN : elle est au programme, comme les questions a) et d).

idnomposteresponsable_id
1BernarddirectriceNULL
2Caronadjoint1
3Duboisprofesseur2
4Éthierprofesseure2
5FortinCPE1
6Gagnonsurveillant5
  • a) Que signifie la valeur nulle de responsable_id pour Bernard ? Dessinez l'organisation sous forme d'arbre et donnez sa hauteur, en nombre d'arêtes.
  • b) Écrivez la requête qui donne le nom de chaque membre et le nom de son responsable. Pourquoi les alias sont-ils obligatoires ? Combien de lignes obtient-on, et combien avec une jointure externe gauche ?
  • c) Écrivez la requête qui donne, pour chaque responsable, le nombre de ses subordonnés directs. Donnez le résultat.
  • d) Écrivez la requête qui donne le nom des membres dont le responsable a lui-même un responsable. Donnez le résultat.
  • e) On veut tous les subordonnés de Bernard, directs ou indirects. Combien y en a-t-il ? Pourquoi un nombre fixé de jointures ne suffit-il pas en général, et comment procéder ?

Tape tes réponses, la page te dit juste ou faux 0/9

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Arbre de hauteur 2
  • b) Alias : 5 lignes, 6 avec LEFT JOIN
  • c) Bernard 2, Caron 2, Fortin 1
  • d) Dubois, Éthier, Gagnon
  • e) 5 subordonnés : parcours répété
a)

La valeur nulle signifie que Bernard n'a PAS de responsable : c'est la racine de l'organisation. L'arbre : Bernard a pour subordonnés Caron et Fortin ; Caron a pour subordonnés Dubois et Éthier ; Fortin a pour subordonné Gagnon. Sa hauteur vaut 2 : le chemin le plus long, de Bernard à Dubois par exemple, compte deux arêtes. La clé étrangère réflexive évoquée à l'exercice 2 sert précisément à ranger une telle hiérarchie dans une seule table.

b)

SELECT p.nom, r.nom FROM personnel AS p JOIN personnel AS r ON p.responsable_id = r.id. La table apparaît deux fois, une fois dans le rôle du membre et une fois dans le rôle du responsable : sans les alias p et r, l'expression personnel.nom serait ambiguë, et le moteur ne saurait pas de quelle copie on parle. On obtient 5 lignes, Bernard étant absent puisque sa clé étrangère nulle ne correspond à aucune ligne. Avec LEFT JOIN, on obtient 6 lignes, Bernard apparaissant avec un responsable nul.

c)

SELECT r.nom, COUNT(*) FROM personnel AS p JOIN personnel AS r ON p.responsable_id = r.id GROUP BY r.id, r.nom. Résultat : Bernard 2, Caron 2, Fortin 1. Les membres sans subordonné n'apparaissent pas ; pour les faire figurer avec 0, il faudrait partir de r en jointure externe et compter p.id, comme au club de sport.

d)

SELECT p.nom FROM personnel AS p JOIN personnel AS r ON p.responsable_id = r.id WHERE r.responsable_id IS NOT NULL. Les responsables qui ont eux-mêmes un responsable sont Caron et Fortin : le résultat est Dubois, Éthier et Gagnon, soit 3 membres, ceux du niveau 2 de l'arbre.

e)

Bernard a 5 subordonnés, tous les autres membres. Une jointure remonte d'un niveau, deux jointures de deux niveaux : une requête écrite avec un nombre fixé de jointures ne voit qu'une profondeur fixée, alors que la hauteur de l'arbre peut changer à chaque réorganisation. Il faut un parcours RÉPÉTÉ jusqu'à ce qu'aucun nouveau membre n'apparaisse : soit une boucle dans le programme Python, qui interroge la base niveau par niveau comme un parcours en largeur, soit une requête récursive, que la plupart des moteurs proposent sous la forme WITH RECURSIVE.

BernardCaronFortinDuboisÉthierGagnonniveau 0niveau 1niveau 2

Partie D : au programme, les agrégats sans regroupement (/10)

Exercice 16 : Agrégats sans regroupement : résumer une sélection

Un service de vélos en libre-service enregistre ses stations dans la table station, d'attributs id, nom et quartier : la Gare, d'identifiant 1, et la Mairie, 2, sont dans le quartier Centre ; le Parc, 3, est dans le quartier Nord.

La table trajet ci-dessous enregistre huit trajets : le vélo utilisé, les identifiants des stations de départ et d'arrivée (clés étrangères vers station), la durée en minutes et la distance en kilomètres. Le boîtier GPS du trajet 5 était en panne : sa distance est NULL.

Le programme de Terminale autorise les fonctions d'agrégation COUNT, SUM, AVG, MIN et MAX, sans les clauses GROUP BY et HAVING. Toutes les requêtes de cet exercice s'écrivent donc avec SELECT, FROM, WHERE, JOIN et ORDER BY seulement. La figure ci-dessous représente la table station et le schéma de la table trajet.

idvelodepart_idarrivee_iddureedistance
1V713184,2
2V32161,1
3V732214,5
4V51250,9
5V31325NULL
6V923163,6
7V531345,8
8V711122,0
idvelodepart_idarrivee_iddureedistancetrajetidnomquartier1GareCentre2MairieCentre3ParcNordstation
  • a) Écrivez la requête qui donne, pour les trajets partis de la Gare, leur nombre, la somme de leurs durées, la durée moyenne, la plus courte et la plus longue. Donnez les cinq valeurs.
  • b) Écrivez la requête qui donne le nombre de trajets partis d'une station du quartier Centre et leur durée moyenne. Donnez les deux valeurs, la moyenne au centième.
  • c) Que renvoie SELECT AVG(distance) FROM trajet ? Un élève calcule plutôt SUM(distance) / COUNT(*). Donnez les deux résultats au centième et dites lequel est juste.
  • d) On veut le nombre de trajets partis de CHAQUE station, sans utiliser GROUP BY. Écrivez les requêtes, donnez les trois nombres et le contrôle qui prouve qu'aucune station n'est oubliée.
  • e) On cherche le vélo qui a fait le trajet le plus long. Pourquoi la requête SELECT velo, MAX(duree) FROM trajet n'est-elle pas une bonne réponse ? Donnez deux écritures correctes et leur résultat.
  • f) Donnez la clé primaire de chacune des deux tables et les clés étrangères de la table trajet. On tente ensuite la commande INSERT INTO trajet (id, velo, depart_id, arrivee_id, duree, distance) VALUES (9, 'V9', 3, 4, 14, 3.1). Que répond le système, et combien de lignes la table trajet compte-t-elle après cette tentative ?
  • g) Le boîtier GPS du trajet 5 est relu : ce trajet a fait 5,1 km. Écrivez la commande qui corrige la table. Que valent ensuite COUNT(distance) et AVG(distance) ? Le calcul de l'élève de la question c), SUM(distance) / COUNT(*), donne-t-il encore un autre résultat ?
  • h) Écrivez la requête qui donne, sans doublon et par ordre alphabétique, le nom des stations d'où le vélo V7 est parti, puis celle qui donne la distance totale qu'il a parcourue. Donnez les deux résultats.
  • i) La station de la Mairie ferme. La commande DELETE FROM station WHERE id = 2 est refusée : pourquoi ? Écrivez la requête qui compte les trajets en cause, puis les commandes qui suppriment la station, dans le bon ordre. Combien de trajets restent dans la table ?

Tape tes réponses, la page te dit juste ou faux 0/26

a)
b)
c)
d)
e)
f)
g)
h)
i)
Voir la correction

Réponses

  • a) WHERE depart_id = 1 : 44 trajets, somme 6060, moyenne 1515, minimum 55, maximum 2525 minutes
  • b) JOIN station ON trajet.depart_id = station.id WHERE station.quartier = 'Centre' : 66 trajets, 826≈13,67\frac{82}{6}\approx 13{,}67 minutes
  • c) AVG : 22,17≈3,16\frac{22{,}1}{7}\approx 3{,}16 km, juste ; SUM / COUNT(*) : 22,18≈2,76\frac{22{,}1}{8}\approx 2{,}76 km, faux
  • d) Une requête par station : 44, 22 et 22 ; contrôle 4+2+2=84+2+2=8 trajets
  • e) Colonne mêlée à un agrégat ; ORDER BY duree DESC, ou MAX puis WHERE duree = 34 : V5, 3434 minutes
  • f) Clés primaires id ; depart_id et arrivee_id clés étrangères vers station ; insertion refusée, 88 lignes
  • g) UPDATE trajet SET distance = 5.1 WHERE id = 5 ; COUNT(distance) =8=8, AVG =27,28=3,40=\frac{27{,}2}{8}=3{,}40 km, comme SUM / COUNT(*)
  • h) DISTINCT, ORDER BY station.nom : Gare, Parc ; SUM(distance) =10,7=10{,}7 km
  • i) 44 trajets référencent la station 2 : DELETE des trajets d'abord, puis de la station ; 44 trajets restent
a)

La requête : SELECT COUNT(*), SUM(duree), AVG(duree), MIN(duree), MAX(duree) FROM trajet WHERE depart_id = 1. Le moteur exécute d'abord FROM, puis WHERE, qui ne garde que les trajets partis de la Gare : les lignes 1, 4, 5 et 8, de durées 1818, 55, 2525 et 1212 minutes. Les cinq fonctions d'agrégation s'appliquent ENSUITE à ces quatre lignes seulement, et le résultat est UNE ligne de cinq colonnes : COUNT(*) vaut 44, SUM(duree) vaut 18+5+25+12=6018+5+25+12=60, AVG(duree) vaut 604=15\frac{60}{4}=15, MIN(duree) vaut 55 et MAX(duree) vaut 2525. Contrôle en une seconde : la moyenne tombe entre le minimum et le maximum, et elle vaut bien la somme divisée par le compte. L'erreur fréquente est de croire que les agrégats portent sur la table entière : ils portent sur ce que le WHERE a laissé passer.

b)

Le quartier est une colonne de station, la durée une colonne de trajet : il faut une jointure, dont la condition relie la clé étrangère depart_id à la clé primaire de station. La requête : SELECT COUNT(*), AVG(trajet.duree) FROM trajet JOIN station ON trajet.depart_id = station.id WHERE station.quartier = 'Centre'. La jointure associe à chaque trajet SA station de départ, huit lignes ; le WHERE garde celles dont la station est la Gare ou la Mairie, les trajets 1, 2, 4, 5, 6 et 8. On obtient 66 trajets, une somme de durées de 18+6+5+25+16+12=8218+6+5+25+16+12=82 et une moyenne de 826≈13,67\frac{82}{6}\approx 13{,}67 minutes. Sans la condition de jointure, le produit cartésien donnerait 8×3=248\times 3=24 lignes, dont 1616 avec une station du Centre, et leur moyenne serait 2×13716=17,125\frac{2\times 137}{16}=17{,}125 minutes, la moyenne de TOUS les trajets : un nombre plausible, sans rapport avec la question. C'est le piège de l'exercice 5, caché cette fois dans un agrégat.

c)

La distance du trajet 5 est NULL, c'est-à-dire INCONNUE. Comme toutes les fonctions d'agrégation sauf COUNT(*), AVG ignore les valeurs nulles : elle fait la moyenne des 77 distances connues, 4,2+1,1+4,5+0,9+3,6+5,8+2,07=22,17≈3,16\frac{4{,}2+1{,}1+4{,}5+0{,}9+3{,}6+5{,}8+2{,}0}{7}=\frac{22{,}1}{7}\approx 3{,}16 km. SUM(distance) ignore aussi la valeur nulle et vaut 22,122{,}1, mais COUNT(*) compte les LIGNES, 88 : le calcul de l'élève donne 22,18≈2,76\frac{22{,}1}{8}\approx 2{,}76 km. Ce second calcul revient à dire que le trajet 5 a fait 00 km alors qu'il a duré 2525 minutes : il est faux. La valeur juste est celle d'AVG, en précisant qu'elle porte sur les trajets dont la distance est connue. Pour écrire le même calcul à la main, on divise par COUNT(distance), qui ne compte que les valeurs non nulles et vaut 77.

d)

Sans GROUP BY, on écrit une requête par station, en changeant la valeur filtrée dans le WHERE. SELECT COUNT(*) FROM trajet WHERE depart_id = 1 donne 44 pour la Gare ; avec depart_id = 2, on obtient 22 pour la Mairie, les trajets 2 et 6 ; avec depart_id = 3, 22 encore pour le Parc, les trajets 3 et 7. Le contrôle : 4+2+2=84+2+2=8, le nombre total de trajets, que donne SELECT COUNT(*) FROM trajet. Si la somme ne tombait pas juste, une station serait oubliée ou un filtre mal écrit. Une copie qui écrit SELECT depart_id, COUNT(*) FROM trajet, sans rien d'autre, mêle une colonne ordinaire et un agrégat : le COUNT(*) résume les huit lignes en une seule, et la requête ne répond pas à la question. La clause GROUP BY depart_id ferait les trois comptes d'un coup, mais elle est hors programme, voir l'exercice 6 ; avec les clauses exigibles, une requête par valeur est la bonne réponse, et la somme la rend vérifiable.

e)

SELECT MAX(duree) FROM trajet donne 3434 minutes. Mais SELECT velo, MAX(duree) FROM trajet mêle une colonne ordinaire, velo, qui a une valeur par ligne, et un agrégat, qui résume toutes les lignes en une seule : la norme SQL la refuse. SQLite l'accepte et renvoie, dans ce cas précis d'un MAX seul, la ligne du maximum ; c'est une particularité de ce moteur, et avec AVG à la place de MAX il prendrait velo sur une ligne quelconque. Deux écritures correctes avec les clauses du programme. Trier : SELECT velo, duree FROM trajet ORDER BY duree DESC, et lire la première ligne, V5 avec 3434 minutes. Ou procéder en deux temps : SELECT MAX(duree) FROM trajet, qui donne 3434, puis SELECT velo FROM trajet WHERE duree = 34, qui donne V5. La seconde a un avantage : en cas d'égalité, elle renvoie TOUS les vélos à 3434 minutes, là où la lecture de la première ligne en oublierait.

f)

Dans station, la clé primaire est id. Dans trajet, la clé primaire est id ; depart_id et arrivee_id sont deux clés étrangères, qui pointent toutes deux vers station.id.

La commande est REFUSÉE : arrivee_id vaut 4, et aucune station d'identifiant 4 n'existe. Le trajet décrirait une arrivée dans une station inconnue, ce que l'intégrité référentielle interdit.

Le système lève une erreur et n'écrit rien : la table trajet compte toujours 88 lignes. La clé primaire, elle, était respectée, puisque l'identifiant 9 est neuf, et depart_id vaut 3, le Parc, qui existe.

Piège : ne vérifier qu'une des deux clés étrangères. Une table peut en avoir plusieurs vers la même table, et la ligne doit les satisfaire TOUTES.

g)

La commande : UPDATE trajet SET distance = 5.1 WHERE id = 5. Une seule ligne est modifiée, désignée par sa clé primaire.

Il n'y a plus de valeur nulle : COUNT(distance) vaut 88, la somme des distances vaut 22,1+5,1=27,222{,}1+5{,}1=27{,}2 et AVG(distance) vaut 27,28=3,40\frac{27{,}2}{8}=3{,}40 km.

Le calcul de l'élève, SUM(distance) / COUNT(*), donne maintenant aussi 27,28=3,40\frac{27{,}2}{8}=3{,}40 km : les deux écritures coïncident dès qu'aucune distance n'est nulle. Celle de l'élève reste fragile, elle redeviendrait fausse au premier boîtier en panne.

Contrôle : la nouvelle moyenne 3,403{,}40 dépasse l'ancienne, 3,163{,}16, ce qui est cohérent puisque la distance ajoutée, 5,15{,}1 km, dépasse 3,163{,}16.

Piège : écrire WHERE distance = NULL pour viser le trajet sans distance. Cette comparaison n'est jamais vraie, et la commande ne modifie AUCUNE ligne, sans message d'erreur. La bonne écriture est WHERE id = 5, ou à défaut WHERE distance IS NULL.

h)

Le nom de la station est dans station : jointure sur la station de DÉPART. La requête : SELECT DISTINCT station.nom FROM trajet JOIN station ON trajet.depart_id = station.id WHERE trajet.velo = 'V7' ORDER BY station.nom.

Le vélo V7 a fait les trajets 1, 3 et 8, partis des stations 1, 3 et 1 : la Gare, le Parc et la Gare. Avec DISTINCT et le tri : Gare, Parc, deux lignes ; sans DISTINCT, la Gare apparaîtrait deux fois.

La distance totale : SELECT SUM(distance) FROM trajet WHERE velo = 'V7'. Elle vaut 4,2+4,5+2,0=10,74{,}2+4{,}5+2{,}0=10{,}7 km ; aucune table n'est à joindre, la distance et le vélo sont dans trajet.

Piège : joindre sur arrivee_id. On obtiendrait les stations d'ARRIVÉE, le Parc, la Mairie et la Gare : trois lignes, qui ne répondent pas à la question.

i)

Quatre trajets référencent la Mairie, d'identifiant 2 : les trajets 2 et 6 en sont partis, les trajets 3 et 4 y sont arrivés. Supprimer la station laisserait ces lignes pointer vers une station inexistante : la clé étrangère l'interdit.

La requête de comptage : SELECT COUNT(*) FROM trajet WHERE depart_id = 2 OR arrivee_id = 2. Elle renvoie 44.

Les commandes, dans cet ordre : DELETE FROM trajet WHERE depart_id = 2 OR arrivee_id = 2, qui supprime ces 44 trajets, puis DELETE FROM station WHERE id = 2, qui est alors acceptée.

Il reste 8−4=48-4=4 trajets, les trajets 1, 5, 7 et 8, qui ne touchent que la Gare et le Parc.

Piège : ne supprimer que les trajets partis de la Mairie, WHERE depart_id = 2. Les trajets 3 et 4 y arrivent encore, et la suppression de la station reste refusée.

Effacer l'historique est un choix : on peut aussi garder la station et la marquer fermée, ce que discutent les comportements de l'exercice 2.

À retenir : au programme, une fonction d'agrégation résume en UNE ligne les lignes que le WHERE a laissé passer. Une question « pour chaque » se traite sans GROUP BY, une requête par valeur, et se contrôle par la somme ; une question « lequel est le plus grand » se traite par ORDER BY ou en deux requêtes, jamais en mêlant une colonne et un agrégat.

Il manque quelque chose dans cette série ? Un type d'exercice que ton prof donne, un énoncé qui te bloque, une erreur repérée : dis-le-moi, je lis chaque demande.

L'envoi passe par ton espace gratuit : c'est ce qui me permet de te répondre et de te prévenir quand l'exercice est ajouté. Un courriel suffit, pas de mot de passe. Crée ton espace. Pas envie de créer un compte ? Écris-moi directement.

De quoi s'agit-il ?
Je lis chaque demande moi-même, personne d'autre ne la voit. Inutile d'écrire ton nom ou celui de ton école.
Quand ta demande est traitée, tu es prévenu par courriel et dans ton espace.

En envoyant, tu acceptes que je lise ta demande pour y répondre. Elle est gardée avec ton compte, effacée avec lui, et jamais publiée : politique de confidentialité.

Chapitre précédent Structures de données : piles, files, arbres et graphes Chapitre suivant Processus, réseaux et sécurisation

© Ahmed Squalli Houssaini. Série publiée sur www.letuteurscientifique.ca/exercices/tnsi-bases-donnees-sql. Libre pour l'usage personnel et en classe ; sa republication ailleurs demande une autorisation écrite (mentions légales).

Voir aussi

Vous cherchez un tuteur en NSI à Montréal ?

Contactez-moi pour une première séance. Les bases de données sont le chapitre de Terminale qui rapporte le plus vite : la syntaxe SQL s'apprend en deux séances, et l'essentiel des points tient dans les jointures et les fonctions d'agrégation.

Site par Studio Squalli