7 - Requêtes SQL imbriquées¶
- Une sous-requête est une requête à l’intérieur d’une autre requête (ou d’une autre sous-requête).
- Nous pouvons avoir plusieurs niveaux de sous-requêtes.
- Parfois, nous faisons référence aux sous-requêtes comme des requêtes internes et aux requêtes englobantes comme des requêtes externes.
Base de Données Universitaire¶
Sous-requêtes Non Corrélées¶
- Aussi appelées sous-requêtes simples.
- Les sous-requêtes corrélées seront abordées plus tard.
- Les sous-requêtes non corrélées sont indépendantes de leurs requêtes externes (englobantes).
- Elles peuvent s’exécuter seules.
- Elles ne dépendent pas de quelque chose défini dans les requêtes externes.
Exemples : avec 2 sous-requêtes non corrélées¶
1- Trouver les étudiants et les instructeurs avec une adresse
email @example.com
select name, email
from student
where email like '%@example.com'
union
select name, email
from instructor
where email like '%@example.com';
2- Vérifier s’il y a des étudiants et des instructeurs avec la même adresse email
3- Trouver les cours qui n’ont jamais été offerts
select cid
from course
except
select cid
from offering;
select c.cid
from course c
left join offering o on c.cid = o.cid
where o.oid is null;
4- Trouver les étudiants non inscrits à un cours
5- Trouver les offres de cours dans lesquelles aucun étudiant n’est inscrit
Sous-requêtes Scalaires¶
- Les sous-requêtes scalaires sont le type le plus simple de sous-requêtes.
- Elles renvoient toujours exactement 1 ligne contenant exactement 1 colonne.
- Elles sont souvent (mais pas toujours) obtenues en calculant une fonction d’agrégat.
Exemples¶
1- Trouver le nombre d’étudiants avec une adresse email @example.com
2- Trouver les cours qui ont été offerts plus souvent que le cours DB
- Trouver le nombre de fois que
DBa été offert
- Insérer le résultat précédent dans la clause
HAVINGd’une requête calculant le nombre de fois que chaque cours a été offert. Il pourrait être préférable de commencer avec un nombre fixe (par exemple 3) au lieu d’insérer immédiatement la première requête dans la seconde
select c.cid, c.code
from course c
left join offering o on c.cid = o.cid
group by c.cid, c.code
having count(o.oid) > 3;
select c.cid, c.code
from course c
left join offering o on c.cid = o.cid
group by c.cid, c.code
having count(o.oid) > (select count(o.cid)
from course c
left join offering o on c.cid = o.cid
where c.code = 'DB');
3- Trouver le nombre moyen de fois que chaque cours a été offert. Commencer par trouver le nombre de fois que chaque cours a été offert, puis prendre la moyenne
select c.cid, c.code, count(o.oid) as n_offerings
from course c
left join offering o on c.cid = o.cid
group by c.cid;
-- ne fonctionnera pas
select c.cid, c.code, avg(count(o.oid)) as n_offerings
from course c
left join offering o on c.cid = o.cid
group by c.cid;
select round(avg(n_offerings), 2) as avg_n_offerings
from (select count(o.oid) as n_offerings
from course c
left join offering o on c.cid = o.cid
group by c.cid) as T;
with T as (select count(o.oid) as n_offerings
from course c
left join offering o on c.cid = o.cid
group by c.cid)
select round(avg(n_offerings), 2) as avg_n_offerings
from T;
4- Trouver les cours qui ont été offerts plus souvent que la moyenne (nombre de fois que chaque cours a été offert)
select c.cid, c.code
from course c
left join offering o on c.cid = o.cid
group by c.cid
having count(o.oid) > (select avg(n_offerings) as avg_n_offerings
from (select count(o.oid) as n_offerings
from course c
left join offering o on c.cid = o.cid
group by c.cid) as T);
Format de Requête SQL with¶
- Afin d’exprimer plus facilement des requêtes avec plusieurs niveaux de
sous-requêtes, nous pouvons utiliser le style de
requête
with ... select .... - Nous définissons (en quelque sorte) des tables temporaires avant que la
requête principale
selectne commence. - Ensuite, nous utilisons les tables temporaires dans la requête
principale
selectcomme si elles étaient des tables stockées dans la base de données.
with T2 as (select avg(n_offerings) as avg_n_offerings
from (select c.cid, c.code, count(o.oid) as n_offerings
from course c
left join offering o on c.cid = o.cid
group by c.cid) as T1)
select c.cid, c.code
from course c
left join offering o on c.cid = o.cid
group by c.cid
having count(o.oid) > (select * from T2);
with n_offerings_per_course as (select c.cid,
c.code,
count(o.oid) as n_offerings
from course c
left join offering o on c.cid = o.cid
group by c.cid)
select cid, code
from n_offerings_per_course
where n_offerings > (select avg(n_offerings) as avg_n_offerings
from n_offerings_per_course);
- Ne pas sur-utiliser la syntaxe
with - Par exemple, ne réécrivez pas cette requête
select name, email
from student
where email like '%@example.com'
union
select name, email
from instructor
where email like '%@example.com';
comme
with students_example as (select name, email from student where email like '%@example.com'),
instructor_example as (select name, email from instructor where email like '%@example.com')
select * from students_example
union
select * from instructor_example;
- Bien que cette requête soit techniquement correcte et équivalente à la requête
originale, l’utilisation de
withpour définir 2 tables temporaires est excessive ici et réduit en fait la lisibilité. - Utilisez
withuniquement lorsque les sous-requêtes sont compliquées ou lorsqu’il y a plusieurs niveaux de sous-requêtes. - Nous pouvons également utiliser
with recursivepour écrire des requêtes récursives.
Valeurs NULL en SQL¶
- SQL utilise une logique à 3 valeurs au lieu de la logique booléenne (une logique à 2 valeurs).
- Les 3 valeurs sont
true,falseetnull(ouT,FetNdans le tableau ci-dessous)… - Les 2 premières valeurs ont le sens habituel, tandis que
nullpeut avoir différentes significations :- inconnu
- non applicable
- n’a pas d’importance
- Les opérateurs logiques doivent être mis à jour pour tenir compte des
valeurs
null.
| A | B | NOT A | A OR B | A AND B |
|---|---|---|---|---|
| T | T | F | T | T |
| T | F | F | T | F |
| T | N | F | T | N |
| F | T | T | T | F |
| F | F | T | F | F |
| F | N | T | N | F |
| N | T | N | T | N |
| N | F | N | N | F |
| N | N | N | N | N |
Sous-requêtes Non-Scalaires¶
- Si une (sous-)requête renvoie plus d’une ligne et/ou plus d’une colonne, alors ce n’est pas une sous-requête scalaire.
- Tenter d’utiliser les opérateurs de comparaison ou arithmétiques normaux avec des sous-requêtes non-scalaires échouera s’il y a plus d’une ligne.
- Certains SGBD, comme PostgreSQL, permettent à certains opérateurs de fonctionner avec des sous-requêtes donnant exactement 1 ligne mais plusieurs colonnes.
- PostgreSQL est un ORDBMS, donc il est plus flexible avec les types de données.
- Il verra la seule ligne avec plusieurs colonnes comme un seul objet avec plusieurs champs.
- En général, nous devons utiliser des opérateurs spéciaux pour traiter les
sous-requêtes non-scalaires :
IN,NOT IN,EXISTS,NOT EXISTS,ANY,ALL
IN¶
expression IN (sub-query)- c’est la même chose que \(\in\) en notation mathématique (sauf que nous
devons gérer les valeurs
null) - la sous-requête doit renvoyer exactement 1 colonne
truesi l’expression est égale à 1 des lignes dans les résultats de la sous-requêtefalsesi l’expression n’est pasnullet qu’il n’y a pas de valeursnulldans la sous-requête et que l’expression n’est égale à aucune ligne de la sous-requêtenullsi l’expression estnullou si l’expression n’est égale à aucune ligne de la sous-requête et qu’il y a au moins 1 valeurnulldans la sous-requête
- c’est la même chose que \(\in\) en notation mathématique (sauf que nous
devons gérer les valeurs
-
Parce que SQL utilise une logique à 3 valeurs, évaluer
INest plus compliqué -
Rappelez-vous que si nous voulons savoir si une valeur de colonne est
null, nous ne pouvons pas utiliser l’opérateur d’égalité=car il renverra toujoursnullnullsignifie inconnu dans ce cas, donc nous ne savons pas comment comparer des valeurs à une valeur inconnue- Nous devons donc utiliser
is nullau lieu de= null…
-
L’opérateur
INcompare les valeurs avec=, donc dès qu’il compare avec unnull, il évaluera ànull- Donc si l’expression est égale à
null,INévaluera ànull - Si l’expression n’est pas
null, alors elle comparera l’expression avec des valeurs non-nulles d’abord dans la sous-requête- si elle trouve une correspondance, alors la valeur de
INsera vraie - si nous ne trouvons pas de correspondance, alors elle vérifiera si la
sous-requête contient des valeurs
null- si non, alors nous savons avec certitude que l’expression n’est
pas dans la sous-requête, donc la valeur de
INserafalse - s’il y a des valeurs
null, alors nous ne savons pas avec certitude si l’expression est dans la sous-requête parce que nous avons des valeurs inconnues (null), donc la valeur deINestnull
- si non, alors nous savons avec certitude que l’expression n’est
pas dans la sous-requête, donc la valeur de
- si elle trouve une correspondance, alors la valeur de
- Donc si l’expression est égale à
-
Cet exemple fonctionne comme prévu
-- notez que (1, 2, 3) n'est pas vraiment une sous-requête, mais agit comme une sous-requête
-- il est utilisé pour simplifier l'exemple
select *
from course
where cid in (1, 2, 3);
-- notez que (2, 3, 4, null) n'est pas vraiment une sous-requête, mais agit comme une sous-requête
-- il est utilisé pour simplifier l'exemple
select *
from course
where cid in (2, 3, 4, null);
- Cet exemple est équivalent et montre comment les opérateurs
INsont évalués en interne
- Cela fonctionne pour les cours avec une valeur
cidde 2, 3 ou 4 parce qu’au moins 1 des comparaisons sera vraie et nous obtiendrons quelque chose commeT OR F OR F OR N, ce qui est vrai - Mais pour les cours avec un
cidnon inclus dans l’ensemble fourni, nous obtiendronsnullparce queF OR F OR F OR NestN - Cela ne crée pas de problème car les lignes avec une condition
whereseront supprimées - Mais si nous nions
INpour obtenir un opérateurNOT IN, nous aurons des ennuis
select *
from course
where cid not in (2, 3, 4, null);
select *
from course
where cid not in (select cid from offering);
select *
from course
except
select c.*
from course c
inner join offering o on c.cid = o.cid;
select c.*
from course c
left join offering o on c.cid = o.cid
where o.oid is null;
select *
from instructor
where iid not in (select iid from offering);
- Nous n’obtenons rien
- Mais le cours avec
cid = 1n’est pas dans la sous-requête, alors pourquoi ne l’obtenons-nous pas ? - C’est à cause de la valeur
null1 in (2, 3, 4, null)évalue ànull- et
1 not in (2, 3, 4, null)évalue ànot null, ce qui estnull
- Donc les requêtes
NOT INsont dangereuses à cause des valeursnull - La requête suivante est correcte parce que nous savons avec certitude
que
ciddans course ne peut pas êtrenull- Donc nous pouvons trouver les cours qui n’ont jamais été offerts de cette manière
--insert into course(name, code, credits) --values ('Data Structures', 'DS', 3);
-- delete from course where code = 'DS';
select *
from course
where cid not in (select cid from offering);
- Mais essayer de faire quelque chose de similaire pour les instructeurs créera
des problèmes car
iiddans offering peut êtrenull - Nous devons explicitement exclure les valeurs
nulldans la sous-requête pour que la requête renvoie les résultats corrects
--insert into instructor(name, email, department) --values ('John', 'john@bbb.com', 'ECE');
-- delete from instructor where name = 'John';
select *
from instructor
where iid not in (select iid from offering);
Recommandation : ne pas utiliser NOT IN¶
Recommandation : utiliser une left join à la place¶
- Non seulement la jointure gauche (ou les jointures externes en général) vous
oblige à penser aux valeurs
null(et à les gérer correctement), mais en termes de performance, les jointures gauches seront généralement plus efficaces. - Utiliser des jointures gauches évite de traiter avec la logique à 3 valeurs de SQL.
ANY et ALL¶
ANYetALLsont utilisés comme modificateurs d’opérateurs (généralement des opérateurs de comparaison)expression operator ANY (sub-query)truelorsqu’il existe une ligne \(r\) dans la sous-requête telle queexpression operator rest vraifalselorsque pour toutes les lignes r dans la sous-requête,expression operator rest faux et qu’il n’y a pas de valeursnulldans la sous-requête…nulllorsque pour toutes les lignes r dans la sous-requête,expression operator rest faux et qu’il y a au moins 1 valeurnulldans la sous-requête
INest équivalent à=ANYexpression operator ALL (sub-query)truelorsque pour toutes les lignes \(r\) dans la sous-requête,expression operator rest vraifalselorsqueexpression operator rest faux pour au moins 1 ligne dans la sous-requêtenulllorsque pour toutes les lignes \(r\) dans la sous-requête,expression operator rn’est pas faux et qu’il y a au moins 1 valeurnulldans la sous-requête
NOT INest équivalent à<> ALL