SELECT * FROM Film WHERE titre IN SELECT titre FROM Role On va donc parcourir les films, et pour chacun, on affichera son titre si et seulement si ce titre apparaît dans au moins une des
Trang 1Notez la syntaxe table.* qui représente tous les attributs de la table table On
peut considérer que la jointure est moins naturelle, et que la requête imbriquée est plus proche de la manière dont la recherche est conçue : on ne s’intéresse pas directement aux films de Clint Eastwood, mais seulement aux rôles Il n’en reste pas
moins que toutes deux donnent le même résultat Autre exemple : donner les films
pour lesquels on connaît au moins un des rôles On peut utiliser une requête imbriquée.
SELECT * FROM Film
WHERE titre IN (SELECT titre FROM Role)
On va donc parcourir les films, et pour chacun, on affichera son titre si et
seulement si ce titre apparaît dans au moins une des lignes de la table Role On peut
là aussi utiliser une jointure
SELECT DISTINCT Film.*
FROM Film, Role
WHERE Film.titre = Role.titre
Il y a une différence un peu subtile : avec la jointure on affichera autant de fois un titre qu’il y a de rôles Le mot-clé DISTINCT permet de se ramener à un résultat équivalent à celui de la requête imbriquée
On peut exprimer la condition d’appartenance sur des lignes comprenant plu-sieurs attributs, comme le montre la requête suivante : on recherche tous les films du
même genre qu’Impitoyable, et sont parus la même année.
SELECT *
FROM Film
WHERE (annee, genre) = (SELECT annee, genre
FROM Film f WHERE titre=’Impitoyable’)
Le nombre et le type des attributs (ici deux attributs) doit correspondre exacte-ment dans la requête principale et la requête imbriquée
Bien entendu la requête ci-dessus s’exprime avec une jointure Ce n’est pas le cas
en revanche de celle ci-dessous, qui sélectionne l’artiste avec la date de naissance la plus ancienne
SELECT prenom, nom
FROM Artiste
WHERE annee_naissance <= ALL (SELECT annee_naissance FROM Artiste
WHERE annee_naissance IS NOT NULL) AND annee_naissance IS NOT NULL;
+ -+ -+
| prenom | nom |
+ -+ -+
| Akira | Kurosawa |
+ -+ -+
Trang 2Le ALL exprime une comparaison qui vaut pour toutes les lignes ramenées par
la requête imbriquée Attention aux valeurs à NULL dans ce genre de situation : toute comparaison avec une de ces valeurs renvoie UNKNOWN et cela peut entraîner l’échec du ALL Il n’existe pas d’expression avec jointure qui puisse exprimer ce genre
de condition En revanche, le ALL peut s’exprimer avec la négation, selon la règle d’équivalence que quand quelque chose est toujours vrai, il n’est jamais faux ! Nous verrons un exemple plus loin
10.4.2 Requêtes corrélées
Les exemples de requêtes imbriquées donnés précédemment pouvaient être évalués indépendamment de la requête principale, ce qui permet au système (s’il le juge nécessaire) d’exécuter la requête en deux phases La clause EXISTS fournit encore
un nouveau moyen d’exprimer les requêtes vues précédemment, en basant la sous-requête sur une ou plusieurs valeurs issues de la sous-requête principale On parle alors de
requêtes corrélées.
Reprenons une dernière fois la requête donnant les rôles des films de Clint Eastwood Elle s’exprime avec EXISTS de la manière suivante :
SELECT * FROM Role
WHERE EXISTS (SELECT titre FROM Film, Artiste
WHERE Film.id_realisateur=Artiste.id AND Film.titre=Role.titre
AND nom=’Eastwood’) + -+ -+ -+
+ -+ -+ -+
| Impitoyable | 20 | William Munny |
| Impitoyable | 21 | Little Bill Dagget |
| Les pleins pouvoirs | 21 | Le pr´ esident |
+ -+ -+ -+
On obtient donc une nouvelle technique d’expression, qui permet d’aborder le
critère de recherche sous une troisième perspective : on conserve un rôle si, pour ce
rôle, le film a été dirigé par Clint Eastwood Notez la jointure entre la table Role
référencée dans la requête principale et la table Film de la requête imbriquée C’est
cette comparaison « à distance » entre deux tables référencées par des clauses FROM
différentes qui explique le terme de corrélation.
Supposons que l’on veuille trouver tous les metteurs en scène ayant dirigé Gene Hackman La requête peut s’exprimer avec EXIST de la manière suivante :
SELECT * FROM Artiste a1
WHERE EXISTS (SELECT *
FROM Film f, Role r, Artiste a2 WHERE f.titre = r.titre
AND r.id_acteur = a2.id
Trang 3AND nom = ’Hackman’
AND f.id_realisateur = a1 id) + + -+ -+ -+
| id | nom | prenom | annee_naissance |
+ + -+ -+ -+
| 20 | Eastwood | Clint | 1930 |
+ + -+ -+ -+
En langage naturel, le raisonnement est le suivant : on prend tous les artistes (requête principale) tels que, parmi les films qu’ils ont dirigés (requête secondaire),
on trouve un rôle joué par Gene Hackman
REMARQUE –dans une sous-requête associée à la clause EXISTS, peu importent les attributs duSELECTpuisque la condition se résume à : cette requête ramène-t-elle au moins une ligne ou non ? On peut donc systématiquement utiliserSELECT *.
La requête équivalente avec IN s’appuie sur un raisonnement légèrement modifié :
on prend tous les artistes dont l’identifiant fait partie de l’ensemble des identifiants des metteurs en scène d’un film avec Gene Hackman
SELECT * FROM Artiste a1
WHERE id IN (SELECT id_realisateur
FROM Film f, Role r, Artiste a2 WHERE f.titre = r.titre
AND r.id_acteur = a2.id AND nom = ’Hackman’)
La solution classique d’une jointure « à plat » reste valable, en utilisant DISTINCT pour éliminer les doublons :
SELECT DISTINCT a1.*
FROM Artiste a1, Film f, Role r, Artiste a2
WHERE f.titre = r.titre
AND r.id_acteur = a2.id
AND a2.nom = ’Hackman’
AND f.id_realisateur = a1 id
Enfin, rien n’empêche d’utiliser plusieurs niveaux d’imbrication !
SELECT * FROM Artiste a1
WHERE EXISTS
(SELECT * FROM Film f
WHERE f.id_realisateur = a1 id AND EXISTS (SELECT * FROM Role r
WHERE f.titre = r.titre AND EXISTS (SELECT * FROM Artiste a2
WHERE r.id_acteur = a2.id AND nom = ’Hackman’)))
Trang 4Je laisse le lecteur déchiffrer cette dernière requête (elle fonctionne !) et se convaincre que l’argument de lisibilité des requêtes imbriquées atteint rapidement ses limites De plus ce genre d’expression sera probablement plus difficile à traiter pour le système
En résumé, une jointure entre les tables R et S de la forme :
SELECT R.*
FROM R, S
WHERE R.a = S.b
peut s’écrire de manière équivalente avec une requête imbriquée :
SELECT *
FROM R
WHERE R.a IN (SELECT S.b FROM S)
ou bien encore sous forme de requête corrélée :
SELECT *
FROM R
WHERE EXISTS (SELECT S.b FROM S WHERE S.b = R.a)
Le choix de la forme est matière de gỏt ou de lisibilité, ces deux critères relevant
de considérations essentiellement subjectives
10.4.3 Requêtes avec négation
Les requêtes imbriquées sont en revanche irremplaçables pour exprimer des négations.
On utilise alors NOT IN ou (de manière équivalente) NOT EXISTS Voici un premier
exemple avec la requête : donner les films pour lesquels on ne connaỵt aucun rơle.
SELECT * FROM Film
WHERE titre NOT IN (SELECT titre FROM Role);
On obtient le résultat suivant :
+ -+ -+ -+ -+
| titre | annee | id_realisateur | genre |
+ -+ -+ -+ -+
| Kagemusha | 1980 | 68 | Drame |
+ -+ -+ -+ -+
La négation est aussi un moyen d’exprimer des requêtes courantes comme celle recherchant le (ou les) films le(s) plus ancien(s) de la base En SQL, on utilisera typiquement une sous-requête pour prendre l’année minimale parmi les années de production des films, laquelle servira à sélectionner un ou plusieurs films
Trang 5SELECT *
FROM Film
WHERE annee = (SELECT MIN(annee) FROM Film)
Il existe en fait beaucoup de manières d’exprimer la même chose avec un SQL
« complet » Tout d’abord cette requête peut en fait s’exprimer sans la fonction MIN(),
avec la négation : si f est le film le plus ancien, c’est en effet qu’il n’existe pas de film strictement plus ancien que f On utilise alors habituellement une requête dite
« corrélée » dans laquelle la sous-requête est basée sur une ou plusieurs valeurs issues des tables de la requête principale
SELECT *
FROM Film f1
WHERE NOT EXISTS (SELECT annee FROM Film f2
WHERE f1.annee > f2.annee)
Le f1.annee dans la requête imbriquée appartient à la table référencée dans le FROM de la requête principale Autre manière d’exprimer la même chose : si un film est le plus ancien, tous les autres sont plus récents On peut utiliser le mot-clé ALL, qui
indique que la comparaison est vraie avec tous les éléments de l’ensemble constitué
par la sous-requête
SELECT *
FROM Film
WHERE annee <= ALL (SELECT annee FROM Film)
On préfère en général NOT EXISTS à ALL, mais les deux sont équivalents, puisque
quand une propriété est vraie pour tous les éléments d’un ensemble, il n’existe pas
d’élément pour lequel elle est fausse Dernier exemple de négation : quels artistes ne sont pas metteur en scène ? Les deux formulations ci-dessous sont équivalentes, l’une s’appuyant sur NOT IN et l’autre sur NOT EXISTS
SELECT *
FROM Artiste
WHERE id NOT IN (SELECT id_realisateur FROM Film)
SELECT *
FROM Artiste
WHERE NOT EXISTS (SELECT * FROM Film WHERE Artiste.id = Film.id_realisateur) Dans les deux cas, on trouve le résultat suivant :
+ + -+ -+ -+
| id | nom | prenom | annee_naissance |
+ + -+ -+ -+
| 21 | Hackman | Gene | 1930 |
| 30 | Dutronc | Jacques | NULL |
+ + -+ -+ -+