Exercices Corrigés – Bases de Données & SQL
- Lisez l'énoncé et essayez de répondre par vous-même avant d'ouvrir la correction.
- Les corrections sont progressives : commencez par identifier les entités et relations, puis passez au SQL.
- Si vous bloquez, relisez les chapitres correspondants avant de consulter la correction.
Partie 1 – Modélisation : MCD et MLD
Ces exercices couvrent la modélisation conceptuelle (MCD) et logique (MLD). Pour chaque énoncé, identifiez d'abord les entités, puis les relations entre elles et leur cardinalité.
Exercice 1 – Bibliothèque
Une bibliothèque gère des livres, des auteurs et des emprunts.
- Un auteur peut avoir écrit plusieurs livres, et un livre a un seul auteur principal.
- Un lecteur peut emprunter plusieurs livres, et un livre peut être emprunté par plusieurs lecteurs (à des dates différentes).
- Pour chaque emprunt, on enregistre la date d'emprunt et la date de retour prévue.
- Identifiez les entités et leurs attributs.
- Identifiez les relations et leurs cardinalités.
- Proposez un MCD (notation textuelle).
- Déduisez le MLD correspondant.
Correction
Entités et attributs :
Auteur: idAuteur, nom, prenomLivre: idLivre, titre, genre, anneeLecteur: idLecteur, nom, prenom, email
Relations :
Auteur—(1,1)— Ecrit —(0,N)—Livre→ un auteur écrit 0 à N livres, un livre a exactement 1 auteurLecteur—(0,N)— Emprunt —(0,N)—Livre→ relation N-M avec attribut (dateEmprunt, dateRetour)
MCD :
[Auteur] --(1,N)-- <Ecrit> --(1,1)-- [Livre] --(0,N)-- <Emprunt> --(0,N)-- [Lecteur]
idAuteur idLivre dateEmprunt idLecteur
nom titre dateRetour nom
prenom genre prenom
annee email
MLD :
Auteur(idAuteur PK, nom, prenom)
Livre(idLivre PK, titre, genre, annee, idAuteur FK -> Auteur)
Lecteur(idLecteur PK, nom, prenom, email)
Emprunt(idLivre PK FK -> Livre, idLecteur PK FK -> Lecteur, dateEmprunt, dateRetour)
Pourquoi Emprunt a une clé primaire composite ? Un même livre peut être emprunté plusieurs fois (par des lecteurs différents). La clé (idLivre, idLecteur) n'est pas suffisante si on veut autoriser plusieurs emprunts du même livre par le même lecteur. Dans ce cas, on ajouterait dateEmprunt à la clé primaire.
Exercice 2 – Entreprise et projets
Une entreprise souhaite modéliser ses salariés, départements et projets.
- Chaque salarié appartient à un seul département.
- Un salarié peut travailler sur plusieurs projets.
- Un projet peut impliquer plusieurs salariés.
- On veut aussi connaître le rôle de chaque salarié sur un projet (ex : développeur, chef de projet).
- Proposez le MCD.
- Déduisez le MLD.
- Quelle est la nature de la relation Salarié–Projet ? Comment la traduit-on en MLD ?
Correction
MCD :
[Departement] --(1,N)-- <Appartient> --(1,1)-- [Salarie] --(0,N)-- <TravailleSur> --(0,N)-- [Projet]
idDept idSalarie role idProjet
nomDept nom nomProjet
localisation prenom
MLD :
Departement(idDept PK, nomDept, localisation)
Salarie(idSalarie PK, nom, prenom, idDept FK -> Departement)
Projet(idProjet PK, nomProjet)
TravailleSur(idSalarie PK FK -> Salarie, idProjet PK FK -> Projet, role)
Explication :
- La relation Département–Salarié est 1-N : elle se traduit par une clé étrangère
idDeptdans la tableSalarie. - La relation Salarié–Projet est N-M : elle génère une table de liaison
TravailleSur. - L'attribut
roleappartient à la relation (pas à une entité) : il va dans la table de liaison.
Exercice 3 – Plateforme vidéo
Modélise une plateforme de vidéos en ligne avec les règles suivantes :
- Un utilisateur peut poster plusieurs vidéos.
- Un utilisateur peut commenter plusieurs vidéos.
- Chaque commentaire est lié à une vidéo et à un utilisateur.
- Une vidéo peut recevoir plusieurs commentaires.
- On veut aussi pouvoir enregistrer les likes (un utilisateur peut liker plusieurs vidéos).
- Proposez le MCD.
- Déduisez le MLD.
Correction
MCD :
[Utilisateur] --(1,N)-- <Poste> --(1,1)-- [Video]
| |
<Like> <Commente>
| |
(0,N) (1,N)
|
[Commentaire]
idCom
contenu
dateCom
MLD :
Utilisateur(idUser PK, nom, email, dateInscription)
Video(idVideo PK, titre, datePublication, duree, idUser FK -> Utilisateur)
Commentaire(idCom PK, contenu, dateCom, idUser FK -> Utilisateur, idVideo FK -> Video)
Like(idUser PK FK -> Utilisateur, idVideo PK FK -> Video, dateLike)
Explication :
Videoporte la cléidUsercar un utilisateur poste une vidéo (relation 1-N).Commentaireporte deux clés étrangères : vers l'auteur du commentaire et vers la vidéo concernée.Likeest une table de liaison N-M entreUtilisateuretVideo.
Exercice 4 – Hôtel
Un hôtel souhaite gérer ses chambres, ses clients et ses réservations.
- Un client peut effectuer plusieurs réservations.
- Une réservation porte sur une seule chambre.
- Une chambre a un type (simple, double, suite) et un tarif par nuit.
- On enregistre pour chaque réservation la date d'arrivée et la date de départ.
- Identifiez les entités et leurs attributs.
- Identifiez les relations et leurs cardinalités.
- Proposez le MLD.
Correction
Entités et attributs :
Client: idClient, nom, prenom, email, telephoneChambre: idChambre, numero, type, tarifNuitReservation: idReservation, dateArrivee, dateDepart
Relations :
Client—(1,N)— Effectue —(1,1)—Reservation→ un client peut avoir plusieurs réservations, une réservation appartient à un seul clientChambre—(1,N)— Concerne —(1,1)—Reservation→ une chambre peut être réservée plusieurs fois (à des dates différentes), une réservation porte sur une chambre
MLD :
Client(idClient PK, nom, prenom, email, telephone)
Chambre(idChambre PK, numero, type, tarifNuit)
Reservation(idReservation PK, dateArrivee, dateDepart, idClient FK -> Client, idChambre FK -> Chambre)
Pourquoi Reservation est une entité à part et non une table de liaison ? Parce que la relation Client–Chambre n'est pas directe : une réservation a ses propres attributs (dateArrivee, dateDepart, idReservation) et une identité propre. Elle se traduit par une entité, pas seulement une table de liaison.
Exercice 5 – Réseau social
Un réseau social permet à des utilisateurs de se suivre mutuellement et de publier des posts.
- Un utilisateur peut suivre plusieurs autres utilisateurs (relation d'abonnement).
- Un utilisateur peut publier plusieurs posts.
- Un post peut recevoir des likes d'autres utilisateurs.
- On veut savoir qui suit qui et depuis quand.
- Identifiez les entités et relations (attention : la relation "suit" est réflexive).
- Proposez le MLD.
Correction
Entités :
Utilisateur: idUser, pseudo, email, dateInscriptionPost: idPost, contenu, datePublication
Relations :
- Suit :
Utilisateur—(0,N)— suit —(0,N)—Utilisateur→ relation réflexive N-M (un utilisateur peut en suivre plusieurs, et être suivi par plusieurs) - Publie :
Utilisateur—(1,N)— publie —(1,1)—Post→ relation 1-N - Like :
Utilisateur—(0,N)— like —(0,N)—Post→ relation N-M
MLD :
Utilisateur(idUser PK, pseudo, email, dateInscription)
Post(idPost PK, contenu, datePublication, idUser FK -> Utilisateur)
Abonnement(idSuiveur PK FK -> Utilisateur, idSuivi PK FK -> Utilisateur, dateAbonnement)
LikePost(idUser PK FK -> Utilisateur, idPost PK FK -> Post)
Point clé – relation réflexive : La table Abonnement référence deux fois Utilisateur avec deux noms différents (idSuiveur = celui qui suit, idSuivi = celui qui est suivi). Les deux colonnes pointent vers la même table mais ont des rôles distincts.
Partie 2 – SQL : Création, insertion, interrogation
Ces exercices s'appuient sur le schéma suivant, créé en exercice 6.
Schéma de référence :
Etudiant(idEtudiant PK, nom, prenom, dateNaissance)
Cours(idCours PK, intitule, volumeHoraire)
Inscription(idEtudiant PK FK, idCours PK FK, note)
Données de test :
Etudiant : Durand Claire (id=1), Martin Lucas (id=2), Lefevre Sophie (id=3), Bernard Tom (id=4)
Cours : Mathematiques (id=1, 30h), Physique (id=2, 24h), Informatique (id=3, 36h)
Inscription : (1,1,14.5) (1,3,16.0) (2,1,11.0) (2,2,9.5) (3,2,13.0) (4,3,12.0)
Exercice 6 – Mise en place du schéma
Créez les tables, insérez des données de test, puis affichez les étudiants inscrits au cours "Mathématiques".
Correction
-- Création des tables
CREATE TABLE Etudiant (
idEtudiant INT PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(50) NOT NULL,
prenom VARCHAR(50) NOT NULL,
dateNaissance DATE
);
CREATE TABLE Cours (
idCours INT PRIMARY KEY AUTO_INCREMENT,
intitule VARCHAR(100) NOT NULL,
volumeHoraire INT CHECK (volumeHoraire > 0)
);
CREATE TABLE Inscription (
idEtudiant INT,
idCours INT,
note DECIMAL(4,2),
PRIMARY KEY (idEtudiant, idCours),
FOREIGN KEY (idEtudiant) REFERENCES Etudiant(idEtudiant) ON DELETE CASCADE,
FOREIGN KEY (idCours) REFERENCES Cours(idCours) ON DELETE RESTRICT
);
-- Insertion des données
INSERT INTO Etudiant (nom, prenom, dateNaissance) VALUES
('Durand', 'Claire', '2001-03-25'),
('Martin', 'Lucas', '2000-07-14'),
('Lefevre', 'Sophie', '2002-11-02'),
('Bernard', 'Tom', '2001-09-10');
INSERT INTO Cours (intitule, volumeHoraire) VALUES
('Mathematiques', 30),
('Physique', 24),
('Informatique', 36);
INSERT INTO Inscription VALUES
(1, 1, 14.5),
(1, 3, 16.0),
(2, 1, 11.0),
(2, 2, 9.5),
(3, 2, 13.0),
(4, 3, 12.0);
-- Etudiants inscrits à "Mathematiques"
SELECT e.nom, e.prenom
FROM Etudiant e
JOIN Inscription i ON e.idEtudiant = i.idEtudiant
JOIN Cours c ON i.idCours = c.idCours
WHERE c.intitule = 'Mathematiques';
Résultat attendu :
| nom | prenom |
|---|---|
| Durand | Claire |
| Martin | Lucas |
Explication : On crée les tables avec leurs contraintes (ON DELETE CASCADE supprime les inscriptions si un étudiant est supprimé). Deux jointures successives permettent de relier Etudiant → Inscription → Cours, puis on filtre sur l'intitulé.
Exercice 7 – Filtrage sur les notes
Affichez le nom, le prénom et la note des étudiants ayant obtenu une note supérieure ou égale à 12, triés par note décroissante.
Correction
SELECT e.nom, e.prenom, i.note
FROM Etudiant e
JOIN Inscription i ON e.idEtudiant = i.idEtudiant
WHERE i.note >= 12
ORDER BY i.note DESC;
Résultat attendu :
| nom | prenom | note |
|---|---|---|
| Durand | Claire | 16.00 |
| Durand | Claire | 14.50 |
| Lefevre | Sophie | 13.00 |
| Bernard | Tom | 12.00 |
Explication : Le WHERE filtre les lignes de la table Inscription avant le tri. Le ORDER BY ... DESC place les meilleures notes en premier.
Exercice 8 – Comptage par cours
Affichez le nombre d'étudiants inscrits par cours, avec l'intitulé du cours, trié par nombre d'inscrits décroissant.
Correction
SELECT c.intitule, COUNT(*) AS nbEtudiants
FROM Cours c
JOIN Inscription i ON c.idCours = i.idCours
GROUP BY c.intitule
ORDER BY nbEtudiants DESC;
Résultat attendu :
| intitule | nbEtudiants |
|---|---|
| Mathematiques | 2 |
| Physique | 2 |
| Informatique | 2 |
Explication : Le GROUP BY c.intitule regroupe les inscriptions par cours. COUNT(*) compte les lignes de chaque groupe. Sans JOIN, on n'aurait pas accès à intitule.
Exercice 9 – Agrégats et filtrage de groupes
Affichez l'intitulé et la moyenne des notes de chaque cours, mais uniquement pour les cours ayant au moins 2 inscrits et une moyenne supérieure à 11.
Correction
SELECT c.intitule,
COUNT(*) AS nbInscrits,
ROUND(AVG(i.note), 2) AS moyenneNotes
FROM Cours c
JOIN Inscription i ON c.idCours = i.idCours
GROUP BY c.intitule
HAVING COUNT(*) >= 2
AND AVG(i.note) > 11
ORDER BY moyenneNotes DESC;
Résultat attendu :
| intitule | nbInscrits | moyenneNotes |
|---|---|---|
| Informatique | 2 | 14.00 |
| Mathematiques | 2 | 12.75 |
Explication :
WHEREne peut pas filtrer surAVG()car les agrégats ne sont pas encore calculés à ce stade.HAVINGs'applique après leGROUP BYet peut utiliser des fonctions d'agrégation.- Physique est exclu : sa moyenne est
(9.5 + 13.0) / 2 = 11.25, soit inférieure à 12 mais supérieure à 11 — vérifiez avec vos données exactes.
Exercice 10 – Cours sans inscrits
Affichez les cours auxquels aucun étudiant n'est inscrit.
Correction
Solution 1 — avec NOT IN :
SELECT intitule
FROM Cours
WHERE idCours NOT IN (
SELECT DISTINCT idCours FROM Inscription
);
Solution 2 — avec LEFT JOIN (recommandée) :
SELECT c.intitule
FROM Cours c
LEFT JOIN Inscription i ON c.idCours = i.idCours
WHERE i.idCours IS NULL;
Explication : La LEFT JOIN conserve tous les cours, même ceux sans inscription. Pour ces derniers, i.idCours vaut NULL. Filtrer sur IS NULL isole les cours sans inscrits.
Cette approche est préférable à NOT IN car elle gère correctement les NULL et est généralement plus performante sur de grandes tables.
Exercice 11 – UPDATE et DELETE
À partir du schéma de l'exercice 6 :
- L'étudiant "Lefevre Sophie" a changé de nom — elle s'appelle désormais "Lefebvre". Mettez à jour la table.
- L'étudiant "Bernard Tom" se désinscrit de la plateforme. Supprimez-le ainsi que toutes ses inscriptions.
- Pourquoi n'est-il pas nécessaire de supprimer manuellement ses inscriptions dans ce cas ?
Correction
-- 1. Mise à jour du nom
UPDATE Etudiant
SET nom = 'Lefebvre'
WHERE nom = 'Lefevre' AND prenom = 'Sophie';
-- 2. Suppression de l'étudiant
DELETE FROM Etudiant
WHERE nom = 'Bernard' AND prenom = 'Tom';
-- 3. Les inscriptions de Bernard sont supprimées automatiquement
-- grâce à la contrainte ON DELETE CASCADE définie lors de la création :
-- FOREIGN KEY (idEtudiant) REFERENCES Etudiant(idEtudiant) ON DELETE CASCADE
Points importants :
- Le
WHEREdansUPDATEest indispensable — sans lui, tous les étudiants seraient renommés. ON DELETE CASCADEsur la clé étrangèreidEtudiantdansInscriptiongarantit que les inscriptions orphelines sont supprimées automatiquement lorsqu'un étudiant est supprimé.- Préférez toujours filtrer sur la clé primaire (
idEtudiant) en production pour éviter les ambiguïtés sur les noms en double.
Exercice 12 – ALTER TABLE
Après avoir créé le schéma de l'exercice 6 :
- Ajoutez une colonne
mentionde typeVARCHAR(20)à la tableInscription, avec la valeur par défaut'Passable'. - Ajoutez une contrainte
UNIQUEsur la colonneemailde la tableEtudiant(en supposant qu'on ait ajouté cette colonne). - Supprimez la colonne
dateNaissancede la tableEtudiant.
Correction
-- 1. Ajout de la colonne mention dans Inscription
ALTER TABLE Inscription
ADD COLUMN mention VARCHAR(20) DEFAULT 'Passable';
-- 2. Ajout de la contrainte UNIQUE sur email
-- (on suppose que la colonne email existe déjà)
ALTER TABLE Etudiant
ADD CONSTRAINT uq_email UNIQUE (email);
-- 3. Suppression de la colonne dateNaissance
ALTER TABLE Etudiant
DROP COLUMN dateNaissance;
Points importants :
ADD COLUMNajoute une colonne avec ses contraintes (iciDEFAULT).ADD CONSTRAINTnomme explicitement la contrainte pour pouvoir la supprimer plus tard avecDROP CONSTRAINT uq_email.DROP COLUMNest irréversible — les données de cette colonne sont perdues. Toujours sauvegarder avant.
Exercice 13 – LIKE, BETWEEN et IS NULL
À partir du schéma de référence :
- Affichez les étudiants dont le nom commence par "D" ou "M".
- Affichez les cours dont le volume horaire est compris entre 25 et 40 heures.
- Affichez les inscriptions dont la note est
NULL(non encore saisie).
Correction
-- 1. Noms commençant par D ou M
SELECT nom, prenom
FROM Etudiant
WHERE nom LIKE 'D%' OR nom LIKE 'M%';
-- Alternative avec REGEXP (MySQL)
-- WHERE nom REGEXP '^[DM]'
-- 2. Volume horaire entre 25 et 40h
SELECT intitule, volumeHoraire
FROM Cours
WHERE volumeHoraire BETWEEN 25 AND 40;
-- 3. Notes non saisies
SELECT idEtudiant, idCours
FROM Inscription
WHERE note IS NULL;
Points importants :
LIKE 'D%': le%remplace n'importe quelle suite de caractères._remplace un seul caractère.BETWEEN 25 AND 40est inclusif : équivalent à>= 25 AND <= 40.note = NULLne fonctionne pas — il faut toujours utiliserIS NULLouIS NOT NULLpour les valeurs nulles.
Partie 3 – Algèbre relationnelle
Exprimez les requêtes suivantes en utilisant les opérateurs σ (sélection), π (projection), ⨝ (jointure), ∪ (union), − (différence), ÷ (division).
Exercice 14 – Projection + sélection
Affichez les noms et prénoms des étudiants inscrits au cours "Mathematiques".
Correction
π_{nom, prenom}(
σ_{intitule = 'Mathematiques'}(
Etudiant ⨝_{Etudiant.idEtudiant = Inscription.idEtudiant}
Inscription ⨝_{Inscription.idCours = Cours.idCours}
Cours
)
)
Lecture de l'expression (intérieur → extérieur) :
- Les trois tables sont jointes sur leurs clés communes.
- On filtre les lignes où
intitule = 'Mathematiques'. - On ne garde que les colonnes
nometprenom.
Traduction SQL :
SELECT nom, prenom
FROM Etudiant
JOIN Inscription USING (idEtudiant)
JOIN Cours USING (idCours)
WHERE intitule = 'Mathematiques';
Exercice 15 – Sélection sur une table liée
Affichez les intitulés des cours suivis par l'étudiant dont le nom est "Durand".
Correction
π_{intitule}(
σ_{nom = 'Durand'}(
Etudiant ⨝ Inscription ⨝ Cours
)
)
Note : Si plusieurs étudiants ont le nom "Durand", tous leurs cours seront affichés. En pratique, on préférerait filtrer sur idEtudiant pour lever l'ambiguïté.
Traduction SQL :
SELECT DISTINCT c.intitule
FROM Etudiant e
JOIN Inscription i ON e.idEtudiant = i.idEtudiant
JOIN Cours c ON i.idCours = c.idCours
WHERE e.nom = 'Durand';
Exercice 16 – Sélection avec condition numérique
Affichez les noms et prénoms des étudiants ayant obtenu une note strictement inférieure à 10.
Correction
π_{nom, prenom}(
σ_{note < 10}(
Etudiant ⨝_{Etudiant.idEtudiant = Inscription.idEtudiant} Inscription
)
)
Ici, il n'est pas nécessaire de joindre Cours car on ne filtre pas sur un attribut du cours.
Traduction SQL :
SELECT e.nom, e.prenom
FROM Etudiant e
JOIN Inscription i ON e.idEtudiant = i.idEtudiant
WHERE i.note < 10;
Exercice 17 – Division relationnelle
Affichez les noms et prénoms des étudiants inscrits à tous les cours disponibles.
Correction
π_{nom, prenom}(
Etudiant ⨝ (
π_{idEtudiant, idCours}(Inscription) ÷ π_{idCours}(Cours)
)
)
Explication de la division :
π_{idEtudiant, idCours}(Inscription) ÷ π_{idCours}(Cours) retourne les idEtudiant pour lesquels il existe une inscription pour chaque idCours de Cours.
Traduction SQL (avec NOT EXISTS) :
SELECT e.nom, e.prenom
FROM Etudiant e
WHERE NOT EXISTS (
SELECT idCours FROM Cours
WHERE idCours NOT IN (
SELECT idCours FROM Inscription
WHERE idEtudiant = e.idEtudiant
)
);
On cherche les étudiants pour lesquels il n'existe pas de cours auquel ils ne seraient pas inscrits.
Exercice 18 – Union
Affichez les identifiants des étudiants inscrits soit au cours "Mathematiques" soit au cours "Physique" (ou les deux).
Correction
π_{idEtudiant}(σ_{intitule = 'Mathematiques'}(Inscription ⨝ Cours))
∪
π_{idEtudiant}(σ_{intitule = 'Physique'}(Inscription ⨝ Cours))
Propriété de l'union : L'union supprime les doublons. Un étudiant inscrit aux deux cours n'apparaîtra qu'une fois dans le résultat.
Traduction SQL :
SELECT idEtudiant FROM Inscription
JOIN Cours USING (idCours)
WHERE intitule = 'Mathematiques'
UNION
SELECT idEtudiant FROM Inscription
JOIN Cours USING (idCours)
WHERE intitule = 'Physique';
Alternative avec IN :
SELECT DISTINCT i.idEtudiant
FROM Inscription i
JOIN Cours c ON i.idCours = c.idCours
WHERE c.intitule IN ('Mathematiques', 'Physique');
Exercice 19 – Différence
Affichez les identifiants des étudiants inscrits au cours "Mathematiques" mais pas au cours "Physique".
Correction
π_{idEtudiant}(σ_{intitule = 'Mathematiques'}(Inscription ⨝ Cours))
−
π_{idEtudiant}(σ_{intitule = 'Physique'}(Inscription ⨝ Cours))
Lecture : On prend l'ensemble des étudiants en Maths, et on lui soustrait l'ensemble des étudiants en Physique. Seuls ceux qui ne sont que en Maths restent.
Traduction SQL :
SELECT idEtudiant FROM Inscription
JOIN Cours USING (idCours)
WHERE intitule = 'Mathematiques'
EXCEPT
SELECT idEtudiant FROM Inscription
JOIN Cours USING (idCours)
WHERE intitule = 'Physique';
Alternative avec NOT IN (compatible MySQL qui ne supporte pas EXCEPT) :
SELECT DISTINCT i.idEtudiant
FROM Inscription i
JOIN Cours c ON i.idCours = c.idCours
WHERE c.intitule = 'Mathematiques'
AND i.idEtudiant NOT IN (
SELECT i2.idEtudiant
FROM Inscription i2
JOIN Cours c2 ON i2.idCours = c2.idCours
WHERE c2.intitule = 'Physique'
);
Partie 4 – Arbres algébriques
Représentez chaque requête SQL sous forme d'arbre algébrique. Rappel : les feuilles sont les tables, les nœuds internes sont les opérations, et la racine est le résultat final. On lit l'arbre de bas en haut.
Exercice 20 – Arbre simple (sélection + projection)
Représentez la requête suivante sous forme d'arbre :
SELECT nom, prenom FROM Etudiant WHERE dateNaissance < '2002-01-01';
Correction
π_{nom, prenom}
|
σ_{dateNaissance < '2002-01-01'}
|
Etudiant
Lecture (bas → haut) :
Etudiant— on part de la table entière.σ_{dateNaissance < '2002-01-01'}— on filtre les étudiants nés avant 2002.π_{nom, prenom}— on ne garde que les colonnes nom et prénom.
Pourquoi ce sens ? La sélection avant la projection est obligatoire ici : si on projetait d'abord, la colonne dateNaissance disparaîtrait et la sélection serait impossible.
Exercice 21 – Arbre avec LIKE
Représentez en arbre :
SELECT nom FROM Etudiant WHERE nom LIKE 'M%';
Correction
π_{nom}
|
σ_{nom LIKE 'M%'}
|
Etudiant
Note : LIKE 'M%' en algèbre relationnelle s'écrit avec la même notation de sélection. La condition reste une condition sur les attributs de la relation.
Exercice 22 – Arbre avec BETWEEN
Représentez en arbre :
SELECT intitule FROM Cours WHERE volumeHoraire BETWEEN 20 AND 40;
Correction
π_{intitule}
|
σ_{volumeHoraire >= 20 ∧ volumeHoraire <= 40}
|
Cours
Équivalence : BETWEEN 20 AND 40 est équivalent à >= 20 AND <= 40. En algèbre relationnelle, on utilise le symbole ∧ (et logique) pour combiner les conditions dans σ.
Exercice 23 – Arbre avec jointure et optimisation
Représentez en arbre la requête suivante, puis proposez une version optimisée :
SELECT e.nom, c.intitule
FROM Etudiant e
JOIN Inscription i ON e.idEtudiant = i.idEtudiant
JOIN Cours c ON i.idCours = c.idCours
WHERE c.intitule = 'Informatique';
Correction
Arbre non optimisé :
π_{nom, intitule}
|
σ_{intitule = 'Informatique'}
|
⨝ (Inscription.idCours = Cours.idCours)
/ \
⨝ (Etudiant.idEtudiant = Inscription.idEtudiant) Cours
/ \
Etudiant Inscription
Arbre optimisé (sélection poussée vers les feuilles) :
π_{nom, intitule}
|
⨝ (Inscription.idCours = Cours.idCours)
/ \
⨝ (Etudiant.idEtudiant = Inscription.idEtudiant) σ_{intitule = 'Informatique'}
/ \ |
Etudiant Inscription Cours
Pourquoi l'optimisation est meilleure ? En filtrant Cours sur intitule = 'Informatique' avant la jointure, on réduit la taille de la table Cours à 1 ligne. Les jointures suivantes portent sur un ensemble beaucoup plus petit, ce qui accélère l'exécution.
Exercice 24 – Arbre avec agrégation
Représentez en arbre la requête suivante :
SELECT c.intitule, COUNT(*) AS nbInscrits
FROM Cours c
JOIN Inscription i ON c.idCours = i.idCours
GROUP BY c.intitule
HAVING COUNT(*) >= 2;
Correction
π_{intitule, nbInscrits}
|
σ_{COUNT(*) >= 2} <-- HAVING
|
γ_{intitule; COUNT(*)} <-- GROUP BY + agrégat
|
⨝ (Cours.idCours = Inscription.idCours)
/ \
Cours Inscription
Notation : On utilise γ (gamma) pour représenter l'opération d'agrégation (GROUP BY + fonction d'agrégation). La syntaxe est γ_{colonnes de groupe ; agrégats}.
Ordre de lecture (bas → haut) :
- Jointure
Cours×Inscriptionsur les clés communes. - Agrégation par
intituleavec calcul duCOUNT(*). - Filtrage des groupes (
HAVING) : on ne garde que ceux avec au moins 2 inscrits. - Projection sur les colonnes utiles.
Partie 5 – Requêtes complexes et imbriquées
Exercice 25 – Jointures multiples
Affichez les noms et prénoms des employés travaillant sur un projet d'un client situé à Paris.
Schéma :
Client(idClient PK, nom, ville)
Projet(idProjet PK, nomProjet, idClient FK)
Employe(idEmploye PK, nom, prenom)
TravailleSur(idEmploye PK FK, idProjet PK FK)
Correction
SELECT DISTINCT e.nom, e.prenom
FROM Employe e
JOIN TravailleSur ts ON e.idEmploye = ts.idEmploye
JOIN Projet p ON ts.idProjet = p.idProjet
JOIN Client c ON p.idClient = c.idClient
WHERE c.ville = 'Paris';
Explication : On remonte la chaîne de jointures depuis l'employé jusqu'au client via la table de liaison TravailleSur. Le DISTINCT évite les doublons : un employé peut travailler sur plusieurs projets parisiens mais ne doit apparaître qu'une fois.
Alternative avec sous-requête :
SELECT nom, prenom
FROM Employe
WHERE idEmploye IN (
SELECT ts.idEmploye
FROM TravailleSur ts
JOIN Projet p ON ts.idProjet = p.idProjet
WHERE p.idClient IN (
SELECT idClient FROM Client WHERE ville = 'Paris'
)
);
Exercice 26 – Sous-requêtes imbriquées
Affichez les intitulés des cours suivis par au moins un étudiant né après le 31 décembre 2001.
Correction
SELECT DISTINCT c.intitule
FROM Cours c
WHERE c.idCours IN (
SELECT i.idCours
FROM Inscription i
WHERE i.idEtudiant IN (
SELECT idEtudiant
FROM Etudiant
WHERE dateNaissance > '2001-12-31'
)
);
Lecture de l'intérieur vers l'extérieur :
- La sous-requête la plus interne sélectionne les
idEtudiantdes étudiants nés après 2001. - La sous-requête intermédiaire trouve les
idCoursauxquels ces étudiants sont inscrits. - La requête externe récupère les intitulés de ces cours.
Attention : Cette requête retourne les cours qui ont AU MOINS UN inscrit né après 2001. Pour retourner les cours suivis UNIQUEMENT par des étudiants nés après 2001 (sans aucun étudiant né avant), la requête serait plus complexe.
Exercice 27 – Double négation (tous les cours)
Affichez les noms et prénoms des étudiants inscrits à tous les cours disponibles.
Correction
Solution avec NOT EXISTS (portable, gère les NULL) :
SELECT e.nom, e.prenom
FROM Etudiant e
WHERE NOT EXISTS (
SELECT c.idCours
FROM Cours c
WHERE c.idCours NOT IN (
SELECT i.idCours
FROM Inscription i
WHERE i.idEtudiant = e.idEtudiant
)
);
Solution avec EXCEPT (PostgreSQL, SQL Server) :
SELECT nom, prenom
FROM Etudiant e
WHERE NOT EXISTS (
SELECT idCours FROM Cours
EXCEPT
SELECT idCours FROM Inscription i WHERE i.idEtudiant = e.idEtudiant
);
Logique de la double négation :
"Un étudiant est inscrit à tous les cours" équivaut à : "Il n'existe aucun cours auquel cet étudiant n'est pas inscrit"
Cette formulation en double négation (NOT EXISTS + cours NOT IN inscriptions) est la traduction directe de la division relationnelle en SQL.
Exercice 28 – Analyse statistique
À partir du schéma Etudiant/Cours/Inscription, répondez aux questions suivantes en une seule requête pour chacune :
- Quelle est la note maximale par cours, avec le nom du cours ?
- Quels sont les cours dont la moyenne est inférieure à la moyenne générale de toutes les notes ?
- Quel étudiant a la meilleure note parmi tous les inscrits ? (Affichez son nom, prénom et note.)
Correction
1. Note maximale par cours :
SELECT c.intitule, MAX(i.note) AS note_max
FROM Cours c
JOIN Inscription i ON c.idCours = i.idCours
GROUP BY c.intitule
ORDER BY note_max DESC;
2. Cours dont la moyenne est inférieure à la moyenne générale :
SELECT c.intitule, ROUND(AVG(i.note), 2) AS moyenne_cours
FROM Cours c
JOIN Inscription i ON c.idCours = i.idCours
GROUP BY c.intitule
HAVING AVG(i.note) < (
SELECT AVG(note) FROM Inscription
)
ORDER BY moyenne_cours;
La sous-requête (SELECT AVG(note) FROM Inscription) calcule la moyenne sur toutes les inscriptions. Le HAVING compare chaque groupe à cette valeur globale.
3. Étudiant avec la meilleure note :
SELECT e.nom, e.prenom, i.note
FROM Etudiant e
JOIN Inscription i ON e.idEtudiant = i.idEtudiant
WHERE i.note = (
SELECT MAX(note) FROM Inscription
);
Si plusieurs étudiants ont la même note maximale, tous seront affichés.
Exercice 29 – Sous-requête corrélée
Pour chaque cours, affichez l'étudiant ayant obtenu la meilleure note dans ce cours (nom, prénom, intitulé du cours, note).
Correction
SELECT e.nom, e.prenom, c.intitule, i.note
FROM Etudiant e
JOIN Inscription i ON e.idEtudiant = i.idEtudiant
JOIN Cours c ON i.idCours = c.idCours
WHERE i.note = (
SELECT MAX(i2.note)
FROM Inscription i2
WHERE i2.idCours = i.idCours -- corrélation : on compare au même cours
);
Pourquoi c'est une sous-requête corrélée ? La sous-requête SELECT MAX(i2.note) ... WHERE i2.idCours = i.idCours fait référence à i.idCours de la requête externe. Elle est réévaluée pour chaque ligne de la requête principale. C'est plus lent qu'une sous-requête simple, mais nécessaire ici.
Résultat attendu :
| nom | prenom | intitule | note |
|---|---|---|---|
| Durand | Claire | Informatique | 16.00 |
| Durand | Claire | Mathematiques | 14.50 |
| Lefevre | Sophie | Physique | 13.00 |
Alternative avec WITH (CTE) pour la lisibilité :
WITH MaxParCours AS (
SELECT idCours, MAX(note) AS maxNote
FROM Inscription
GROUP BY idCours
)
SELECT e.nom, e.prenom, c.intitule, i.note
FROM Etudiant e
JOIN Inscription i ON e.idEtudiant = i.idEtudiant
JOIN Cours c ON i.idCours = c.idCours
JOIN MaxParCours m ON i.idCours = m.idCours AND i.note = m.maxNote;
Exercice 30 – EXISTS vs IN
À partir du schéma de référence, affichez les étudiants qui ont au moins une inscription avec une note inférieure à 10. Comparez les approches avec IN, EXISTS et jointure.
Correction
Solution 1 — avec IN :
SELECT nom, prenom
FROM Etudiant
WHERE idEtudiant IN (
SELECT idEtudiant FROM Inscription WHERE note < 10
);
Solution 2 — avec EXISTS :
SELECT e.nom, e.prenom
FROM Etudiant e
WHERE EXISTS (
SELECT 1
FROM Inscription i
WHERE i.idEtudiant = e.idEtudiant
AND i.note < 10
);
Solution 3 — avec JOIN + DISTINCT :
SELECT DISTINCT e.nom, e.prenom
FROM Etudiant e
JOIN Inscription i ON e.idEtudiant = i.idEtudiant
WHERE i.note < 10;
Comparaison des trois approches :
| Approche | Avantages | Inconvénients |
|---|---|---|
IN | Lisible, intuitif | Peut être lent si la sous-requête retourne beaucoup de lignes ; problèmes avec NULL |
EXISTS | Optimisé (s'arrête au 1er résultat) | Syntaxe plus verbeuse |
JOIN | Performant, flexible | Nécessite DISTINCT pour éviter les doublons |
Règle pratique : Préférez EXISTS quand vous voulez juste vérifier l'existence, JOIN quand vous avez besoin de colonnes de la table jointe.
