Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

PAS DANS vs PAS EXISTE

Par défaut, je suis toujours NOT EXISTS .

Les plans d'exécution peuvent être les mêmes pour le moment, mais si l'une ou l'autre colonne est modifiée à l'avenir pour autoriser NULL s le NOT IN la version devra faire plus de travail (même si aucun NULL s sont réellement présents dans les données) et la sémantique de NOT IN si NULL s sont présents sont peu susceptibles d'être ceux que vous voulez de toute façon.

Lorsque ni Products.ProductID ou [Order Details].ProductID autoriser NULL s le NOT IN sera traité de la même manière que 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 mes données d'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. Ils peuvent certainement l'être lorsqu'ils 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 join. Les anti-semi-jointures 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 -able 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 NULL ProductId s est de ne renvoyer aucun résultat. Consultez le spool supplémentaire anti-semi join et row count pour vérifier ce qui est ajouté au plan.

Si Products.ProductID est également modifié pour devenir NULL -able 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 NULL Products.ProductId ne doit pas être renvoyé dans les résultats sauf si le NOT IN la sous-requête devait ne renvoyer aucun résultat (c'est-à-dire le [Order Details] le tableau est vide). Auquel cas ça devrait. Dans le plan de mes exemples de données, cela est implémenté en ajoutant une autre jointure anti-semi comme ci-dessous.

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

De plus, 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 SQL Server suppose que cela se produira mais qu'en fait il n'y avait pas de NULL lignes 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 large, avec des boucles imbriquées inappropriées provoquant 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 pour un NOT IN sur un NULL -able colonne cependant. Cet article en montre un autre pour une requête sur 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 -able 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 la recherche d'index corrélé unique précédente sur Sales.SalesOrderDetail.ProductID = <correlated_product_id> à deux recherches par rangée extérieure. L'autre est sur WHERE Sales.SalesOrderDetail.ProductID IS NULL .

Comme il s'agit d'une jointure anti-semi si celle-ci renvoie des lignes, la deuxième recherche ne se produira pas. Cependant si Sales.SalesOrderDetail ne contient aucun NULL ProductId s cela doublera le nombre d'opérations de recherche requises.