106 votes

Comment fonctionnent les instructions SQL EXISTS ?

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

En utilisant l'opérateur exists, votre sous-requête peut renvoyer zéro, un ou plusieurs enregistrements, et la condition vérifie simplement si la sous-requête a renvoyé des enregistrements. Si vous regardez la clause select de la sous-requête, vous verrez qu'elle est constituée d'un seul littéral (1). Puisque la condition de la requête contenue 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-elle 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'identifiant du fournisseur et celui de la table des commandes correspondent, la sous-requête retournera vrai et toutes les colonnes de la ligne correspondante dans la table des fournisseurs seront éditées. Ce que je ne comprends pas, c'est comment la sous-requête communique quelle ligne spécifique (disons la ligne avec l'id du 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.

118voto

sojin Points 1752

Pensez-y de cette façon :

Pour "chaque" ligne de Suppliers vérifiez s'il existe une ligne dans le fichier Order table qui répond à la condition Suppliers.supplier_id (ceci provient de la requête externe 'row' actuelle) = Orders.supplier_id . Lorsque vous trouvez la première rangée correspondante, arrêtez-vous là - la WHERE EXISTS a été satisfaite.

Le lien magique entre la requête externe et la sous-requête réside dans le fait que Supplier_id est transmis 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 était exécutée sur l'ensemble et obtenait le "vrai/faux", puis essayait de faire correspondre cette condition "vrai/faux" avec la requête externe.

12 votes

Merci ! "Ce n'est PAS comme si une sous-requête était exécutée sur l'ensemble et obtenait le 'vrai/faux', puis essayait de faire correspondre cette condition 'vrai/faux' avec la requête externe." C'est ce qui m'a vraiment éclairé, je continue à penser que c'est comme ça que les sous-requêtes fonctionnent (et c'est souvent le cas), mais ce que vous avez dit est logique parce que la sous-requête repose sur la requête externe et doit donc être exécutée une fois par ligne.

33voto

OMG Ponies Points 144785

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

À votre avis, à quoi sert la clause WHERE dans l'exemple EXISTS ? Comment parvenez-vous à cette conclusion lorsque la référence aux FOURNISSEURS ne figure pas dans les clauses FROM ou JOIN de la clause EXISTS ?

EXISTS évalue pour VRAI/FAUX, et sort comme VRAI à 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 provoquer une erreur de division par zéro, mais ce n'est pas le cas. La clause WHERE est la pièce la plus importante d'une clause EXISTS.

Sachez également qu'une JOIN n'est pas un remplacement direct de EXISTS, car il y aura des enregistrements parents en double s'il y a plus d'un enregistrement enfant associé au parent.

1 votes

Il me manque encore quelque chose. S'il se termine au premier résultat, comment se fait-il que la sortie soit tous les résultats où o.supplierid = s.supplierid ? Ne devrait-il pas simplement afficher le premier résultat à la place ?

3 votes

@Dan : Le EXISTS sort en renvoyant VRAI à la première correspondance, car le fournisseur existe au moins une fois dans la table ORDERS. Si vous vouliez voir la duplication des données du FOURNISSEUR en raison de l'existence de plus d'une relation enfant dans ORDERS, vous devriez utiliser une JOIN. Mais la plupart des utilisateurs ne veulent pas de cette duplication, et l'utilisation de GROUP BY/DISTINCT risque d'ajouter des frais généraux à la requête. EXISTS est plus efficace que SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ... sur SQL Server, je n'ai pas testé sur Oracle ou MySQL dernièrement.

0 votes

J'ai une question : la correspondance est-elle effectuée pour chaque enregistrement qui est sélectionné dans la requête externe ? Par exemple, devons-nous extraire 5 fois des commandes s'il y a 5 lignes sélectionnées dans les fournisseurs ?

31voto

Anthony Faull Points 6490

Vous pouvez obtenir des résultats identiques en utilisant soit JOIN , EXISTS , IN o 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

1 votes

Excellente réponse, mais sachez aussi qu'il est préférable de ne pas utiliser exists pour éviter toute corrélation.

2 votes

À votre avis, quelle requête s'exécutera plus rapidement si les fournisseurs ont 10 millions de lignes et les commandes 100 millions de lignes, et pourquoi ?

7voto

Menahem Points 1539

Si vous aviez une clause where qui ressemble à ça :

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

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

Quand 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.

0voto

David Fells Points 3829

EXISTS signifie que la sous-requête renvoie au moins une ligne, c'est tout. Dans ce cas, il s'agit d'une sous-requête corrélée, car elle compare le numéro de fournisseur de la table externe au numéro de fournisseur de la table interne. Cette requête dit, en fait :

SELECTIONNER tous les fournisseurs Pour chaque ID de fournisseur, vérifiez si une commande existe pour ce fournisseur. Si le fournisseur n'est pas présent dans la table des commandes, supprimez-le des résultats. RETOURNER tous les fournisseurs qui ont des lignes correspondantes dans la table des commandes.

Vous pourriez 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 de Ponies est correct. Vous devez effectuer un regroupement avec cette jointure, ou sélectionner des données distinctes en fonction des données dont vous avez besoin.

4 votes

La jointure interne produira des résultats différents de ceux de EXISTS si plus d'un enregistrement enfant est associé à un parent -- ils ne sont pas identiques.

0 votes

Je pense que ma confusion vient du fait que j'ai lu que la sous-requête avec un EXISTS renvoie vrai ou faux ; mais cela ne peut pas être la seule chose qu'elle renvoie, n'est-ce pas ? 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 livres de texte dit qu'elle ne renvoie qu'un résultat booléen, et j'ai donc du mal à concilier le résultat du code avec ce qu'on me dit qu'il renvoie.

0 votes

Lire EXISTS comme une fonction... EXISTS(resultset). La fonction EXISTS renvoie alors true si le resultset contient des lignes, false s'il est vide. En gros, c'est ça.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X