Question

Je souhaite sélectionner des lignes dans une table où la clé primaire se trouve dans une autre table. Je ne suis pas sûr de devoir utiliser un opérateur JOIN ou IN dans SQL Server 2005. Existe-t-il une différence de performances significative entre ces deux requêtes SQL avec un jeu de données volumineux (c'est-à-dire des millions de lignes)?

SELECT *
FROM a
WHERE a.c IN (SELECT d FROM b)

SELECT a.*
FROM a JOIN b ON a.c = b.d
Était-ce utile?

La solution

Mise à jour:

Cet article de mon blog récapitule ma réponse et mes commentaires en une autre réponse, et présente les plans d'exécution réels:

SELECT  *
FROM    a
WHERE   a.c IN (SELECT d FROM b)

SELECT  a.*
FROM    a
JOIN    b
ON      a.c = b.d

Ces requêtes ne sont pas équivalentes. Ils peuvent donner des résultats différents si votre table b n'est pas préservée par la clé (c.-à-d. Les valeurs de b.d ne sont pas uniques).

L'équivalent de la première requête est le suivant:

SELECT  a.*
FROM    a
JOIN    (
        SELECT  DISTINCT d
        FROM    b
        ) bo
ON      a.c = bo.d

Si bd est UNIQUE et marqué comme tel (avec un UNIQUE INDEX ou un UNIQUE CONSTRAINT ), alors ces requêtes sont identiques et utiliseront probablement des plans identiques, car SQL Server est suffisamment intelligent pour en tenir compte.

SQL Server peut utiliser l'une des méthodes suivantes pour exécuter cette requête:

  • S'il existe un index sur ac , d est UNIQUE et b est relativement petit. Par rapport à a , la condition est propagée dans la sous-requête et le INNER JOIN simple est utilisé (avec b en tête)

  • S'il existe un index sur bd et que d n'est pas UNIQUE , la condition est également propagée et LEFT SEMI JOIN est utilisé. Il peut également être utilisé pour la condition ci-dessus.

  • S'il existe un index sur bd et ac et qu'ils sont grands, MERGE SEMI JOIN est utilisé

  • S'il n'y a pas d'index sur une table, une table de hachage est construite sur b et HASH SEMI JOIN est utilisé.

Aucune de ces méthodes ne réévalue la sous-requête entière à chaque fois.

Consultez cette entrée sur mon blog pour plus de détails sur son fonctionnement:

Il existe des liens pour tous les SGBDR des quatre grands.

Autres conseils

Ni l'un ni l'autre. Utilisez une jointure ANSI-92:

SELECT a.*
FROM a JOIN b a.c = b.d

Cependant, c'est mieux comme EXISTS

SELECT a.*
FROM a
WHERE EXISTS (SELECT * FROM b WHERE a.c = b.d)

Ceci supprime les doublons qui pourraient être générés par JOIN, mais s'exécutent tout aussi vite sinon plus vite

Le IN est évalué (et la sélection parmi les ré-exécutions de b) pour chaque ligne de a, tandis que JOIN est optimisé pour utiliser des index et autres astuces de pagination soignées ...

Cependant, dans la plupart des cas, l'optimiseur serait capable de construire une jointure à partir d'une sous-requête corrélée et de se retrouver avec le même plan d'exécution.

Modifier: Veuillez lire les commentaires ci-dessous pour poursuivre ... la discussion sur la validité de cette réponse et la réponse à la question du PO. =)

Parlant d'expérience sur une table comportant 49 000 000 lignes, je recommanderais LEFT OUTER JOIN. Utilisation de IN ou de EXISTS Il a fallu 5 minutes pour terminer le processus où LEFT OUTER JOIN se termine en 1 seconde.

SELECT a.*
FROM a LEFT OUTER JOIN b ON a.c = b.d
WHERE b.d is not null -- Given b.d is a primary Key with index

En fait, dans ma requête, je le fais sur 9 tables.

Mis à part le fait de le tester vous-même sur un grand nombre de données de test, je dirais que vous utilisez le système JOINS. J'ai toujours eu de meilleures performances en les utilisant dans la plupart des cas par rapport à une sous-requête IN, et vous avez beaucoup plus d'options de personnalisation en ce qui concerne la manière de rejoindre, ce qui est sélectionné, ce qui ne l'est pas, etc.

Ce sont des requêtes différentes avec des résultats différents. Avec la requête IN, vous obtenez 1 ligne de la table 'a' chaque fois que le prédicat correspond. Avec la requête INNER JOIN, vous obtiendrez une * b lignes chaque fois que la condition de jointure correspond. Ainsi, avec des valeurs dans a de {1,2,3} et b de {1,2,2,3}, vous obtiendrez 1,2,2,3 du JOIN et 1,2,3 du IN.

EDIT - Je pense que vous pouvez trouver ici quelques réponses qui vous donneront une idée fausse. Allez tester vous-même et vous verrez que ce sont tous des plans de requête précis:

create table t1 (t1id int primary key clustered)
create table t2 (t2id int identity primary key clustered
    ,t1id int references t1(t1id)
)


insert t1 values (1)
insert t1 values (2)
insert t1 values (3)
insert t1 values (4)
insert t1 values (5)

insert t2 values (1)
insert t2 values (2)
insert t2 values (2)
insert t2 values (3)
insert t2 values (4)


select * from t1 where t1id in (select t1id from t2)
select * from t1 where exists (select 1 from t2 where t2.t1id = t1.t1id)
select t1.* from t1 join t2 on t1.t1id = t2.t1id

Les deux premiers plans sont identiques. Le dernier plan est une boucle imbriquée, cette différence est attendue car, comme je l’ai mentionné ci-dessus, la jointure a une sémantique différente.

De la documentation MSDN sur les fondamentaux de la sous-requête :

  

De nombreuses instructions Transact-SQL qui   inclure les sous-requêtes peuvent être   alternativement formulé comme jointure.   D'autres questions peuvent être posées uniquement avec   sous-requêtes. Dans Transact-SQL, il y a   généralement pas de différence de performance   entre une déclaration qui comprend un   sous-requête et un équivalent sémantique   version qui ne le fait pas. Cependant, dans   certains cas où l'existence doit être   vérifié, une jointure donne de meilleurs résultats   performance. Sinon, le imbriqué   la requête doit être traitée pour chaque   résultat de la requête externe pour assurer   élimination des doublons. Dans tel   cas, une approche conjointe aboutirait   de meilleurs résultats.

Dans l'exemple que vous avez fourni, la requête imbriquée n'a besoin d'être traitée qu'une seule fois pour chacun des résultats de la requête externe. Par conséquent, il ne devrait y avoir aucune différence de performances. Vérifier les plans d’exécution des deux requêtes devrait le confirmer.

Remarque: bien que la question elle-même ne spécifie pas SQL Server 2005, j'ai répondu à cette hypothèse en fonction des balises de question. D'autres moteurs de base de données (même différentes versions de SQL Server) peuvent ne pas être optimisés de la même manière.

Observez le plan d’exécution des deux types et tirez vos conclusions. À moins que le nombre d'enregistrements renvoyés par la sous-requête dans le champ "IN" déclaration est très petite, la variante IN est presque certainement plus lente.

Je voudrais utiliser une jointure, en pariant que ce sera beaucoup plus rapide que IN. Cela suppose évidemment la définition de clés primaires, ce qui permet une indexation extrêmement rapide.

Il est généralement admis qu'une jointure serait plus efficace que la sous-requête IN; Toutefois, l'optimiseur SQL * Server n'entraîne normalement aucune différence de performances notable. Malgré tout, il est probablement préférable de coder en utilisant la condition de jointure pour maintenir la cohérence de vos normes. De plus, si vos données et votre code doivent être migrés ultérieurement, le moteur de base de données risque de ne pas être aussi tolérant (par exemple, utiliser une jointure au lieu d'une sous-requête IN fait une énorme différence dans MySql).

La théorie ne vous mènera que jusqu'à présent sur des questions comme celle-ci. À la fin de la journée, vous voudrez tester les deux requêtes et voir celles qui fonctionnent réellement plus rapidement. Il est arrivé que la version JOIN prenne plus d'une minute et que la version IN prenne moins d'une seconde. J'ai également eu des cas où JOIN était en fait plus rapide.

Personnellement, j’ai tendance à commencer avec la version IN si je sais que je n’aurai pas besoin de champs de la table de sous-requêtes. Si cela commence à ralentir, je vais optimiser. Heureusement, pour les grands ensembles de données, la réécriture de la requête fait une telle différence que vous pouvez simplement la chronométrer à partir de l'Analyseur de requêtes et savoir que vous progressez.

Bonne chance!

J'ai toujours été un partisan de la méthodologie IN. Ce lien contient les détails d'un test effectué dans PostgresSQL. http://archives.postgresql.org/pgsql-performance/2005- 02 / msg00327.php

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top