Sous-requêtes
Les sous-requêtes
Il est possible de combiner plusieurs requêtes dans les clauses from
et where
, avec ce qu'on appelle une sous-requête ou sub-query.
Par exemple, imaginez qu'on veut connaître tous les intervenants qui interviennent à Paris. On peut formuler une requête ainsi :
Ou bien la même requête à l'aide des sub-queries :
Ici, on divise la requête en 3 parties. D'abord on récupère la liste d'interventions dans le 75 :
Ceci produira une table bien plus limitée que la table d'interventions initiale. On peut utiliser cette table temporaire pour trouver uniquement les événements liés à ces intervenants déjà filtrés, avec l'opérateur in
:
Et, pour aller plus loin, on a maintenant une liste de eventId
qui est beaucoup plus limité que la table d'événements initiale. On répète le processus pour trouver les intervenants.
Il y a plusieurs avantages :
La requête est souvent plus facile à lire et comprendre
On limite des tables impliquées dans les produits cartésiens : des requêtes plus optimisées ! Et, ceci est connu et suggéré par les développeurs de MySQL
Parenthèse : optimisation et le produit cartésien
Pendant les phases de développement d'une application, on ne se rend pas compte des usages qui vont émerger.
Par exemple, on crée une base de données en mettant en avant son interactivité (mises à jour et résultats en temps réel). On utilise tous les règles, outils et méthodologies de database design comme la normalisation (voir les chapitres suivants) pour ce faire.
Seulement, après quelques mois ou années d'opération, vous avez maintenant une base de données bien remplie (quelques milliards de lignes par table).
Présentement, vous devez extraire des rapports, nécessitant plusieurs jointures de table. Et, les requêtes sont beaucoup trop longues. Pourquoi ? Et, que faire ?
À la racine du problème de performance est notre idée de produit cartésien. S'il faut joindre 2 tables immenses sur des critères pas assez uniques, la seule stratégie à la disposition de votre SGBDR est de démultiplier des lignes des 2 tables et extraire des lignes correspondantes. Si vous avez deux tables, chacune avec 100k lignes, il y aura finalement 10 milliards de lignes à traiter !
Qu'est-ce que je veux dire par critère pas assez unique ? Souvent, on stocke des caractéristiques d'une entité dans une colonne de notre base. Si, même en filtrant les lignes par un attribut pas vraiment distinguant, on peut se trouver avec quelques milliers de lignes de chaque côté de la jointure.
On utilise le terme cardinalité pour parler du degré d'unicité d'un attribut ou colonne. Une cardinalité élevée veut dire qu'il y a beaucoup de valeurs uniques. Une cardinalité basse veut dire qu'il y a beaucoup de répétition.
Attention ! On va revoir le terme cardinalité dans le contexte des liens entre des tables dans le chapitre de la modélisation de données.
Utiliser les sous-requêtes donc est souvent une stratégie d'optimisation ! On essaye de limiter le nombre de lignes de chaque côté d'une jointure pour limiter autant possible le produit cartésien obligatoire.
Dans les SGBDR modernes, notre requête SQL est transformée en plan d'action (ou query plan), et fréquemment, il est possible que ce plan d'action arrive à sous-diviser notre requête pour la meilleure performance (regardez cette page pour plus d'information).
Fréquemment, on donne au SGDBR d'autres aides à l'optimisation de son plan d'action : la création des indexes (voir les chapitres suivants), ou bien la reformulation nous-même d'une requête.
Inclusion : in
in
On utilise l'opérateur in
pour créer une requête qui utilise les résultats d'une sous-requête dans une clause where
.
Par exemple, je veux extraire le nombre d'interventions avec, au moins, un événement annulé en 2020 :
Notez bien l'usage de distinct
dans la sous-requête : cela optimise d'abord le set d'interventions à utiliser dans le croisement, parce qu'il peut y avoir plusieurs événements par intervention.
Exclusion : not in
not in
Au contraire, pour savoir les interventions non annulées en 2022, je peux inverser ma requête :
in
et not in
pour les valeurs simples
in
et not in
pour les valeurs simplesJe peux trouver toutes les interventions avec un état (state
) de plusieurs valeurs avec l'opérateur in
:
Inclusion : tout ou une partie avec some
et all
some
et all
L'opérateur in
permet de tester si une valeur se trouve dans un set (récupéré via une sous-requête). C'est une comparaison d'égalité. En revanche, parfois, je veux faire une comparaison de grandeur :
est qu'une valeur est supérieure à au moins une autre ?
est qu'une valeur est supérieure à toutes les autres ?
Pour cela, les opérateurs some
et all
existent. Vous vous souvenez le problème suivant ?
Je veux savoir s'il existe des interventions qui sont facturées plus chères que celles en Île de France.
La solution qu'on a trouvée utilisait l'opérateur as
:
En revanche, la requête était très longue (> 2 minutes )! On peut optimiser avec une sous-requête et l'opérateur some
:
La requête se fait en quelques millisecondes !!
Attention, par contre, dans le résultat on sort toutes les interventions avec un budget supérieur à au moins une intervention hors Île-de-France.
Si je veux extraire les interventions avec budget supérieur à toutes les interventions en Île-de-France, j'utilise l'opérateur > all
:
Les options de some
et all
sont :
Tester l'existence : exists
et not exists
exists
et not exists
Parfois, je veux exprimer une condition basée sur l'existence d'un résultat (le nombre ou valeur ne compte pas).
Par exemple, je veux extraire la liste d'intervenants qui n'ont jamais participé à un événement (pour une purge éventuelle de la base). Dans ce cas, il faut juste tester l'existence d'une ligne dans event_animator
pour chaque intervenant de la plateforme :
Notez que dans cet exemple, on utilise une information provenant de la requête "mère" (a.animatorId
) dans la sous-requête. Dans ce cas, l'utilisation des alias est important.
Sous requêtes dans la clause from
from
Je peux aussi considérer les résultats d'une requête comme une table éphémère qui peut aussi être interrogé.
Par exemple, je veux extraire la liste de départements où le budget est supérieur à 1000€. Je peux diviser cette requête en 2 parties :
extraire le moyen de budget par département (avec
avg
etgroup by
)ensuite, filtrer les lignes de la requête avant
Il y a toujours plusieurs façons de faire la même chose. L'équivalent de la requête dessus est :
Votre choix de requête dépendra plusieurs facteurs :
La performance de la requête : en fonction de votre schéma, parfois l'utilisation d'une sous-requête pourrait être plus rapide.
La lisibilité de la requête : si la différence en performance est négligeable, le fait de pouvoir lire et facilement comprendre la requête compte pour beaucoup !
On peut également utiliser la clause with
afin de définir de façon plus stricte et explicite la structure de la table éphémère. La requête suivant aura les mêmes résultats :
Ici, on déclare et remplit d'abord une table éphémère qui s'appelle action_temp
, avec 2 attributs code
et avg_budget
. Ensuite, on exécute une requête select
sur cette table éphémère.
Sous requêtes dans la clause select
select
Je peux aussi utiliser une sous-requête dans la clause `select.
Imaginons que je veux savoir le nombre d'interventions par région.
Il y a toujours plusieurs façons de faire la même chose. L'équivalent de la requête dessus est :
Exercices
Les exercices suivants sont basés sur la base de données SAKILA, une base de données de test fourni par MySQL.
Question 1
Dans l’exercice 3, nous avons affiché une liste d’acteurs qui partagent le même nom de famille. Comment récupérer juste le nombre de lignes retournées par cette requête ?
Question 2
Utilisez des sous-requêtes afin de trouver tous les acteurs qui sont dans le film « Alone Trip »
Question 3
Utilisez l’opérateur in
afin de trouver tous les films avec un classement « G », « PG », ou « PG-13 »
Question 4
Calculez la durée moyenne des films avec le classement « G », « PG », ou « PG-13 ».
Dernière mise à jour