Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

NICHT IN vs NICHT VORHANDEN

Ich verwende standardmäßig immer NOT EXISTS .

Die Ausführungspläne können im Moment dieselben sein, aber wenn eine der Spalten in der Zukunft geändert wird, um NULL zuzulassen s NOT IN Version mehr Arbeit leisten muss (selbst wenn kein NULL s tatsächlich in den Daten vorhanden sind) und die Semantik von NOT IN wenn NULL s sind anwesend sind wahrscheinlich sowieso nicht die, die Sie wollen.

Wenn weder Products.ProductID oder [Order Details].ProductID NULL zulassen s NOT IN wird genauso behandelt wie die folgende Abfrage.

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

Der genaue Plan kann variieren, aber für meine Beispieldaten erhalte ich Folgendes.

Ein ziemlich weit verbreitetes Missverständnis scheint zu sein, dass korrelierte Unterabfragen im Vergleich zu Joins immer "schlecht" sind. Sie können es sicherlich sein, wenn sie einen Plan mit verschachtelten Schleifen erzwingen (Unterabfrage wird Zeile für Zeile ausgewertet), aber dieser Plan enthält einen logischen Anti-Semi-Join-Operator. Anti-Semi-Joins sind nicht auf verschachtelte Schleifen beschränkt, sondern können auch Hash- oder Merge-Joins (wie in diesem Beispiel) verwenden.

/*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 

Wenn [Order Details].ProductID ist NULL -fähig wird die Abfrage dann

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) 

Der Grund dafür ist die korrekte Semantik von [Order Details] enthält beliebiges NULL ProductId s soll keine Ergebnisse zurückgeben. Sehen Sie sich den zusätzlichen Anti-Semi-Join- und Zeilenzähl-Spool an, um zu überprüfen, ob dies dem Plan hinzugefügt wurde.

Wenn Products.ProductID wird ebenfalls in NULL geändert -fähig wird die Abfrage dann

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) 

Der Grund dafür ist ein NULL Products.ProductId sollte außer nicht in den Ergebnissen zurückgegeben werden wenn der NOT IN Unterabfrage überhaupt keine Ergebnisse zurückgeben (d. h. die [Order Details] Tisch ist leer). In diesem Fall sollte es. Im Plan für meine Beispieldaten wird dies durch Hinzufügen eines weiteren Anti-Semi-Joins wie unten implementiert.

Wie sich das auswirkt, zeigt der von Buckley bereits verlinkte Blogbeitrag. Im dortigen Beispiel erhöht sich die Anzahl der logischen Lesezugriffe von etwa 400 auf 500.000.

Hinzu kommt die Tatsache, dass ein einzelnes NULL die Zeilenanzahl auf null reduzieren kann, macht die Kardinalitätsschätzung sehr schwierig. Wenn SQL Server davon ausgeht, dass dies passieren wird, aber tatsächlich kein NULL vorhanden war Zeilen in den Daten kann der Rest des Ausführungsplans katastrophal schlechter sein, wenn dies nur Teil einer größeren Abfrage ist, mit unangemessenen verschachtelten Schleifen, die beispielsweise die wiederholte Ausführung eines teuren Teilbaums verursachen.

Dies ist nicht der einzig mögliche Ausführungsplan für ein NOT IN auf NULL -fähigen Spalte jedoch. Dieser Artikel zeigt einen anderen für eine Abfrage gegen AdventureWorks2008 Datenbank.

Für NOT IN auf einem NOT NULL Spalte oder NOT EXISTS gegen eine nullable oder non-nullable Spalte gibt es den folgenden Plan.

Wenn sich die Spalte zu NULL ändert -fähig NOT IN Plan sieht jetzt so aus

Es fügt dem Plan einen zusätzlichen inneren Join-Operator hinzu. Dieser Apparat wird hier erklärt. Es ist alles vorhanden, um die vorherige einzelne korrelierte Indexsuche auf Sales.SalesOrderDetail.ProductID = <correlated_product_id> umzuwandeln auf zwei Suchvorgänge pro äußere Reihe. Die zusätzliche ist auf WHERE Sales.SalesOrderDetail.ProductID IS NULL .

Da dies unter einem Anti-Semi-Join erfolgt, wenn dieser Zeilen zurückgibt, wird die zweite Suche nicht ausgeführt. Wenn jedoch Sales.SalesOrderDetail kein NULL enthält ProductID s Dadurch wird die Anzahl der erforderlichen Suchvorgänge verdoppelt.