Je choisis toujours par défaut NOT EXISTS
.
Les plans d'exécution peuvent être les mêmes pour le moment mais si l'une ou l'autre des colonnes est modifiée à l'avenir pour permettre NULL
s le NOT IN
devra faire plus de travail (même si aucune NULL
sont effectivement présents dans les données) et la sémantique de l'option NOT IN
si NULL
s sont présents ont peu de chances d'être ceux que vous voulez de toute façon.
Lorsque ni l'un ni l'autre Products.ProductID
ou [Order Details].ProductID
permettre NULL
s le NOT IN
sera traitée de manière identique à la requête suivante.
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
Le plan exact peut varier, mais pour les données de mon exemple, j'obtiens ce qui suit.
Une idée fausse assez courante semble être que les sous-requêtes corrélées sont toujours "mauvaises" par rapport aux jointures. Elles peuvent certainement l'être lorsqu'elles forcent un plan de boucles imbriquées (sous-requête évaluée ligne par ligne) mais ce plan inclut un opérateur logique anti-semi-joint. Les anti-semi-jonctions ne sont pas limitées aux boucles imbriquées mais peuvent aussi utiliser des jointures de hachage ou de fusion (comme dans cet exemple).
/*Not valid syntax but better reflects the plan*/
SELECT p.ProductID,
p.ProductName
FROM Products p
LEFT ANTI SEMI JOIN [Order Details] od
ON p.ProductId = od.ProductId
Si [Order Details].ProductID
es NULL
-La requête devient alors
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
AND NOT EXISTS (SELECT *
FROM [Order Details]
WHERE ProductId IS NULL)
La raison en est que la sémantique correcte si [Order Details]
contient tout NULL
ProductId
est de ne retourner aucun résultat. Voir le spool extra anti semi joint et row count pour vérifier cela qui est ajouté au plan.
Si Products.ProductID
est également modifié pour devenir NULL
-La requête devient alors
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
AND NOT EXISTS (SELECT *
FROM [Order Details]
WHERE ProductId IS NULL)
AND NOT EXISTS (SELECT *
FROM (SELECT TOP 1 *
FROM [Order Details]) S
WHERE p.ProductID IS NULL)
La raison de celle-ci est qu'un NULL
Products.ProductId
ne doit pas être retourné dans les résultats excepto si le NOT IN
sous-requête ne renvoie aucun résultat (c'est-à-dire que l'option [Order Details]
est vide). Dans ce cas, il devrait le faire. Dans le plan pour mon échantillon de données, ceci est implémenté en ajoutant une autre jointure anti semi comme ci-dessous.
L'effet de ce phénomène est illustré dans l'article du blog déjà cité par Buckley . Dans cet exemple, le nombre de lectures logiques passe d'environ 400 à 500 000.
En outre, le fait qu'un seul NULL
peut réduire le nombre de lignes à zéro rend l'estimation de la cardinalité très difficile. Si le serveur SQL suppose que cela se produira, mais qu'en fait il n'y a pas eu d'erreur de calcul. NULL
rangs dans les données, le reste du plan d'exécution peut être catastrophiquement pire, s'il ne s'agit que d'une partie d'une requête plus importante, avec des boucles imbriquées inappropriées entraînant l'exécution répétée d'un sous-arbre coûteux, par exemple .
Ce n'est pas le seul plan d'exécution possible d'une NOT IN
sur un NULL
-Toutefois, cette colonne peut être utilisée. Cet article en montre un autre pour une requête contre le AdventureWorks2008
base de données.
Pour le NOT IN
sur un NOT NULL
ou la colonne NOT EXISTS
contre une colonne nullable ou non nullable, il donne le plan suivant.
Lorsque la colonne devient NULL
-d'activer le NOT IN
Le plan ressemble maintenant à
Il ajoute un opérateur de jointure interne supplémentaire au plan. Cet appareil est expliqué ici . Tout est là pour convertir l'ancienne recherche d'index unique corrélé sur Sales.SalesOrderDetail.ProductID = <correlated_product_id>
à deux recherches par rangée extérieure. La recherche supplémentaire se fait sur WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Comme il s'agit d'une jointure anti-demi, si celle-ci renvoie des lignes, la deuxième recherche n'aura pas lieu. Cependant, si Sales.SalesOrderDetail
ne contient pas de NULL
ProductID
s, cela doublera le nombre d'opérations de recherche nécessaires.
3 votes
Avez-vous essayé le plan en utilisant une jointure gauche où est nul ?
2 votes
NOT IN et NOT EXISTS ne sont pas identiques. Jetez un coup d'œil à ce lien pour connaître la différence entre les deux : weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
2 votes
Je me demande si Databases diffère, mais dans mon dernier benchmark contre PostgreSQL, ceci
NOT IN
requête :SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)
est presque 30 fois plus rapide que cetteNOT EXISTS
:SELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
1 votes
Duplicata possible de Quelle est la différence entre NOT EXISTS, NOT IN et LEFT JOIN WHERE IS NULL ?
1 votes
@rcdmk Avez-vous vérifié la date sur les questions ?
0 votes
@ilitirit La seule intention de ce drapeau était de lier les deux questions. Je ne peux pas fermer cette question à moi seul, à moins que 4 autres utilisateurs ne soient d'accord. La réponse à cette question nous donne plus de détails à ajouter. Peut-être que les deux pourraient être fusionnées pour produire une référence encore meilleure.
0 votes
Dans SQL Server 2012, testé sur différentes paires de tables, de différentes tailles, les deux requêtes étaient au coude à coude. Sachez que si le
col
dans la sous-requête est nullable alors vous devez ajouter... WHERE col IS NOT NULL
dans la sous-requête. Après cela, les résultats seront les mêmes et les plans seront à peu près identiques.