web-dev-qa-db-fra.com

PAS IN vs PAS EXISTE

Laquelle de ces requêtes est la plus rapide?

N'EXISTE PAS:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE NOT EXISTS (
    SELECT 1 
    FROM Northwind..[Order Details] od 
    WHERE p.ProductId = od.ProductId)

Ou PAS DANS:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
    SELECT ProductID 
    FROM Northwind..[Order Details])

Le plan d'exécution de la requête indique qu'ils font tous les deux la même chose. Si tel est le cas, quelle est la forme recommandée?

Ceci est basé sur la base de données NorthWind.

[Modifier]

Je viens de trouver cet article utile: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Je pense que je vais rester avec NOT EXISTS.

491
ilitirit

J'ai toujours défaut à NOT EXISTS.

Les plans d’exécution peuvent être les mêmes pour le moment, mais si l’une des colonnes est modifiée à l’avenir pour permettre à NULLs, la version NOT IN devra faire plus (même si aucun NULLs n’est réellement présent dans les données) et la sémantique de NOT IN si NULLs sont présents ne sont probablement pas ceux que vous voulez de toute façon.

Lorsque ni Products.ProductID ni [Order Details].ProductID n'autorisent NULLs, le NOT IN sera traité 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 exemples de données, j’obtiens ce qui suit.

Neither NULL

Une idée fausse assez commune semble être que les sous-requêtes corrélées sont toujours "mauvaises" par rapport aux jointures. Cela peut certainement arriver lorsqu'ils imposent un plan de boucles imbriquées (sous-requête évaluée ligne par ligne), mais ce plan comprend un opérateur logique anti-jointure. Les jointures anti-semi ne sont pas limitées aux boucles imbriquées, mais peuvent également 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 est 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 n'importe quel NULLProductIds ne renvoie aucun résultat. Reportez-vous à la file d'attente supplémentaire anti-jointure et au nombre de lignes pour vérifier si elle est ajoutée au plan.

One NULL

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 en est qu’un NULLProducts.ProductId ne doit pas être renvoyé dans les résultats , sauf si la sous-requête NOT IN était pour ne renvoyer aucun résultat (c'est-à-dire que la table [Order Details] est vide). Dans ce cas, cela devrait. Dans le plan de mes exemples de données, ceci est mis en œuvre en ajoutant un autre anti-jointure comme indiqué ci-dessous.

Both NULL

L'effet de ceci est montré dans l'article de blog déjà lié par Buckley . Dans cet exemple, le nombre de lectures logiques est passé d’environ 400 à 500 000.

De plus, le fait qu'un seul NULL puisse réduire le nombre de lignes à zéro rend l'estimation de la cardinalité très difficile. Si SQL Server suppose que cela se produira mais qu’il n’ya en fait aucune ligne NULL dans les données, le reste du plan d’exécution risque d’être catastrophique, si cela ne fait que faire partie d’une requête plus volumineuse avec des boucles imbriquées inappropriées. exécution répétée d'un sous-arbre coûteux, par exemple .

Cependant, ce n'est pas le seul plan d'exécution possible pour un NOT IN sur une colonne NULL-. Cet article en présente un autre pour une requête sur la base de données AdventureWorks2008.

Pour le NOT IN sur une colonne NOT NULL ou le NOT EXISTS par rapport à une colonne nullable ou non nullable, il donne le plan suivant.

Not EXists

Lorsque la colonne devient NULL-, le plan NOT IN ressemble maintenant à

Not In - Null

Il ajoute un opérateur de jointure interne supplémentaire au plan. Cet appareil est expliqué ici . Tout est fait pour convertir la précédente recherche d'index unique corrélée sur Sales.SalesOrderDetail.ProductID = <correlated_product_id> en deux recherches par ligne externe. Le numéro supplémentaire est sur WHERE Sales.SalesOrderDetail.ProductID IS NULL.

Comme il s’agit d’une jointure anti semi si celle-ci renvoie des lignes, la seconde recherche n’aura pas lieu. Toutefois, si Sales.SalesOrderDetail ne contient pas de NULLProductIDs, le nombre d'opérations de recherche requises sera doublé.

650
Martin Smith

Sachez également que NOT IN n'est pas équivalent à NOT EXISTS en ce qui concerne null.

Ce post explique très bien

http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/

Lorsque la sous-requête renvoie même une valeur null, NOT IN ne correspond à aucune ligne.

La raison de ceci peut être trouvée en regardant les détails de ce que signifie réellement l'opération NOT IN.

Supposons qu’à titre d’illustration, il y a 4 lignes dans la table appelée t, une colonne appelée ID avec les valeurs 1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t)

est équivalent à

WHERE SomeValue != (SELECT AVal FROM t WHERE ID=1)
AND SomeValue != (SELECT AVal FROM t WHERE ID=2)
AND SomeValue != (SELECT AVal FROM t WHERE ID=3)
AND SomeValue != (SELECT AVal FROM t WHERE ID=4)

Ajoutons que AVal est NULL où ID = 4. Par conséquent, la comparaison! = Renvoie INCONNU. La table de vérité logique pour AND indique que INCONNU et VRAI est INCONNU, INCONNU et FAUX est FAUX. Il n’existe pas de valeur pouvant être AND avec DONNÉ pour produire le résultat VRAI.

Par conséquent, si une ligne de cette sous-requête renvoie la valeur NULL, l'ensemble de l'opérateur NOT IN sera évalué à FALSE ou à NULL et aucun enregistrement ne sera renvoyé.

78
buckley

Si le planificateur d'exécution dit qu'ils sont identiques, ils sont identiques. Utilisez celui qui rendra votre intention plus évidente - dans ce cas, le second.

23
John Millikin

En fait, je crois que ce serait le plus rapide:

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null
14
James Curran

J'ai une table qui contient environ 120 000 enregistrements et je n'ai besoin de sélectionner que ceux qui n'existent pas (correspondant à une colonne varchar) dans quatre autres tables avec un nombre de lignes d'environ 1500, 4000, 40000, 200. Toutes les tables concernées ont un index unique. sur la colonne Varchar concernée.

NOT IN a pris environ 10 minutes, NOT EXISTS a pris 4 secondes.

J'ai une requête récursive qui pourrait avoir une section non syntonisée qui aurait pu contribuer aux 10 minutes, mais l'autre option prenant 4 secondes m'explique, au moins que NOT EXISTS est bien meilleur ou du moins que IN et EXISTS ne sont pas exactement les mêmes et méritent toujours un contrôle avant de continuer avec du code.

10
Yella Chalamala

Dans votre exemple spécifique, ils sont identiques, car l'optimiseur a déterminé que ce que vous essayez de faire est identique dans les deux exemples. Mais il est possible que l'optimiseur ne le fasse pas dans des exemples non triviaux, et dans ce cas, il y a des raisons de préférer l'une à l'autre à l'occasion.

NOT IN devrait être préféré si vous testez plusieurs lignes dans votre sélection externe. La sous-requête à l'intérieur de l'instruction NOT IN peut être évaluée au début de l'exécution et la table temporaire peut être vérifiée par rapport à chaque valeur de la sélection externe, plutôt que de réexécuter la sous-sélection à chaque fois comme cela est nécessaire avec l'option NOT EXISTS déclaration.

Si la sous-requête must doit être corrélée à la sélection externe, alors NOT EXISTS peut être préférable, car l'optimiseur peut détecter une simplification empêchant la création de tables temporaires pour exécuter la même fonction.

7

J'utilisais

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)

et a constaté que cela donnait des résultats erronés (Par faux, je veux dire pas de résultats). Comme il y avait un NULL dans TABLE2.Col1.

En modifiant la requête en

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)

m'a donné les bons résultats.

Depuis lors, j'ai commencé à utiliser NOT EXISTS partout.

5
ravish.hacker

Ils sont très similaires mais pas vraiment les mêmes.

En termes d’efficacité, j’ai trouvé l’énoncé jointure à gauche nulle plus efficace (il faut sélectionner une abondance de lignes).

4

Si l'optimiseur dit qu'ils sont identiques, alors considérez le facteur humain. Je préfère voir NOT EXISTS :)

1
onedaywhen