2009-08-31 10 views
11

Was ist der effizienteste Weg, um eine select-Anweisung ähnlich der folgenden zu schreiben.Was ist der effizienteste Weg, um eine Select-Anweisung mit einer "nicht in" Unterabfrage zu schreiben?

Das Wesentliche ist, Sie wollen die Datensätze aus einer Tabelle, wenn das Element nicht in einer anderen Tabelle ist.

+5

Der beste Weg ist es, die verschiedenen Ansätze zu versuchen und die Ausführungspläne zu untersuchen. – pjp

+0

In meiner Situation SQL Server 2000, angesichts der Indizes für die fraglichen Tabellen war die "Join" -Abfrage die schnellste. SELECT * FROM Bestellungen o LEFT JOIN HeldOrder h on o.Order_ID = h.Order_ID und h.Order_ID ist null – Stimy

Antwort

7

„Am effizientesten“ Probieren Sie verschiedene sein wird Größen abhängig von Tabellen , Indizes und so weiter. Mit anderen Worten, es wird je nach dem spezifischen Fall, den Sie verwenden, unterschiedlich sein.

Es gibt drei Möglichkeiten, die ich normalerweise verwende, um zu erreichen, was Sie wollen, abhängig von der Situation.

1. Ihr Beispiel funktioniert gut, wenn Orders.order_id indiziert ist, und HeldOrders ist ziemlich klein.

2. Eine andere Methode ist die „korrelierte Unterabfrage“, das ist eine leichte Variation von dem, was Sie haben ...

SELECT * 
FROM Orders o 
WHERE Orders.Order_ID not in (Select Order_ID 
           FROM HeldOrders h 
           where h.order_id = o.order_id) 

Notiere die Zugabe der Where-Klausel. Dies funktioniert besser, wenn HeldOrders eine große Anzahl von Zeilen hat. Order_ID muss in beiden Tabellen indiziert werden.

3. Eine andere Methode, die ich manchmal verwenden wird Outer-Joins links ...

SELECT * 
FROM Orders o 
left outer join HeldOrders h on h.order_id = o.order_id 
where h.order_id is null 

Wenn die linke Outer-Join verwendet, h.order_id einen Wert haben wird es passend o.order_id, wenn es eine passende Zeile. Wenn keine übereinstimmende Zeile vorhanden ist, wird h.order_id NULL sein. Indem Sie nach den NULL-Werten in der where-Klausel suchen, können Sie nach allem filtern, das keine Übereinstimmung hat.

Jede dieser Varianten kann in verschiedenen Szenarien mehr oder weniger effizient arbeiten.

+0

'@ Dave': Warum benutzt du' NOT IN' anstelle von 'NOT EXISTS' in Methode' 2'? – Quassnoi

+1

@Quassnoi: Ehrlich gesagt, wahrscheinlich eine schlechte Angewohnheit. Nachdem ich deine Antwort oben gelesen habe, plane ich, NOT EXISTS zu verwenden. –

+0

Option 3 funktionierte am besten in meinem Szenario (SQL Server 2000 mit meinen Tabellenindizes). Ich denke, die beste Antwort ist es, eine Reihe von Methoden zu testen. – Stimy

4

Sie können LEFT OUTER JOIN verwenden und auf der rechten Tabelle nach NULL suchen.

SELECT O1.* 
FROM Orders O1 
LEFT OUTER JOIN HeldOrders O2 
ON O1.Order_ID = O2.Order_Id 
WHERE O2.Order_Id IS NULL 
+1

Dies ist * weit * davon entfernt, eine sehr effiziente Methode zu sein. – Quassnoi

+0

Dies ist nicht notwendigerweise die effizienteste Methode. –

+0

Es ist jedoch wesentlich effizienter als eine Unterabfrage - zumindest wird es nur einmal gegen die zweite Tabelle ausgeführt, statt einmal/Zeile. – SqlRyan

19

Für den Anfang, ein Link zu einem alten Artikel in meinem Blog darüber, wie NOT IN Prädikat arbeitet in SQL Server (und in anderen Systemen auch):


Sie können es wie folgt umschreiben:

SELECT * 
FROM Orders o 
WHERE NOT EXISTS 
     (
     SELECT NULL 
     FROM HeldOrders ho 
     WHERE ho.OrderID = o.OrderID 
     ) 

jedoch behandeln die meisten Datenbanken diese Abfragen gleich. Diese beiden Abfragen verwenden eine Art von ANTI JOIN.

Dies ist nützlich für SQL Server, wenn Sie zwei oder mehr Spalten überprüfen möchten, da SQL Server diese Syntax nicht unterstützt:

SELECT * 
FROM Orders o 
WHERE (col1, col2) NOT IN 
     (
     SELECT col1, col2 
     FROM HeldOrders ho 
     ) 

Beachten Sie jedoch, dass NOT IN schwierig sein kann auf die Art und Weise da es NULL behandelt Werte.

Wenn Held.Orders NULL-Werte zulässt, werden keine Datensätze gefunden und die Unterabfrage gibt nur eine einzige NULL, wird die ganze Abfrage zurückgeben nichts (beide IN und NOT IN wird NULL in diesem Fall bewerten).

Betrachten Sie diese Daten:

Orders: 

OrderID 
--- 
1 

HeldOrders: 

OrderID 
--- 
2 
NULL 

Diese Abfrage:

SELECT * 
FROM Orders o 
WHERE OrderID NOT IN 
     (
     SELECT OrderID 
     FROM HeldOrders ho 
     ) 

kehrt nichts, das ist wahrscheinlich nicht das, was man erwarten würde.

jedoch diese:

SELECT * 
FROM Orders o 
WHERE NOT EXISTS 
     (
     SELECT NULL 
     FROM HeldOrders ho 
     WHERE ho.OrderID = o.OrderID 
     ) 

wird die Zeile mit OrderID = 1 zurück.

Beachten Sie, dass LEFT JOIN von anderen vorgeschlagene Lösungen bei weitem nicht die effizienteste Lösung ist.

Diese Abfrage:

SELECT * 
FROM Orders o 
LEFT JOIN 
     HeldOrders ho 
ON  ho.OrderID = o.OrderID 
WHERE ho.OrderID IS NULL 

wird eine Filterbedingung verwenden, die alle passende Zeilen müssen auszuwerten und auszufiltern, die

Ein ANTI JOIN Verfahren verwendet sowohl IN und EXISTS Numerius werden kann, muss nur sicherstellen, dass ein Datensatz nicht existiert einmal pro Zeile in Orders, so wird es zuerst alle möglichen Dubletten zu beseitigen:

  • NESTED LOOPS ANTI JOIN und MERGE ANTI JOIN wird nur die Duplikate überspringen, wenn HeldOrders Auswertung.
  • Ein HASH ANTI JOIN wird Dubletten beim Erstellen der Hash-Tabelle beseitigen.
+1

Zum ersten Mal habe ich eine korrelierte Unterabfrage gesehen, die tatsächlich eine korrelierte Unterabfrage sein musste, die ich in weniger als 5 Minuten erreichen konnte. Ich wünschte, ich hätte diesen Trick vor Jahren erkannt. –

+0

'@Philip Kelley': welcher Trick genau? – Quassnoi

+0

Was meinen Sie in diesem Abschnitt: "Dies ist nützlich für SQL Server, wenn Sie zwei oder mehr Spalten überprüfen möchten, da SQL Server diese Syntax nicht unterstützt:". Sagen Sie, dass dies nicht für SQL Server gilt? Vermissen Sie ein "nicht"? – Stimy

1

Ich bin mir nicht sicher, was die effizienteste ist, aber andere Optionen sind:

1. Use EXISTS 

SELECT * 
FROM ORDERS O 
WHERE NOT EXISTS (SELECT 1 
        FROM HeldOrders HO 
        WHERE O.Order_ID = HO.OrderID) 

2. Use EXCEPT 

SELECT O.Order_ID 
FROM ORDERS O 
EXCEPT 
SELECT HO.Order_ID 
FROM HeldOrders 
0

SELECT * 
FROM Orders 
LEFT JOIN HeldOrders 
ON HeldOrders.Order_ID = Orders.Order_ID 
WHERE HeldOrders.Order_ID IS NULL 
Verwandte Themen