Bases de données & SQL · L2 · Section 10/11
Annales corrigées
Progression
#Annales corrigées
Cette page rassemble des sujets réellement posés en contrôle continu, en contrôle terminal et en travaux dirigés dans l'UE Base de données de L2 Informatique (UCA, campus Valrose), accompagnés de leurs corrections détaillées.
Provenance des sujets. Les contrôles continus portent la marque de leurs sessions : un CC d'octobre 2015 (durée 50 minutes, questions de cours et algèbre relationnelle sur une compagnie maritime), un CC de novembre 2020 (durée 50 minutes, base d'hôtels), un CC n°2 de novembre 2013 (durée 1h30, CarTrack puis requêtes SQL Armazon), et un contrôle continu d'avril 2012 en trois heures sur la base Apple Store. Le contrôle terminal de décembre 2016 nous est parvenu sous forme d'extraits : questions de cours sur le verrouillage en deux phases et l'ordre de traitement des clauses SQL, modélisation E-A d'une base Zoo, graphe d'attente et normalisation. Les exercices de TD 9 (normalisation et transformation E-A) et TD 10 (contrôle de concurrence) complètent l'ensemble, avec leurs corrigés.
Ce que les sujets testent, dans l'ordre de fréquence. L'identification des clés étrangères d'un schéma donné ; l'écriture de requêtes en algèbre relationnelle, dont au moins une question en « tous les » qui appelle la division ; les mêmes requêtes en SQL ; la normalisation d'une table plate jusqu'en 3NF ; et, quand la session porte sur les transactions, la construction d'un graphe de précédence ou d'un graphe d'attente. Trois questions de cours reviennent régulièrement : le rôle des clés étrangères, la différence entre algèbre relationnelle et SQL, et l'utilité du verrouillage en deux phases.
#Exercice 1 — Compagnie maritime : questions de cours et algèbre (CC octobre 2015)
Base de données d'une compagnie maritime. Les clés primaires sont soulignées en sujet.
1NAVIRE(NUMERO, NOM, TYPE, CAPACITE, DATE_FABRICATION)2CAPITAINE(NUMERO, NOM, SALAIRE)3TRAVERSEE(NUMERO, CAPITAINE, NAVIRE, VILLE_DEPART, VILLE_ARRIVE, HEURE_DEPART, HEURE_ARRIVE)- Dans une base de données relationnelle, quel est le rôle des clés étrangères ?
- En algèbre relationnelle, l'opérateur « projection » peut-il réduire la cardinalité d'une relation ? Pourquoi ? Y a-t-il un moyen de garantir que la cardinalité reste invariée ?
- Quelles opérations requièrent la compatibilité envers l'union ? Qu'est-ce que la compatibilité envers l'union ?
- a) Quelles sont les clés étrangères ?
- Réaliser les requêtes suivantes en algèbre relationnelle :
- b) le nom des capitaines ;
- c) le nom des capitaines percevant un salaire inférieur à 2000 ;
- d) le nom des navires fabriqués avant le 1/1/99 et dont la capacité est supérieure à 100 ;
- e) le nom des capitaines effectuant une traversée en direction de NICE ;
- f) les capitaines (nom) percevant un salaire supérieur à 2500 et effectuant une traversée ;
- g) les navires (numéro) dont la capacité est inférieure à 50 ou effectuant une traversée à destination de NICE ;
- h) les capitaines effectuant toutes les traversées au départ de NICE ;
- i) les capitaines effectuant toutes les traversées au départ de NICE et dont le nom est différent de « De Maria » ;
- l) les navires (nom) fabriqués après le navire « Casanova ».
Correction détaillée
1. Rôle des clés étrangères. Elles garantissent l'intégrité référentielle : les valeurs d'une clé étrangère doivent correspondre à une valeur d'une clé candidate d'un tuple de la relation référencée, ou être complètement nulles. Elles matérialisent les associations du modèle E-A et interdisent les liens orphelins — un TRAVERSEE pointant vers un capitaine inexistant est refusé par le moteur.
2. La projection et la cardinalité. Oui, la projection peut réduire la cardinalité : elle supprime les doublons. Si deux tuples ne différaient que par des attributs non projetés, ils deviennent identiques après projection et se confondent en un seul — c'est la sémantique ensembliste de l'opérateur. Pour garantir que la cardinalité reste invariée, il suffit de projeter un ensemble d'attributs qui contient une clé candidate (a fortiori la clé primaire) : chaque tuple reste alors distinguable, donc aucune fusion n'est possible.
3. Compatibilité envers l'union. Les opérations qui la requièrent sont les opérateurs ensemblistes binaires : union, intersection et différence. Elle signifie deux choses : les deux relations ont le même degré, et leurs attributs sont compatibles deux à deux (le i-ème attribut de l'une a le même domaine que le i-ème attribut de l'autre). Les noms d'attributs, eux, peuvent différer. Le produit cartésien, en revanche, n'exige aucune compatibilité : il accepte des schémas quelconques.
4. a) Clés étrangères.
1TRAVERSEE.CAPITAINE -> CAPITAINE.NUMERO2TRAVERSEE.NAVIRE -> NAVIRE.NUMEROb) Le nom des capitaines.
1π_NOM ( CAPITAINE )c) Salaire inférieur à 2000.
1π_NOM ( σ_SALAIRE < 2000 ( CAPITAINE ) )d) Navires fabriqués avant le 1/1/99 et de capacité supérieure à 100.
1π_NOM ( σ_DATE_FABRICATION < '1/1/99' ∧ CAPACITE > 100 ( NAVIRE ) )e) Capitaines effectuant une traversée en direction de NICE. « En direction de » désigne la destination, donc VILLE_ARRIVE. On joint après avoir réduit TRAVERSEE à ce qui sert :
1π_NOM ( CAPITAINE ⋈_CAPITAINE.NUMERO = TRAVERSEE.CAPITAINE π_CAPITAINE ( σ_VILLE_ARRIVE = 'NICE' ( TRAVERSEE ) ) )f) Salaire supérieur à 2500 et effectuant une traversée.
1π_NOM ( σ_SALAIRE > 2500 ( CAPITAINE ) ⋈_CAPITAINE.NUMERO = TRAVERSEE.CAPITAINE π_CAPITAINE ( TRAVERSEE ) )L'ordre compte ici pour l'efficacité, pas pour le résultat : filtrer le salaire avant la jointure réduit le volume intermédiaire. Les deux écritures sont acceptées, la première est meilleure.
g) Navires de capacité inférieure à 50 ou effectuant une traversée à destination de NICE. C'est le « ou » qui impose l'union, et c'est aussi la question qui teste la compatibilité envers l'union :
1π_NUMERO ( σ_CAPACITE < 50 ( NAVIRE ) ) ∪ π_NAVIRE ( σ_VILLE_ARRIVE = 'NICE' ( TRAVERSEE ) )Les deux opérandes ont le même degré (1) et des domaines compatibles (un numéro de navire des deux côtés). Une jointure ou une sélection n'aurait pas suffi : aucun navire ne satisfait nécessairement les deux conditions à la fois, il faut la réunion.
h) Capitaines effectuant toutes les traversées au départ de NICE. Question en « tous les » : division.
1R = π_CAPITAINE, NUMERO ( σ_VILLE_DEPART = 'NICE' ( TRAVERSEE ) )2S = π_NUMERO ( σ_VILLE_DEPART = 'NICE' ( TRAVERSEE ) )3π_CAPITAINE ( R ÷ S )i) Les mêmes, de nom différent de « De Maria ». On compose la division avec une sélection sur CAPITAINE :
1π_NOM ( σ_NOM ≠ 'De Maria' ( CAPITAINE ) ⋈ ( R ÷ S ) )l) Navires fabriqués après le navire « Casanova ». Il faut d'abord extraire la date de fabrication de « Casanova » dans une relation à un seul attribut renommé, puis comparer :
1C = ρ_DATE_CASANOVA ← DATE_FABRICATION ( π_DATE_FABRICATION ( σ_NOM = 'Casanova' ( NAVIRE ) ) )2π_NOM ( σ_NAVIRE.DATE_FABRICATION > C.DATE_CASANOVA ( NAVIRE × C ) )Le renommage n'est pas décoratif : sans lui, les deux attributs s'appelleraient DATE_FABRICATION et la condition de comparaison serait ambiguë.
#Exercice 2 — Base d'hôtels (CC novembre 2020)
1HOTEL(nom, nomPropriétaire, ville, catégorie, wifi, restaurant)2CHAMBRE(identifiantChambre, numéro, surface, nombreCouchage, minibar, nomHôtel)3EQUIPE_SERVICE(numéro, nbrPersonnes, corpsDeMétier, nomHôtel)4INTERVENTION(numéro, identifiantChambre, numéroEquipeService, raisonIntervention, date, heureDébut, heureFin)5RESERVATION(numéro, nomClient, dateDébut, dateFin, identifiantChambre)Un hôtel a un nom (supposé suffisant pour l'identifier), un propriétaire, une ville, une catégorie (simple, de charme, luxe, exceptionnel), et offre ou non le wifi et la restauration. Un hôtel a des chambres, caractérisées par un nombre de couchages, une surface et la présence d'un minibar. Chaque chambre a un numéro dans l'hôtel et un identifiant unique sur l'ensemble des hôtels. Une équipe d'intervention est forcément associée à un hôtel et identifiée par un numéro ; on note son nombre de personnes et son corps de métier. Chaque intervention est réalisée par une seule équipe et ne concerne qu'une chambre.
- Quelles sont les principales différences entre l'algèbre relationnelle et le langage SQL ?
- a) Identifiez les clés étrangères de la base.
- En algèbre relationnelle :
- b) la liste des chambres réservées par le client de nom « Dupont », avec pour chacune le nom de l'hôtel ;
- c) la liste des équipes d'intervention qui sont intervenues au moins une fois dans toutes les chambres.
- En SQL :
- d) pour chaque hôtel de catégorie « luxe », donner le nombre de chambres ;
- e) la liste des chambres qui n'ont jamais été réservées ;
- f) dans la requête suivante, la sous-requête est-elle indépendante ou corrélée ? Pourquoi ? Quel est le résultat de cette requête ?
1select CHAMBRE.identifiantChambre2from CHAMBRE3where CHAMBRE.surface > (select AVG(C.surface)4 from CHAMBRE C5 where CHAMBRE.nomHôtel = C.nomHôtel)Correction détaillée
1. Algèbre relationnelle contre SQL. Quatre différences structurelles.
- Deux syntaxes : l'algèbre est symbolique et fonctionnelle, SQL est textuel et en clauses.
- SQL est informatique, l'algèbre est formelle : SQL nomme explicitement les relations et les attributs, l'algèbre raisonne sur des symboles.
- Sacs contre ensembles : sans
DISTINCT, une projection SQL conserve les doublons, alors que la projection algébrique les élimine par définition. L'algèbre est ensembliste, SQL est multi-ensembliste. - Composition : toute opération d'algèbre rend une relation, donc s'imbrique librement (propriété de fermeture). SQL n'offre cette composition que par les sous-requêtes et les CTE.
2. a) Clés étrangères.
1CHAMBRE.nomHôtel -> HOTEL.nom2EQUIPE_SERVICE.nomHôtel -> HOTEL.nom3INTERVENTION.identifiantChambre -> CHAMBRE.identifiantChambre4INTERVENTION.numéroEquipeService -> EQUIPE_SERVICE.numéro5RESERVATION.identifiantChambre -> CHAMBRE.identifiantChambreNotez que INTERVENTION porte deux clés étrangères et aucune vers HOTEL : l'hôtel est atteignable par transitivité, via la chambre ou via l'équipe.
b) Chambres réservées par « Dupont », avec le nom de l'hôtel.
1π_CHAMBRE.numéro, HOTEL.nom ( σ_nomClient = 'Dupont' ( RESERVATION )2 ⋈_RESERVATION.identifiantChambre = CHAMBRE.identifiantChambre CHAMBRE3 ⋈_CHAMBRE.nomHôtel = HOTEL.nom HOTEL )On filtre d'abord (le client), on joint ensuite : le prédicat le plus sélectif va le plus près possible des feuilles.
c) Équipes intervenues dans toutes les chambres. Division, à nouveau :
1R = π_numéroEquipeService, identifiantChambre ( INTERVENTION )2S = π_identifiantChambre ( CHAMBRE )3π_numéroEquipeService ( R ÷ S )Une équipe qui n'est jamais intervenue n'apparaît pas dans INTERVENTION : elle ne peut donc pas figurer dans le résultat, ce qui est bien la réponse attendue.
d) Nombre de chambres par hôtel de catégorie « luxe ».
1select h.nom, count(c.identifiantChambre) as nbChambres2from HOTEL h3join CHAMBRE c on c.nomHôtel = h.nom4where h.catégorie = 'luxe'5group by h.nom;group by h.nom suffit (le nom identifie l'hôtel). Un hôtel de catégorie « luxe » sans chambre n'apparaîtrait pas avec un join : si l'énoncé attend 0, il faut un left join.
e) Chambres jamais réservées.
1select c.identifiantChambre2from CHAMBRE c3where not exists (select 1 from RESERVATION r4 where r.identifiantChambre = c.identifiantChambre);Variante équivalente : left join RESERVATION ... where r.numéro is null.
f) La sous-requête est corrélée. Elle référence CHAMBRE.nomHôtel, attribut de la requête externe : elle ne peut pas être évaluée une fois pour toutes, elle doit être réévaluée pour chaque ligne de CHAMBRE, avec une moyenne calculée sur les chambres du même hôtel. C'est ce qui la distingue d'une sous-requête indépendante, qui ne dépend pas de la ligne courante et que le moteur peut calculer une seule fois.
Résultat : la requête renvoie les identifiants des chambres dont la surface est supérieure à la surface moyenne des chambres de leur propre hôtel. Une chambre de 30 m² dans un hôtel dont la moyenne est de 20 m² sort ; la même chambre dans un hôtel dont la moyenne est de 40 m² ne sort pas. C'est un classement relatif, jamais absolu.
#Exercice 3 — Apple Store (contrôle continu avril 2012, 3 heures)
On souhaite représenter une version simplifiée du système de gestion des données de l'Apple Store.
Le magasin propose des applications téléchargeables classées par catégories (Actualité, Productivité, Jeux, Météo, Style de vie). Chaque catégorie a une description brève. Chaque application a un identifiant unique interne, un nom, un prix en dollars, une date de sortie et le nom du développeur. Une application est associée à une catégorie principale et possiblement à une catégorie secondaire.
Chaque utilisateur doit créer un compte, identifié par son email. On connaît aussi son nom, son prénom, sa date de naissance, son numéro de carte bancaire et la date d'expiration de celle-ci. Le pictogramme de vérification ne doit surtout pas être enregistré. Le compte est protégé par un mot de passe. Chaque utilisateur peut lister les terminaux Apple (iPhone, iPad, iPod) qu'il possède ; ces terminaux sont attachés à ce compte et à aucun autre. Pour chaque terminal, on renseigne son numéro de série (unique), sa date d'achat et un nom d'usage.
Chaque utilisateur peut acheter des applications. Chaque achat est référencé par son numéro de facture, et la date d'achat est importante. Une application peut être achetée par plusieurs utilisateurs. Enfin, chaque application possédée par un utilisateur peut être installée sur un ou plusieurs de ses terminaux, avec une date d'installation ; seules les applications achetées par l'utilisateur peuvent être installées. La solution doit contenir six tables.
Partie 1 — conception. Concevez la base et donnez son équivalent SQL.
Partie 2 — requêtes de consultation.
- Lister les noms d'applications dont la catégorie secondaire n'est pas spécifiée.
- Lister les utilisateurs (email, nom, prénom) ayant acheté au moins une application.
- Pour chaque catégorie principale, donner le nombre d'applications achetées par l'utilisateur ayant pour email
jean-vivien.millo@inria.fr. - Lister les utilisateurs (nom, prénom) qui n'ont pas acheté l'application « Angry birds ».
- Lister les utilisateurs qui ont acheté toutes les applications.
- Donner l'email des personnes ainsi que les noms des applications associées dont le prix dépasse celui de l'application la plus chère de la catégorie « Sport ».
- Lister les utilisateurs (nom et prénom) qui possèdent des applications mais qui ne les ont pas installées.
Partie 3 — requêtes de mise à jour.
- Ajouter une information indiquant la taille en kilo-octet d'une application.
- Baisser de 10 centimes le prix de toutes les applications qui coûtent plus de 2 dollars.
- Créer une nouvelle table, par une requête unique, constituée du nom et du prénom de l'utilisateur, du numéro de facture et de la date d'achat.
Correction détaillée
Partie 1 — schéma (six tables).
1create table Categorie (2 numCat integer primary key,3 libelle text unique not null,4 description text5);6 7create table Application (8 idApp integer primary key,9 nom text not null,10 prix real not null,11 dateSortie text,12 developpeur text,13 catPrincipale integer not null references Categorie(numCat),14 catSecondaire integer references Categorie(numCat) -- nullable : catégorie secondaire facultativeTrois points de conception que l'énoncé fait travailler :
catSecondairedoit accepter NULL : une application peut ne pas avoir de catégorie secondaire, et c'est la question 1 de la partie 2 qui l'exploite.- La contrainte « seules les applications achetées peuvent être installées » est encodée par la clé étrangère composite de
InstallationversAchat(email, idApp). Pour que ce soit possible, il faut déclarer une contrainte d'unicité sur(email, idApp)dansAchat— la clé primairenumFacturene suffit pas à servir de cible. - « attachés à ce compte et à aucun autre » interdit de mettre
numSeriedansUtilisateur(colonne multivaluée) : d'où la tableTerminalavec sa clé étrangère.
Partie 2 — requêtes.
1-- 1. Applications sans catégorie secondaire2select nom from Application where catSecondaire is null;3 4-- 2. Utilisateurs ayant acheté au moins une application5select distinct u.email, u.nom, u.prenom6from Utilisateur u7join Achat a on a.email = u.email;8 9-- 3. Nombre d'applications achetées par catégorie principale, pour un utilisateur donné10select ap.catPrincipale, count(*) as nb11from Achat a12join Application ap on ap.idApp = a.idApp13where a.email = 'jean-vivien.millo@inria.fr'14group by ap.catPrincipale;Deux remarques de fond.
- La question 4 se résout par une anti-jointure : « n'a pas acheté » couvre aussi les utilisateurs qui n'ont rien acheté du tout. Un
joinpuiswhere ap.nom <> 'Angry birds'donnerait une réponse fausse, puisqu'il exclurait silencieusement les clients sans achat. - Les questions 5 et 6 sont les deux formes de la question en « tous les » et de la comparaison à un agrégat global. La question 5 est une division (double
not exists) ; la question 6 est une sous-requête scalaire dans leWHERE, qui peut être non corrélée parce que le maximum de la catégorie « Sport » est une constante.
Partie 3 — mises à jour.
1-- 1. Ajouter la taille en kilo-octets2alter table Application add column tailleKo integer;3 4-- 2. Baisser de 10 centimes les applications à plus de 2 dollars5update Application set prix = prix - 0.10 where prix > 2;6 7-- 3. Créer une table à partir d'une requête (CREATE TABLE ... AS SELECT)8create table AchatUtilisateur as9select u.nom, u.prenom, a.numFacture, a.dateAchat10from Utilisateur u11join Achat a on a.email = u.email;La requête 3 illustre la différence entre LDD et LMD : create table ... as select est une instruction de définition de données qui se nourrit d'une instruction de manipulation. C'est le pont entre les deux langages d'un SGBD.
#Exercice 4 — CarTrack et Armazon (CC n°2, novembre 2013)
Partie 1 — conception (10 pts). CarTrack est une startup qui fournit des services de sécurité aux voitures : suivi 24h/24, blocage à distance, récupération de véhicules volés, déverrouillage à distance.
Pour chaque voiture, on note la marque, le modèle, la couleur, la plaque, le propriétaire (un client), la date d'immatriculation et le code du système installé. Une voiture a un seul système et un système est associé à une seule voiture ; il est toutefois conseillé de répertorier les systèmes dans une table à part. Pour chaque client, on stocke le nom, le prénom, l'adresse et le numéro de téléphone.
Quand le système central envoie une requête sur l'état de la voiture, le système du véhicule retourne les coordonnées GPS (latitude, longitude), l'état de l'alarme (active ou inactive) et l'état des portes (déverrouillées ou verrouillées). Une fois l'information reçue, le système central inscrit la date, l'heure et les données reçues.
L'entreprise a un groupe de partenaires qui installent son système dans les voitures. Pour chaque partenaire, on enregistre son nom, son adresse, son numéro de téléphone et le code des systèmes installés. Pour chaque système installé par un partenaire, le test d'installation doit être enregistré.
Partie 2 — requêtes SQL (10 pts). Armazon vend tout type de produits. Schéma partiel :
1Client(code, nom, prenom, codeAdresse, telephone)2Categorie(code, nom)3Produit(code, nom, description, codeCategorie, prix)4Stock(date, codeProduit, unites)5Commande(code, date, codeClient, codeProduit, quantite)- Nombre d'unités disponibles du ou des produits « Ventilateur ».
- Nombre de commandes de livres effectués par « Marie Pascal » (Livre est une catégorie).
- Les clients (code) qui ont acheté pendant le mois de novembre 2013, avec pour chacun la quantité totale de produits achetés, le résultat étant ordonné selon la quantité décroissante.
- Noms des produits qui ne sont pas achetés.
- Les produits dont le prix est supérieur au prix moyen des produits de la même catégorie.
Correction détaillée
Partie 1 — schéma CarTrack. L'énoncé laisse volontairement des zones d'ambiguïté ; l'attendu est un schéma cohérent accompagné de ses justifications.
1create table Client (2 numClient integer primary key,3 nom text not null, prenom text not null,4 adresse text, telephone text5);6 7create table Voiture (8 plaque text primary key,9 marque text, modele text, couleur text,10 dateImmatriculation text,11 numClient integer not null references Client(numClient)12);13 14create table Systeme (Les décisions à justifier :
- Le 1-1 est modélisé par une table
Systemeséparée, comme le suggère l'énoncé, avec une contrainteuniquesurplaque: c'est cette unicité qui encode le « un seul système par voiture ». Sans elle, la relation serait 1-N. - Les relevés sont dans une table à part parce qu'un système produit une quantité non bornée de mesures : les stocker en colonnes de
Systemeserait un attribut multivalué. - Le lien partenaire-système est N-N (un partenaire installe plusieurs systèmes, un système est installé par un partenaire), et il porte des attributs (la date et le résultat du test d'installation). C'est le cas typique qui impose la table d'association.
- Le test d'installation appartient à l'association, pas au système seul : deux partenaires peuvent avoir installé le même modèle de système et obtenu des résultats différents.
Partie 2 — requêtes SQL.
1-- 1. Unités disponibles du ou des produits « Ventilateur »2select p.code, p.nom, sum(s.unites) as unitesDisponibles3from Produit p4join Stock s on s.codeProduit = p.code5where p.nom = 'Ventilateur'6group by p.code, p.nom;Le group by est nécessaire : Stock est historisé par date, et le même produit peut apparaître sur plusieurs lignes. Sans regroupement, on obtiendrait une ligne par relevé de stock.
1-- 2. Nombre de commandes de livres effectués par Marie Pascal2select count(*) as nbCommandes3from Commande c4join Client cl on cl.code = c.codeClient5join Produit p on p.code = c.codeProduit6join Categorie cat on cat.code = p.codeCategorie7where cl.nom = 'Pascal' and cl.prenom = 'Marie' and cat.nom = 'Livre';Trois jointures sont inévitables : le nom du client est dans Client, la catégorie du produit dans Categorie, et le lien passe par Produit. La comparaison porte sur nom et prenom : rien ne garantit l'unicité du nom de famille.
1-- 3. Clients ayant acheté en novembre 2013, avec quantité totale, tri décroissant2select c.codeClient, sum(c.quantite) as quantiteTotale3from Commande c4where c.date >= '2013-11-01' and c.date < '2013-12-01'5group by c.codeClient6order by quantiteTotale desc;Le filtre de date s'écrit en bornes (>= et <) plutôt qu'avec between ou month() : c'est à la fois SARGable (un index sur date reste utilisable) et correct sur les bornes — between '2013-11-01' and '2013-11-30' raterait les commandes du 30 novembre à 14h si la colonne contenait une heure.
1-- 4. Produits jamais achetés2select p.nom3from Produit p4where not exists (select 1 from Commande c where c.codeProduit = p.code);5 6-- 5. Produits plus chers que la moyenne de leur catégorie7select p.nom, p.prix, p.codeCategorie8from Produit p9where p.prix > (select avg(p2.prix)10 from Produit p211 where p2.codeCategorie = p.codeCategorie);La question 5 est le cas d'école de la sous-requête corrélée : la moyenne dépend de la catégorie de la ligne courante, donc elle est recalculée pour chaque produit. Une version non corrélée serait fausse, puisqu'elle comparerait chaque produit à la moyenne globale du catalogue. Une variante par jointure sur une table dérivée donne le même résultat :
1select p.nom, p.prix2from Produit p3join (select codeCategorie, avg(prix) as moyenne from Produit group by codeCategorie) m4 on m.codeCategorie = p.codeCategorie5where p.prix > m.moyenne;#Exercice 5 — Modélisation E-A d'une base Zoo (extrait du CT décembre 2016)
Chaque animal est identifié par son numéro, a un nom, appartient à une espèce et est parqué dans un emplacement numéroté. Un emplacement peut être vide ou accueillir plusieurs animaux. Chaque emplacement, identifié par son numéro, appartient à un type (volière, cage, îlot). À chaque emplacement sont affectés un ou plusieurs gardiens, et un gardien peut être affecté à plusieurs emplacements. Chaque gardien a un numéro d'identification, un nom, un salaire et un remplaçant, qui est lui aussi un gardien. Un gardien peut être le remplaçant de plusieurs autres gardiens.
- Concevez un modèle E-A pour cette base (notation UML ou Chen, au choix).
- Transformez le modèle E-A obtenu en un ensemble de tables, avec clés primaires et étrangères définies.
Correction détaillée
1. Modèle E-A. Quatre entités et deux associations, plus une association réflexive.
1Animal(numAnimal, nom, espece) -- association « parqué dans » vers Emplacement, 1..1 / 0..*2Emplacement(numEmplacement, type) -- association « affecté à » vers Gardien, 1..* / 0..*3Gardien(numGardien, nom, salaire) -- association réflexive « remplace », 0..* / 0..1- Animal — parqué dans — Emplacement : cardinalités 1..1 du côté animal (un animal est dans exactement un emplacement) et 0..* du côté emplacement (un emplacement peut être vide). C'est un 1-N.
- Emplacement — affecté à — Gardien : 1..* des deux côtés. C'est un N-N, donc une table d'association.
- Gardien — remplace — Gardien : association réflexive, 0..1 du côté « remplaçant » et 0..* du côté « remplacé ». Un gardien a au plus un remplaçant ; il peut être le remplaçant de plusieurs.
2. Tables.
1Animal(numAnimal, nom, espece, numEmplacement#)2Emplacement(numEmplacement, type)3Gardien(numGardien, nom, salaire, numRemplacant#)4Affectation(numEmplacement#, numGardien#)Application des trois règles de passage : l'association 1-N Animal–Emplacement place la clé étrangère du côté « 1 », c'est-à-dire dans Animal ; l'association N-N Emplacement–Gardien devient la table Affectation à clé primaire composite ; l'association réflexive se traduit par une clé étrangère de Gardien vers Gardien, numRemplacant étant nullable puisqu'un gardien peut n'avoir aucun remplaçant.
La contrainte « il ne peut pas être son propre remplaçant » n'est pas exprimable par une simple clé étrangère : elle demande un check (numRemplacant <> numGardien) — et le fait de pouvoir nommer précisément cette limite est souvent ce qui distingue une bonne copie.
#Exercice 6 — Transformation E-A d'un fragment enseignant-chercheur (TD 9)
Transformez en schéma relationnel ce fragment de schéma E-A du département d'Informatique de l'Université Côte d'Azur.
1Enseignant(Numéro, Nom, Age) -- Donne, 1..1 / 0..* --> Cours(Code, Intitulé, Nb_heures)2Enseignant -- Recherche pour, 1..* / 0..* --> Projet(Code, Intitulé, Description)Chaque enseignant donne aucun ou plusieurs cours. Chaque cours est enseigné par un seul enseignant. Chaque enseignant fait de la recherche pour aucun ou plusieurs projets. Chaque projet est géré par un ou plusieurs enseignants.
Correction détaillée
1Enseignant(Numéro, Nom, Age)2Cours(Code, Intitulé, Nb_heures, num_enseignant#)3Projet(Code, Intitulé, Description)4EnseignantProjet(num_enseignant#, code_projet#)Association « Donne » (1-N). La cardinalité de (Enseignant, Donne) vaut 1 : la clé étrangère va du côté « 1 », donc dans Cours. Mettre num_cours dans Enseignant serait une faute : un enseignant donne plusieurs cours, donc la colonne serait multivaluée — exactement ce que la 1NF interdit. C'est l'erreur que le corrigé du TD signale en premier.
Association « Recherche pour » (N-N). Aucune des deux cardinalités ne vaut 1 : impossible de placer la clé étrangère d'un côté ou de l'autre sans créer de doublons ou violer la 1NF. Il faut la table intermédiaire EnseignantProjet, dont la clé primaire est composite — les deux clés étrangères — ce qui interdit d'associer deux fois le même couple enseignant-projet.
Le critère de décision, énoncé une fois pour toutes : si la cardinalité d'une entité dans l'association vaut 1, la clé étrangère va chez elle ; sinon, l'association devient une table.
#Exercice 7 — Planifications et graphes de précédence (TD 10)
Pour chacune des planifications suivantes, tracez un graphe de précédence et indiquez si la planification garantit la sérialisation des conflits. Pour les planifications sérialisables, donnez une planification sérielle équivalente.
1a) L(T1,x), L(T2,x), E(T1,x), E(T2,x), val(T1), val(T2)2b) L(T1,x), L(T2,y), E(T3,x), E(T1,y), val(T1), val(T2), val(T3)3c) L(T1,x), E(T2,x), E(T1,x), avort(T2), val(T1)4d) E(T1,x), L(T2,x), E(T1,x), val(T2), avort(T1)5e) L(T1,x), E(T2,x), E(T1,x), L(T3,x), val(T1), val(T2), val(T3)Correction détaillée
Méthode : écrire d'abord le tableau des temps (chaque action correspond à un temps), puis chercher les couples lecture/écriture sur la même donnée, puis chercher un cycle.
a) T1 lit x (t1), T2 lit x (t2), T1 écrit x (t3), T2 écrit x (t4).
- T2 lit x avant que T1 ne l'écrive : flèche .
- T1 écrit x avant que T2 ne l'écrive : flèche .
Le graphe est cyclique : la planification n'est pas sérialisable.
b) T1 lit x (t1), T2 lit y (t2), T3 écrit x (t3), T1 écrit y (t4).
- T1 a lu x avant l'écriture de T3 : flèche .
- T2 a lu y avant l'écriture de T1 : flèche .
Le graphe est acyclique : la planification est sérialisable. L'ordre topologique fournit la planification sérielle équivalente : les opérations de T2 d'abord, puis celles de T1, puis celles de T3.
c) T1 lit x (t1), T2 écrit x (t2), T1 écrit x (t3), T2 avorte (t4), T1 valide (t5).
- T1 lit x avant que T2 ne l'écrive : flèche .
- T2 écrit x avant que T1 ne l'écrive : flèche .
Le graphe brut est cyclique. Mais T2 avorte : ses effets sont annulés (c'est l'atomicité), et ses opérations ne participent donc plus à l'exécution finale. Une fois T2 retirée du graphe, il ne reste que T1 : la planification est sérialisable, et la planification sérielle équivalente se réduit à T1 seule. Le point à retenir : une transaction avortée ne peut pas figurer dans l'ordre sériel équivalent.
d) T1 écrit x (t1), T2 lit x (t2), T1 écrit x (t3), T2 valide (t4), T1 avorte (t5).
- T1 a écrit x avant que T2 ne le lise : flèche .
- T2 a lu x avant que T1 ne le réécrive : flèche .
Le graphe est cyclique : la planification n'est pas sérialisable. Le fait que T1 avorte ensuite ne rachète rien : T2 a validé sur la base d'une valeur que T1 a ensuite annulée — c'est exactement le problème de la dépendance non validée. C'est la différence essentielle avec le cas c.
e) T1 lit x (t1), T2 écrit x (t2), T1 écrit x (t3), T3 lit x (t4).
- T1 lit x avant l'écriture de T2 : .
- T2 écrit x avant l'écriture de T1 : .
- T1 écrit x avant la lecture de T3 : ; T2 aussi : .
Le cycle subsiste : la planification n'est pas sérialisable. L'ajout de T3, qui ne fait que lire après coup, ne change rien au diagnostic.
#Exercice 8 — Sérialisabilité d'une longue planification (TD 10)
Déterminez si la planification suivante est sérialisable au niveau des conflits :
1P = [ L1(Z), L2(Y), E2(Y), L3(Y), L1(X), E1(X), E1(Z), E3(Y), L2(X), L1(Y), E1(Y), E2(X), L3(W), E3(W) ]où et désignent une lecture et une écriture de la donnée Z par la transaction .
Correction détaillée
On parcourt la séquence dans l'ordre et on enregistre, pour chaque donnée, le dernier écrivain et les lecteurs depuis cette écriture. Une flèche apparaît dès que écrit une donnée que a lue, ou que lit une donnée que a écrite.
| Temps | Action | Conséquence |
|---|---|---|
| t1 | Z lu par T1 | |
| t2 | Y lu par T2 | |
| t3 | Y écrit par T2 | |
| t4 | : T3 lit Y écrit par T2 | |
| t5 | X lu par T1 | |
| t6 | X écrit par T1 | |
| t7 | Z écrit par T1 (T1 était seule lectrice) | |
| t8 | : T3 écrit Y après T2 | |
| t9 | : T2 lit X écrit par T1 | |
| t10 | Y lu par T1 | |
| t11 | et : T1 écrit Y lu par T2 et T3 | |
| t12 | : T2 écrit X après T1 | |
| t13 | W lu par T3 | |
| t14 | W écrit par T3 |
Ensemble des flèches : , , , .
Le graphe contient le cycle : la planification n'est pas sérialisable au niveau des conflits.
#Exercice 9 — Graphe d'attente et verrous indéfinis (TD 10 / extrait du CT 2016)
Produisez un graphe d'attente pour le scénario de transactions suivant et déterminez si des verrous indéfinis y apparaissent.
| Transaction | Données verrouillées | Données attendues |
|---|---|---|
| T1 | x2 | x1, x3 |
| T2 | x3, x10 | x7, x8 |
| T3 | x8 | x4, x5 |
| T4 | x7 | x1 |
| T5 | x1, x5 | x3 |
| T6 | x4, x9 | x6 |
| T7 | x6 | x5 |
Correction détaillée
Construction du graphe. Une flèche existe quand attend un élément actuellement verrouillé par . On croise la colonne « attend » avec la colonne « verrouille » :
| Attente | Détenu par | Flèche |
|---|---|---|
| T1 attend x1 | T5 | |
| T1 attend x3 | T2 | |
| T2 attend x7 | T4 | |
| T2 attend x8 | T3 | |
| T3 attend x4 | T6 | |
| T3 attend x5 | T5 | |
| T4 attend x1 | T5 | |
| T5 attend x3 | T2 | |
| T6 attend x6 | T7 | |
| T7 attend x5 | T5 |
1T1 ──→ T5 T5 ──→ T22T1 ──→ T2 T6 ──→ T73T2 ──→ T4 T7 ──→ T54T2 ──→ T35T3 ──→ T66T3 ──→ T57T4 ──→ T5Détection. Par le théorème de Holt, un verrou indéfini existe si et seulement si le graphe des attentes contient un cycle. Les cycles orientés simples sont au nombre de trois :
1T2 → T3 → T5 → T22T2 → T4 → T5 → T23T2 → T3 → T6 → T7 → T5 → T2Conclusion : le graphe contient des cycles, donc il y a interblocage. Les transactions concernées sont T2, T3, T4, T5, T6 et T7 ; T1 attend un verrou détenu par T5 mais personne n'attend un verrou détenu par T1, elle n'est donc pas dans un cycle — elle subit l'attente sans participer à l'impasse.
#Exercice 10 — Mise à jour perdue et protocole V2P (TD 10)
Déterminez le problème de la planification suivante et résolvez-le à l'aide du protocole de verrouillage en deux phases.
| Temps | T1 | T2 |
|---|---|---|
| t1 | verrou-écriture(qte) | verrou-écriture(qte) |
| t2 | lire qte | attente |
| t3 | qte ← qte + 3000 | attente |
| t4 | validation / déverrouillage | lire qte |
| t5 | qte ← qte + 500 | |
| t6 | validation / déverrouillage |
Correction détaillée
Le problème : la mise à jour perdue. Sans verrouillage, les deux transactions liraient la même valeur initiale de qte et écriraient chacune une valeur calculée à partir de cette valeur initiale. La seconde écriture écraserait la première : sur un stock de 1000, on attendrait 4500 et on obtiendrait 1500 ou 4000 selon l'ordre. C'est exactement « une opération de modification apparemment achevée avec succès est écrasée par un autre utilisateur ».
La résolution par le verrouillage. Le tableau montre le mécanisme en action :
- t1 : T1 obtient le verrou d'écriture sur
qte. T2 demande le même verrou et doit attendre : un verrou exclusif n'est pas compatible avec un autre verrou exclusif. - t2 et t3 : T1 est seule à pouvoir lire et écrire. Elle lit la valeur, calcule
qte + 3000. - t4 : T1 valide et déverrouille. Ce n'est qu'à ce moment que les effets de l'écriture deviennent visibles aux autres transactions.
- t4 à t6 : T2 lit alors la valeur déjà modifiée par T1, et calcule
qte + 500à partir d'elle.
Le résultat final est la somme des deux mises à jour, ce qui est le comportement attendu. Le protocole de verrouillage en deux phases est ce qui garantit que T2 ne peut pas acquérir son verrou après en avoir libéré un, ni libérer un verrou avant d'avoir acquis tous ceux dont elle a besoin : les opérations de verrouillage précèdent toutes la première opération de déverrouillage.
Pourquoi le verrouillage seul ne suffit pas. Le cours donne une planification de verrouillage incorrecte pour le prouver : T9 déverrouille soldex avant d'avoir fini sa phase de croissance, ce qui laisse T10 s'insérer au milieu et produit un résultat qui ne correspond à aucune exécution sérielle (soldex = 220 et soldey = 340 alors que les deux ordres sériels donnent respectivement 220/330 et 210/340). C'est la discipline en deux phases qui garantit la sérialisabilité, pas la présence de verrous.
#Exercice 11 — Bibliothèque de DVD (TD 1 / TP 1)
Schéma fourni par l'énoncé (les clés primaires sont soulignées en sujet, les clés étrangères en italique) :
1Societe(numS, nomS, rueS, villeS, directeurS)2Dvd(numD, titreD, auteurD, anneeD, categorieD, dateAchatD, nombreD, societeD)3Acteur(numA, nomA, prenomA, ageA, sexeA)4Casting(dvdC, acteurC, roleC)5Emprunt(dvdE, dateE, clientE, dureeE)Chaque société de location a un nom et une adresse, ainsi qu'un nom de directeur ; plusieurs sociétés peuvent porter le même nom. Un DVD a un titre, un auteur, une année de sortie, un numéro d'identification, une catégorie, une date d'acquisition, un nombre d'exemplaires achetés et la société qui l'a acquis. Pour un acteur, on mémorise son nom, son prénom, son âge et son sexe ; pour chaque DVD et chaque acteur principal, on mémorise le rôle tenu. Un DVD est emprunté par un client (seul son numéro est mémorisé) à une date donnée et pour une durée déterminée. Un même client ne peut pas emprunter un même DVD plus d'une fois par jour.
Mettez en œuvre les requêtes suivantes :
- La liste des DVD acquis par la société « DVDStore ».
- La liste des DVD acquis par la société « DVDStore », avec pour chacun le nom et le prénom des acteurs qui ont participé au film.
- La liste des acteurs de chaque DVD.
- La liste des clients ayant loué un DVD dont un des acteurs a « Xavier » pour prénom.
Correction détaillée
Un point de conception à comprendre d'abord. « Plusieurs sociétés de location peuvent porter le même nom » : nomS ne peut donc pas être la clé primaire de Societe, d'où numS. De même, Dvd possède son propre identifiant numD : un même film (même titre, même année) peut exister chez deux sociétés, et ce sont deux lignes différentes, donc deux DVD différents. C'est ce que le corrigé du TD souligne : bien comprendre le rôle de l'identifiant unique. Conséquence directe sur la question 4 : un même film loué chez deux sociétés n'est pas le même DVD.
Requête 1.
1select d.numD, d.titreD2from Dvd d3join Societe s on s.numS = d.societeD4where s.nomS = 'DVDStore';La jointure est indispensable : le nom de la société n'est pas dans Dvd, seulement sa clé étrangère.
Requête 2.
1select d.numD, d.titreD, a.nomA, a.prenomA2from Dvd d3join Societe s on s.numS = d.societeD4join Casting c on c.dvdC = d.numD5join Acteur a on a.numA = c.acteurC6where s.nomS = 'DVDStore'7order by d.numD, a.nomA;Trois jointures en chaîne, et le rôle (roleC) est disponible dans Casting si l'énoncé le demande. Attention à la cardinalité : un DVD avec cinq acteurs produit cinq lignes. Si l'on veut une ligne par DVD avec la liste des acteurs, il faut agréger — group_concat en SQLite et MySQL, string_agg en PostgreSQL.
Requête 3.
1select d.titreD, a.nomA, a.prenomA, c.roleC2from Dvd d3join Casting c on c.dvdC = d.numD4join Acteur a on a.numA = c.acteurC5order by d.titreD, a.nomA;C'est la même jointure que la question 2 sans le filtre sur la société : Casting est la table d'association qui porte la relation N-N entre Dvd et Acteur, et c'est elle qui porte aussi le rôle — attribut de l'association, pas de l'acteur.
Requête 4.
1select distinct e.clientE2from Emprunt e3join Casting c on c.dvdC = e.dvdE4join Acteur a on a.numA = c.acteurC5where a.prenomA = 'Xavier';Le distinct est nécessaire : un client qui a loué trois films avec Xavier apparaîtrait trois fois. Comme toujours quand la question porte sur l'existence d'une correspondance, la semi-jointure est plus fidèle :
1select e.clientE2from Emprunt e3where exists (4 select 1 from Casting c5 join Acteur a on a.numA = c.acteurC6 where c.dvdC = e.dvdE and a.prenomA = 'Xavier'7);#Exercice 12 — Questions de cours du contrôle terminal (décembre 2016)
- Quelle est l'utilité du protocole de verrouillage en deux phases ?
- En SQL, quel est l'ordre de traitement des clauses
SELECT,FROM,WHERE,GROUP BY,HAVING,ORDER BY? Quelle est la logique de cet ordre ?
Correction détaillée
1. Utilité du V2P. Il garantit la sérialisabilité des conflits : il évite l'incohérence des données lorsque deux transactions effectuent des opérations de lecture et d'écriture sur une même valeur. Le protocole impose que toutes les opérations de verrouillage d'une transaction précèdent sa première opération de déverrouillage. La transaction se décompose alors en une phase de croissance (elle acquiert tous ses verrous, n'en libère aucun) et une phase de résorption (elle libère ses verrous, n'en acquiert plus).
Le mécanisme pratique : la première transaction arrivée obtient le verrou exclusif, ce qui restreint la lecture et l'écriture de la valeur à elle seule ; les autres transactions doivent attendre la fin de la transaction détentrice. Une fois celle-ci validée, elle cède sa place et la valeur finale reste cohérente. C'est le « premier arrivé, premier servi » — mais c'est la discipline en deux phases, et non le verrouillage en soi, qui empêche une transaction de s'insérer au milieu d'une autre.
2. Ordre de traitement des clauses. L'ordre est :
1FROM -> WHERE -> GROUP BY -> HAVING -> sous-requête -> SELECT -> ORDER BY -> LIMITLa logique est celle d'un pipeline : chaque étape ne peut utiliser que ce que les étapes précédentes ont produit.
FROMlit les tables : c'est la source.WHEREapplique une ou des conditions sur les lignes issues de ces tables.GROUP BYregroupe les lignes selon un attribut — les lignes regroupées sont celles qui ont passé leWHERE.HAVINGn'affiche les groupes que selon une condition de calcul sur un attribut.- La sous-requête récupère un jeu de données, qui peut ensuite alimenter le
SELECT. SELECTsélectionne les attributs.ORDER BYfixe l'ordre d'affichage.LIMITborne le nombre de lignes affichées.
Deux conséquences pratiques découlent de cet ordre : un alias défini dans le SELECT n'est pas utilisable dans le WHERE (évalué avant) mais l'est dans le ORDER BY (évalué après) ; et un agrégat comme AVG(...) ne peut pas apparaître dans un WHERE, puisqu'il n'existe pas encore à ce stade.
#Synthèse : ce qu'il faut savoir faire sans hésiter
| Compétence | Où elle est évaluée | Section du module |
|---|---|---|
| Énumérer les clés étrangères d'un schéma | Tous les sujets | Modélisation |
| Traduire un énoncé en E-A puis en tables | CT 2016, TD 9 | Modélisation |
| Écrire une requête en algèbre relationnelle | CC 2015, CC 2020, TD 3 | Algèbre relationnelle |
| Reconnaître une question en « tous les » (division) | CC 2015, CC 2020, CC 2012 | Algèbre relationnelle |
| Traduire une expression algébrique en SQL et inversement | CC 2015, TD 3 | Algèbre relationnelle |
| Écrire des requêtes SQL de consultation, jointures comprises | CC 2012, CC 2013, CC 2020 | SELECT, JOIN |
| Agréger et filtrer des groupes | TD 5, CC 2013 | Agrégations |
| Distinguer sous-requête corrélée et indépendante | CC 2020, CC 2013 | SELECT, JOIN |
| Normaliser une table plate jusqu'en 3NF | CT 2016, TD 9 | Normalisation |
| Diagnostiquer les anomalies d'une table dénormalisée | TD 9 | Normalisation |
| Construire un graphe de précédence et conclure | TD 10 | Transactions |
| Construire un graphe d'attente et détecter un interblocage | CT 2016, TD 10 | Transactions |
| Expliquer l'utilité du V2P | CT 2016 | Transactions |