NICHT IN vs NICHT EXISTIERT

538

Welche dieser Abfragen ist die schnellere?

EXISTIERT NICHT:

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

Oder NICHT IN:

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

Der Ausführungsplan für Abfragen besagt, dass beide dasselbe tun. Wenn dies der Fall ist, welches ist das empfohlene Formular?

Dies basiert auf der NorthWind-Datenbank.

[Bearbeiten]

Ich habe gerade diesen hilfreichen Artikel gefunden: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Ich denke, ich bleibe bei NOT EXISTS.

ilitirit
quelle
3
Haben Sie den Plan mit einem linken Join versucht, bei dem null ist?
Sebas
1
NOT IN und NOT EXISTS sind nicht identisch. Schauen Sie sich diesen Link für den Unterschied zwischen ihnen an: weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
Ameya Gokhale
2
Ich frage mich, ob sich die Datenbanken unterscheiden, aber in meinem neuesten Benchmark gegen PostgreSQL ist diese NOT INAbfrage: SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)fast 30-mal so schnell wie diese NOT EXISTS:SELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
Phương Nguyễn
1
@rcdmk Hast du das Datum auf den Fragen überprüft?
ilitirit

Antworten:

693

Ich benutze immer standardmäßig NOT EXISTS.

Die Ausführungspläne können gleich im Moment, aber wenn entweder Spalte wird in Zukunft geändert , damit NULLs die NOT INVersion benötigt mehr Arbeit zu tun (auch wenn keine NULLs in den Daten tatsächlich vorhanden sind) und die Semantik , NOT INwenn NULLs sind vorhanden Es ist unwahrscheinlich, dass es die sind, die Sie sowieso wollen.

Wenn weder Products.ProductIDoder [Order Details].ProductIDerlauben NULLs das NOT INwird identisch mit der folgenden Abfrage behandelt.

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.

Weder NULL

Ein ziemlich häufiges 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 für verschachtelte Schleifen erzwingen (Unterabfrage wird zeilenweise 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].ProductIDist NULL-able die Abfrage dann wird

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, dass die korrekte Semantik, wenn sie s [Order Details]enthält, darin NULL ProductIdbesteht, keine Ergebnisse zurückzugeben. Überprüfen Sie die zusätzliche Spule für Anti-Semi-Join und Zeilenanzahl, um zu überprüfen, ob diese dem Plan hinzugefügt wurde.

Ein NULL

Wenn Products.ProductIDauch geändert wird, um NULL-able zu werden, 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, dass a NULL Products.ProductIdin den Ergebnissen nicht zurückgegeben werden sollte, es sei denn, die NOT INUnterabfrage würde überhaupt keine Ergebnisse zurückgeben (dh die [Order Details]Tabelle ist leer). In welchem ​​Fall sollte es. Im Plan für meine Beispieldaten wird dies durch Hinzufügen eines weiteren Anti-Semi-Joins wie unten implementiert.

Beide NULL

Der Effekt davon wird in dem Blog-Beitrag gezeigt, der bereits von Buckley verlinkt wurde . Im dortigen Beispiel steigt die Anzahl der logischen Lesevorgänge von rund 400 auf 500.000.

Zusätzlich macht die Tatsache, dass eine einzelne NULLdie Zeilenanzahl auf Null reduzieren kann, die Kardinalitätsschätzung sehr schwierig. Wenn SQL Server davon ausgeht, dass dies passieren wird, aber tatsächlich keine NULLZeilen in den Daten vorhanden sind, kann der Rest des Ausführungsplans katastrophal schlechter sein, wenn dies nur Teil einer größeren Abfrage ist und unangemessene verschachtelte Schleifen die wiederholte Ausführung eines teuren Unterprogramms verursachen Baum zum Beispiel .

Dies ist jedoch nicht der einzig mögliche Ausführungsplan für eine Spalte NOT INmit einem NULLWert. Dieser Artikel zeigt einen anderen für eine Abfrage für die AdventureWorks2008Datenbank.

Für die Spalte NOT INauf einer NOT NULLSpalte oder NOT EXISTSgegen eine nullbare oder nicht nullbare Spalte wird der folgende Plan angegeben.

Existiert nicht

Wenn die Spalte in NULL-able geändert wird, sieht der NOT INPlan jetzt so aus

Nicht In - Null

Es fügt dem Plan einen zusätzlichen inneren Verknüpfungsoperator hinzu. Diese Vorrichtung wird hier erklärt . Es ist alles vorhanden, um die vorherige Suche mit einem einzelnen korrelierten Index Sales.SalesOrderDetail.ProductID = <correlated_product_id>in zwei Suchvorgänge pro äußerer Zeile umzuwandeln . Der zusätzliche ist eingeschaltetWHERE Sales.SalesOrderDetail.ProductID IS NULL .

Da dies unter einem Anti-Semi-Join liegt, wird die zweite Suche nicht durchgeführt, wenn dieser Zeilen zurückgibt. Wenn Sales.SalesOrderDetailjedoch keine NULL ProductIDs enthalten sind , wird die Anzahl der erforderlichen Suchvorgänge verdoppelt.

Martin Smith
quelle
4
Darf ich fragen, wie Sie das Profildiagramm wie gezeigt erhalten?
xis
5
@xis Dies sind Ausführungspläne, die im SQL Sentry Plan Explorer geöffnet wurden. Sie können Ausführungspläne auch grafisch in SSMS anzeigen.
Martin Smith
Ich schätze dies nur aus dem Grund, dass: so NOT EXISTSfunktioniert, wie ich es erwartet NOT INhabe (was es nicht tut).
Levininja
Mit NOT EXISTS versuche ich, SELECT 1 wie NOT EXISTS (SELECT 1 FROM sometable WHERE etwas) zu verwenden, damit die Datenbank keine Spalten von der Festplatte zurückgeben muss. Es ist wahrscheinlich eine gute Idee, EXPLAIN zu verwenden, um festzustellen, ob dies in Ihrem Fall einen Unterschied macht.
Mayur Patel
4
@Mayur In SQL Server ist dies nicht erforderlich. stackoverflow.com/questions/1597442/…
Martin Smith
84

Beachten Sie auch, dass NOT IN nicht gleich NOT EXISTS ist, wenn es um Null geht.

Dieser Beitrag erklärt es sehr gut

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

Wenn die Unterabfrage auch nur eine Null zurückgibt, stimmt NOT IN nicht mit Zeilen überein.

Der Grund dafür kann anhand der Details gefunden werden, was die NOT IN-Operation tatsächlich bedeutet.

Nehmen wir an, zur Veranschaulichung, dass die Tabelle 4 Zeilen mit dem Namen t enthält, gibt es eine Spalte mit dem Namen ID mit den Werten 1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t)

ist äquivalent zu

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)

Nehmen wir weiter an, dass AVal NULL ist, wobei ID = 4. Daher gibt der Vergleich! = UNBEKANNT zurück. Die logische Wahrheitstabelle für AND besagt, dass UNBEKANNT und WAHR UNBEKANNT, UNBEKANNT und FALSCH FALSCH ist. Es gibt keinen Wert, der mit UNKNOWN UND-verknüpft werden kann, um das Ergebnis TRUE zu erzeugen

Wenn eine Zeile dieser Unterabfrage NULL zurückgibt, wird der gesamte NOT IN-Operator entweder mit FALSE oder NULL ausgewertet, und es werden keine Datensätze zurückgegeben

Buckley
quelle
24

Wenn der Ausführungsplaner sagt, dass sie gleich sind, sind sie gleich. Verwenden Sie diejenige, die Ihre Absicht deutlicher macht - in diesem Fall die zweite.

John Millikin
quelle
3
Die Ausführungsplanungszeit kann gleich sein, aber die Ausführungsergebnisse können unterschiedlich sein, sodass es einen Unterschied gibt. NOT IN führt zu unerwarteten Ergebnissen, wenn Ihr Datensatz NULL enthält (siehe Antwort von Buckley). Verwenden Sie am besten NICHT EXISTS als Standard.
Nanonerd
15

Eigentlich glaube ich, dass dies das schnellste wäre:

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null
James Curran
quelle
2
Ist möglicherweise nicht der schnellste, wenn der Optimierer seine Arbeit erledigt, aber sicherlich schneller, wenn dies nicht der Fall ist.
Cade Roux
2
Möglicherweise hat er auch seine Abfrage für diesen Beitrag vereinfacht
Kip
1
Zustimmen Der linke äußere Join ist oft schneller als eine Unterabfrage.
HLGEM
7
@HLGEM Nicht einverstanden. Nach meiner Erfahrung ist der beste Fall für LOJ, dass sie identisch sind und SQL Server den LOJ in einen Anti-Semi-Join konvertiert. Im schlimmsten Fall verbindet SQL Server LEFT JOINs alles und filtert die NULL-Werte heraus. Danach kann es viel ineffizienter sein. Beispiel dafür am Ende dieses Artikels
Martin Smith
12

Ich habe eine Tabelle mit ungefähr 120.000 Datensätzen und muss nur diejenigen auswählen, die in vier anderen Tabellen mit einer Anzahl von Zeilen von ca. 1500, 4000, 40000, 200 nicht vorhanden sind (mit einer Varchar-Spalte abgeglichen). Alle beteiligten Tabellen haben einen eindeutigen Index auf die betroffenen Varchar Spalte.

NOT IN dauerte etwa 10 Minuten, NOT EXISTS dauerte 4 Sekunden.

Ich habe eine rekursive Abfrage, die möglicherweise einen nicht abgestimmten Abschnitt hatte, der möglicherweise zu den 10 Minuten beigetragen hat, aber die andere Option, die 4 Sekunden dauert, erklärt, zumindest für mich, dass dies NOT EXISTSweitaus besser ist oder zumindest das INund EXISTSnicht genau das gleiche und immer einen Wert hat Überprüfen Sie dies, bevor Sie mit dem Code fortfahren.

Yella Chalamala
quelle
8

In Ihrem speziellen Beispiel sind sie gleich, da der Optimierer herausgefunden hat, dass das, was Sie versuchen, in beiden Beispielen gleich ist. Es ist jedoch möglich, dass der Optimierer dies in nicht trivialen Beispielen nicht tut, und in diesem Fall gibt es Gründe, gelegentlich einen anderen vorzuziehen.

NOT INsollte bevorzugt werden, wenn Sie mehrere Zeilen in Ihrer äußeren Auswahl testen. Die Unterabfrage in der NOT INAnweisung kann zu Beginn der Ausführung ausgewertet werden, und die temporäre Tabelle kann mit jedem Wert in der äußeren Auswahl verglichen werden, anstatt die Unterauswahl jedes Mal erneut auszuführen, wie dies für die NOT EXISTSAnweisung erforderlich wäre .

Wenn die Unterabfrage mit der äußeren Auswahl korreliert werden muss , ist dies NOT EXISTSmöglicherweise vorzuziehen, da der Optimierer möglicherweise eine Vereinfachung feststellt, die die Erstellung temporärer Tabellen verhindert, um dieselbe Funktion auszuführen.

Jeffrey L Whitledge
quelle
6

Ich habe benutzt

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

und stellte fest, dass es falsche Ergebnisse gab (mit falsch meine ich keine Ergebnisse). Da gab es eine NULL in TABLE2.Col1.

Beim Ändern der Abfrage in

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

gab mir die richtigen Ergebnisse.

Seitdem benutze ich NOT EXISTS überall.

ravish.hacker
quelle
5

Sie sind sehr ähnlich, aber nicht wirklich gleich.

In Bezug auf die Effizienz habe ich festgestellt, dass der linke Join eine Null- Anweisung ist, die effizienter ist (wenn also eine Fülle von Zeilen ausgewählt werden soll).

Onga Leo-Yoda Vellem
quelle
2

Wenn der Optimierer angibt, dass sie gleich sind, berücksichtigen Sie den menschlichen Faktor. Ich sehe lieber NICHT EXISTS :)

eines Tages, wenn
quelle
1

Dies ist eine sehr gute Frage, deshalb habe ich beschlossen, einen sehr detaillierten Artikel zu diesem Thema in meinem Blog zu schreiben .

Datenbanktabellenmodell

Nehmen wir an, wir haben die folgenden zwei Tabellen in unserer Datenbank, die eine Eins-zu-Viele-Tabellenbeziehung bilden.

SQL EXISTS-Tabellen

Die studentTabelle ist die übergeordnete und die student_gradeuntergeordnete Tabelle, da sie eine Fremdschlüsselspalte student_id enthält, die auf die ID-Primärschlüsselspalte in der Schülertabelle verweist.

Das student tableenthält die folgenden zwei Datensätze:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

In der student_gradeTabelle sind die Noten aufgeführt, die die Schüler erhalten haben:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL EXISTIERT

Nehmen wir an, wir möchten alle Schüler erreichen, die im Mathematikunterricht eine Note von 10 erhalten haben.

Wenn wir nur an der Studentenkennung interessiert sind, können wir eine Abfrage wie diese ausführen:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Die Anwendung ist jedoch daran interessiert, den vollständigen Namen von a anzuzeigen student, nicht nur den Bezeichner. Daher benötigen wir auch Informationen aus der studentTabelle.

Um die studentDatensätze mit einer Note von 10 in Mathematik zu filtern , können Sie den EXISTS SQL-Operator wie folgt verwenden:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Wenn Sie die obige Abfrage ausführen, sehen Sie, dass nur die Alice-Zeile ausgewählt ist:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Die äußere Abfrage wählt die studentZeilenspalten aus, die an den Client zurückgegeben werden sollen. Die WHERE-Klausel verwendet jedoch den EXISTS-Operator mit einer zugehörigen inneren Unterabfrage.

Der EXISTS-Operator gibt true zurück, wenn die Unterabfrage mindestens einen Datensatz zurückgibt, und false, wenn keine Zeile ausgewählt ist. Das Datenbankmodul muss die Unterabfrage nicht vollständig ausführen. Wenn ein einzelner Datensatz übereinstimmt, gibt der EXISTS-Operator true zurück und die zugehörige andere Abfragezeile wird ausgewählt.

Die innere Unterabfrage ist korreliert, da die Spalte student_id der student_gradeTabelle mit der Spalte id der äußeren Schülertabelle abgeglichen wird.

SQL existiert nicht

Angenommen, wir möchten alle Schüler auswählen, deren Note nicht niedriger als 9 ist. Dazu können wir NOT EXISTS verwenden, wodurch die Logik des EXISTS-Operators negiert wird.

Daher gibt der Operator NOT EXISTS true zurück, wenn die zugrunde liegende Unterabfrage keinen Datensatz zurückgibt. Wenn jedoch ein einzelner Datensatz mit der inneren Unterabfrage übereinstimmt, gibt der Operator NOT EXISTS false zurück und die Ausführung der Unterabfrage kann gestoppt werden.

Um alle Schülerdatensätze, denen kein student_grade zugeordnet ist, mit einem Wert unter 9 abzugleichen, können Sie die folgende SQL-Abfrage ausführen:

SELECT
    id, first_name, last_name
FROM
    student
WHERE NOT EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade < 9
)
ORDER BY id

Wenn Sie die obige Abfrage ausführen, sehen Sie, dass nur der Alice-Datensatz übereinstimmt:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Der Vorteil der Verwendung der SQL EXISTS- und NOT EXISTS-Operatoren besteht also darin, dass die Ausführung der inneren Unterabfrage gestoppt werden kann, solange ein übereinstimmender Datensatz gefunden wird.

Vlad Mihalcea
quelle
-1

Es hängt davon ab, ob..

SELECT x.col
FROM big_table x
WHERE x.key IN( SELECT key FROM really_big_table );

wäre nicht relativ langsam, das ist nicht viel, um die Größe der Abfrage zu begrenzen, um zu sehen, ob sie eingegeben werden. EXISTS wäre in diesem Fall vorzuziehen.

Abhängig vom Optimierer des DBMS kann dies jedoch nicht anders sein.

Als Beispiel dafür, wann EXISTS besser ist

SELECT x.col
FROM big_table x
WHERE EXISTS( SELECT key FROM really_big_table WHERE key = x.key);
  AND id = very_limiting_criteria
Greg Ogle
quelle
1
INund EXISTS erhalten Sie den gleichen Plan in SQL Server . Die Frage ist sowieso NOT INgegen vs. NOT EXISTS
Martin Smith