Comment fonctionnent les instructions SQL EXISTS?

88

J'essaie d'apprendre SQL et j'ai du mal à comprendre les déclarations EXISTS. Je suis tombé sur cette citation sur "existe" et je ne comprends pas quelque chose:

En utilisant l'opérateur existe, votre sous-requête peut renvoyer zéro, une ou plusieurs lignes, et la condition vérifie simplement si la sous-requête a renvoyé des lignes. Si vous regardez la clause select de la sous-requête, vous verrez qu'elle se compose d'un seul littéral (1); étant donné que la condition de la requête contenant n'a besoin que de savoir combien de lignes ont été renvoyées, les données réelles renvoyées par la sous-requête ne sont pas pertinentes.

Ce que je ne comprends pas, c'est comment la requête externe sait quelle ligne la sous-requête vérifie? Par exemple:

SELECT *
  FROM suppliers
 WHERE EXISTS (select *
                 from orders
                where suppliers.supplier_id = orders.supplier_id);

Je comprends que si l'ID du fournisseur et la table des commandes correspondent, la sous-requête retournera true et toutes les colonnes de la ligne correspondante dans la table des fournisseurs seront générées. Ce que je n'obtiens pas, c'est comment la sous-requête communique quelle ligne spécifique (disons la ligne avec l'ID de fournisseur 25) doit être imprimée si seulement un vrai ou un faux est retourné.

Il me semble qu'il n'y a pas de relation entre la requête externe et la sous-requête.

Dan
la source

Réponses:

98

Pense-y de cette façon:

Pour «chaque» ligne de Suppliers, vérifiez s'il «existe» une ligne dans la Ordertable qui remplit la condition Suppliers.supplier_id(cela provient de la «ligne» actuelle de la requête externe) = Orders.supplier_id. Lorsque vous trouvez la première ligne correspondante, arrêtez-vous là - le WHERE EXISTSa été satisfait.

Le lien magique entre la requête externe et la sous-requête réside dans le fait qu'il Supplier_idest passé de la requête externe à la sous-requête pour chaque ligne évaluée.

Ou, pour le dire autrement, la sous-requête est exécutée pour chaque ligne de table de la requête externe.

Ce n'est PAS comme si la sous-requête est exécutée dans son ensemble et obtient le «vrai / faux», puis essaie de faire correspondre cette condition «vrai / faux» avec la requête externe.

sojin
la source
7
Merci! "Ce n'est PAS comme une sous-requête exécutée dans son ensemble et obtient le" vrai / faux ", puis essaie de faire correspondre cette condition" vrai / faux "avec la requête externe." est ce qui m'a vraiment éclairci, je continue de penser que c'est ainsi que les sous-requêtes fonctionnent (et souvent elles le font), mais ce que vous avez dit a du sens car la sous-requête repose sur la requête externe et doit donc être exécutée une fois par ligne
Clarence Liu
32

Il me semble qu'il n'y a pas de relation entre la requête externe et la sous-requête.

Que pensez-vous que fait la clause WHERE dans l'exemple EXISTS? Comment arrivez-vous à cette conclusion lorsque la référence SUPPLIERS n'est pas dans les clauses FROM ou JOIN de la clause EXISTS?

EXISTS prend la valeur TRUE / FALSE et se termine par TRUE lors de la première correspondance des critères - c'est pourquoi il peut être plus rapide que IN. Sachez également que la clause SELECT dans un EXISTS est ignorée - IE:

SELECT s.*
  FROM SUPPLIERS s
 WHERE EXISTS (SELECT 1/0
                 FROM ORDERS o
                WHERE o.supplier_id = s.supplier_id)

... devrait atteindre une division par zéro erreur, mais ce ne sera pas le cas. La clause WHERE est l'élément le plus important d'une clause EXISTS.

Sachez également qu'un JOIN ne remplace pas directement EXISTS, car il y aura des enregistrements parent en double s'il y a plus d'un enregistrement enfant associé au parent.

Poneys OMG
la source
1
Il me manque encore quelque chose. S'il se termine à la première correspondance, comment la sortie finit-elle par être tous les résultats où o.supplierid = s.supplierid? Ne sortirait-il pas simplement le premier résultat à la place?
Dan
3
@Dan: Les EXISTSsorties, retournant TRUE sur la première correspondance - car le fournisseur existe au moins une fois dans la table ORDERS. Si vous souhaitez voir la duplication des données FOURNISSEUR en raison de la présence de plusieurs relations enfants dans ORDERS, vous devez utiliser un JOIN. Mais la plupart ne veulent pas de cette duplication, et l'exécution de GROUP BY / DISTINCT est susceptible d'ajouter une surcharge à la requête. EXISTSest plus efficace que SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...sur SQL Server, n'a pas testé sur Oracle ou MySQL ces derniers temps.
OMG Ponies
J'avais une question, la correspondance est-elle effectuée pour chaque enregistrement sélectionné dans la requête externe. Comme dans, nous récupérons les commandes 5 fois s'il y a 5 lignes sélectionnées parmi les fournisseurs.
Rahul Kadukar le
24

Vous pouvez produire des résultats identiques utilisant JOIN, EXISTS, INou INTERSECT:

SELECT s.supplier_id
FROM suppliers s
INNER JOIN (SELECT DISTINCT o.supplier_id FROM orders o) o
    ON o.supplier_id = s.supplier_id

SELECT s.supplier_id
FROM suppliers s
WHERE EXISTS (SELECT * FROM orders o WHERE o.supplier_id = s.supplier_id)

SELECT s.supplier_id 
FROM suppliers s 
WHERE s.supplier_id IN (SELECT o.supplier_id FROM orders o)

SELECT s.supplier_id
FROM suppliers s
INTERSECT
SELECT o.supplier_id
FROM orders o
Anthony Faull
la source
1
bonne réponse, mais sachez aussi qu'il vaut mieux ne pas utiliser existe pour éviter la corrélation
Florian Fröhlich
1
Selon vous, quelle requête sera plus rapide si les fournisseurs ont 10 millions de lignes et les commandes 100 millions de lignes et pourquoi?
Teja
7

Si vous aviez une clause where qui ressemblait à ceci:

WHERE id in (25,26,27) -- and so on

vous pouvez facilement comprendre pourquoi certaines lignes sont renvoyées et d'autres non.

Lorsque la clause where est comme ceci:

WHERE EXISTS (select * from orders where suppliers.supplier_id = orders.supplier_id);

cela signifie simplement: retourner les lignes qui ont un enregistrement existant dans la table des commandes avec le même identifiant.

Menahem
la source
2

C'est une très bonne question, j'ai donc décidé d'écrire un article très détaillé sur ce sujet sur mon blog.

Modèle de table de base de données

Supposons que nous ayons les deux tables suivantes dans notre base de données, qui forment une relation de table un-à-plusieurs.

Tables SQL EXISTS

La studenttable est le parent et la student_gradeest la table enfant car elle a une colonne de clé étrangère student_id faisant référence à la colonne de clé primaire id dans la table student.

Le student tablecontient les deux enregistrements suivants:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

Et, le student_gradetableau stocke les notes que les élèves ont reçues:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL EXISTE

Disons que nous voulons obtenir tous les élèves qui ont reçu une note de 10 en classe de mathématiques.

Si nous ne sommes intéressés que par l'identifiant de l'étudiant, nous pouvons exécuter une requête comme celle-ci:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Mais, l'application est intéressée à afficher le nom complet d'un student, pas seulement l'identifiant, nous avons donc également besoin d'informations de la studenttable.

Afin de filtrer les studentenregistrements qui ont une note de 10 en mathématiques, nous pouvons utiliser l'opérateur SQL EXISTS, comme ceci:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Lors de l'exécution de la requête ci-dessus, nous pouvons voir que seule la ligne Alice est sélectionnée:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

La requête externe sélectionne les studentcolonnes de lignes que nous souhaitons renvoyer au client. Cependant, la clause WHERE utilise l'opérateur EXISTS avec une sous-requête interne associée.

L'opérateur EXISTS renvoie true si la sous-requête renvoie au moins un enregistrement et false si aucune ligne n'est sélectionnée. Le moteur de base de données n'a pas à exécuter entièrement la sous-requête. Si un seul enregistrement correspond, l'opérateur EXISTS renvoie true et l'autre ligne de requête associée est sélectionnée.

La sous-requête interne est corrélée car la colonne student_id de la student_gradetable est mise en correspondance avec la colonne id de la table externe Student .

Vlad Mihalcea
la source
Quelle belle réponse. Je pense que je n'ai pas compris le concept parce que j'utilisais un mauvais exemple. Fonctionne-t-elle EXISTuniquement avec la sous-requête corrélée? Je jouais avec une requête contenant seulement 1 table, comme SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). Je sais que ce que j'ai écrit pourrait être réalisé par une simple requête WHERE, mais je l'utilisais juste pour comprendre EXISTS. J'ai toutes les lignes. Est-ce bien dû au fait que je n'ai pas utilisé de sous-requête corrélée? Merci.
Bowen Liu
Cela n'a de sens que pour les sous-requêtes corrélées, car vous souhaitez filtrer les enregistrements de la requête externe. Dans votre cas, la requête interne peut être remplacée par WHERE TRUE
Vlad Mihalcea
Merci Vlad. C'est ce que je pensais. C'est juste une idée étrange qui s'est produite lorsque je déconnais avec ça. Honnêtement, je ne connaissais pas le concept de sous-requête corrélée. Et maintenant, il est beaucoup plus logique de filtrer les lignes de la requête externe avec la requête interne.
Bowen Liu
0

EXISTS signifie que la sous-requête renvoie au moins une ligne, c'est vraiment ça. Dans ce cas, il s'agit d'une sous-requête corrélée car elle vérifie le supplier_id de la table externe par rapport au supplier_id de la table interne. Cette requête dit, en effet:

CHOISIR tous les fournisseurs Pour chaque ID fournisseur, voir si une commande existe pour ce fournisseur Si le fournisseur n'est pas présent dans la table des commandes, supprimer le fournisseur des résultats RETOUR tous les fournisseurs qui ont des lignes correspondantes dans la table des commandes

Vous pouvez faire la même chose dans ce cas avec un INNER JOIN.

SELECT suppliers.* 
  FROM suppliers 
 INNER 
  JOIN orders 
    ON suppliers.supplier_id = orders.supplier_id;

Le commentaire des poneys est correct. Vous devez effectuer un regroupement avec cette jointure ou sélectionner distinct en fonction des données dont vous avez besoin.

David Fells
la source
4
La jointure interne produira des résultats différents de EXISTS si plusieurs enregistrements enfants sont associés à un parent - ils ne sont pas identiques.
OMG Ponies
Je pense que ma confusion pourrait être que j'ai lu que la sous-requête avec un EXISTS retourne vrai ou faux; mais ce ne peut pas être la seule chose qui revient, non? La sous-requête renvoie-t-elle également tous les "fournisseurs qui ont des lignes correspondantes dans la table des commandes"? Mais si c'est le cas, comment l'instruction EXISTS renvoie-t-elle un résultat booléen? Tout ce que je lis dans les manuels dit qu'il ne renvoie qu'un résultat booléen, donc j'ai du mal à réconcilier le résultat du code avec ce qu'on me dit qu'il renvoie.
Dan
Lisez EXISTS comme une fonction ... EXISTS (resultset). La fonction EXISTS renverrait alors true si l'ensemble de résultats contient des lignes, false s'il est vide. C'est fondamentalement ça.
David Fells
3
@Dan, considérez que EXISTS () est évalué logiquement pour chaque ligne source indépendamment - ce n'est pas une valeur unique pour toute la requête.
Arvo
-1

Ce que vous décrivez est une soi-disant requête avec une sous-requête corrélée .

(En général) c'est quelque chose que vous devriez essayer d'éviter en écrivant la requête en utilisant une jointure à la place:

SELECT suppliers.* 
FROM suppliers 
JOIN orders USING supplier_id
GROUP BY suppliers.supplier_id

Dans le cas contraire, la sous-requête sera exécutée pour chaque ligne de la requête externe.

Wouter van Nifterick
la source
2
Ces deux solutions ne sont pas équivalentes. La jointure donne un résultat différent de la sous-requête EXISTS s'il y a plus d'une ligne ordersqui correspond à la condition de jointure.
a_horse_with_no_name
1
merci pour la solution alternative. mais suggérez-vous que si on me donne une option entre la sous-requête corrélée et la jointure, je devrais opter pour la jointure parce que c'est plus efficace?
sunny_dev