2008-10-06 238 views
406

哪些查詢速度更快?不在與不存在

NOT EXISTS:

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

或者不:

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

查詢執行計劃說,他們都做同樣的事情。如果是這種情況,那麼推薦的形式是?

這是基於NorthWind數據庫。

[編輯]

剛剛發現這是很有幫助的文章: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

我想我會堅持使用NOT EXISTS。

+1

您是否嘗試使用左連接的計劃,其中爲空? – Sebas 2012-06-17 20:37:15

+1

我不知道數據庫是否有所不同,但是在我對PostgreSQL的最新基準測試中,這個'NOT IN`查詢:`SELECT「A」。* FROM「A」WHERE「A」。「id」NOT IN(SELECT「B」。「 AID「FROM」B「WHERE」B「。」Uid「= 2)`的速度幾乎是這個'NOT EXISTS'的30倍:'SELECT'A」。* FROM「A」WHERE(NOT(EXISTS(SELECT 1 FROM「B」WHERE「B」。「user_id」= 2 AND「B」。「Aid」=「A」。「id」)))` – 2012-12-04 19:06:02

+0

可能重複[NOT EXISTS vs. NOT IN與。LEFT JOIN WHERE IS NULL?](http://stackoverflow.com/questions/2246772/whats-the-difference-between-not-exists-vs-not-in-vs-left-join-where-is-null ) – rcdmk 2016-05-14 17:50:23

回答

553

我總是默認爲NOT EXISTS

執行計劃可能是在目前相同的,但如果任一列在未來改變,以允許NULL S中NOT IN版本將需要做更多的工作(即使沒有NULL s爲實際存在的數據)和NOT IN的語義如果NULL s 目前不太可能是你想要的。

Products.ProductID[Order Details].ProductID都不允許NULLNOT IN將被視爲與以下查詢相同。

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

確切的計劃可能會有所不同,但對於我的示例數據,我會得到以下內容。

Neither NULL

相當普遍的誤解似乎是相關子查詢始終是「壞」相比,聯接。當它們強制執行嵌套循環計劃(子查詢逐行計算)時,它們當然可以是這樣,但此計劃包含反半連接邏輯運算符。反半連接不限於嵌套循環,但可以使用散列或合併(如本例中)連接。

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

如果[Order Details].ProductIDNULL -able查詢就變成

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) 

這樣做的原因是,正確的語義,如果[Order Details]包含任何NULLProductId s是不返回任何結果。請參閱額外的反半連接和行計數後臺來驗證添加到計劃中的這一點。

One NULL

如果Products.ProductID也發生了變化,成爲NULL -able查詢就變成

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) 

的原因,一個是因爲NULLProducts.ProductId不應該將結果返回除了如果NOT IN子查詢完全沒有返回結果(即[Order Details]表爲空)。在這種情況下,它應該。在我的樣本數據計劃中,這是通過添加另一個反半連接來實現的,如下所示。

Both NULL

這樣做的效果示於the blog post already linked by Buckley。在該示例中,邏輯讀取的數量從大約400增加到500,000。

此外,單個NULL可以將行數減少到零的事實使得基數估計非常困難。如果SQL Server假定會發生這種情況,但實際上數據中沒有NULL行,那麼其餘的執行計劃可能會更糟糕,如果這只是更大的查詢with inappropriate nested loops causing repeated execution of an expensive sub tree for example的一部分。

但是,這不是NOT INNULL列列上唯一可能的執行計劃。對於針對AdventureWorks2008數據庫的查詢,查詢爲This article shows another one

對於NOT INNOT NULL列或NOT EXISTS針對可爲空或不可空列的情況,它給出以下計劃。

Not EXists

當列更改爲NULL -able的NOT IN計劃現在看起來像

Not In - Null

它增加了一個額外的內部連接運營商的計劃。該設備是explained here。將Sales.SalesOrderDetail.ProductID = <correlated_product_id>上的前一個單相關索引搜索轉換爲每個外行的兩個搜索。另外一個是WHERE Sales.SalesOrderDetail.ProductID IS NULL

因爲這是在反半連接下,如果那個返回任何行,第二次搜索將不會發生。但是,如果Sales.SalesOrderDetail不包含任何NULLProductID s,它將使所需的查找操作數量加倍。

22

如果執行計劃員說他們是一樣的,他們是一樣的。使用任何一個會使你的意圖更明顯 - 在這種情況下,第二個。

0

這取決於..

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

不會是比較慢的是沒有太多限制什麼,看到查詢檢查,如果他們是關鍵的大小。EXISTS會在這種情況下優選。

但是,根據DBMS的優化器,這可能沒有什麼不同。

爲EXISTS時的一個例子是更好

SELECT x.col 
FROM big_table x 
WHERE EXISTS(SELECT key FROM really_big_table WHERE key = x.key); 
    AND id = very_limiting_criteria 
11

其實,我相信這將是最快的:

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

在你的具體的例子,他們都是一樣的,因爲優化已經想通在兩個例子中,你所要做的都是一樣的。但有可能的是,在非平凡的例子中,優化器可能不會這樣做,並且在那種情況下,有時有理由選擇其他的。

如果您要在外部選擇中測試多個行,則應該首選NOT IN。 NOT IN語句中的子查詢可以在執行開始時進行評估,並且可以根據外部select中的每個值檢查臨時表,而不是每次都重新運行子查詢,這與NOT EXISTS語句所需要的一樣。

如果子查詢必須與外選擇相關,那麼NOT EXISTS可以是優選的,因爲優化程序可能發現的簡化,以防止任何的臨時表的創建到執行相同的功能。

0

如果優化器表示它們是相同的,則考慮人爲因素。我更喜歡看不存在:)

62

另外請注意,NOT IN不等於NOT EXISTS當爲null。

這篇文章解釋了它很好

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

當子查詢返回甚至一個空,而不是將不會匹配任何 行。

其原因可以通過查看 NOT IN的實際含義來找到。

比方說,爲說明的目的,有4行中稱爲T的 表,有一個稱爲ID與值列1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t) 

相當於

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) 

我們進一步說AVal在ID = 4時爲NULL。因此,!= 比較返回UNKNOWN。 AND狀態 的邏輯真值表UNKNOWN和TRUE爲UNKNOWN,UNKNOWN和FALSE爲FALSE。有 可與UNKNOWN被AND'd產生結果TRUE

因此沒有價值,如果子查詢的任何行返回NULL,而不是整個IN 運營商將求值FALSE或NULL,並沒有記錄會是 返回

2

我用

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

,發現它是給錯誤的結果(通過錯了我的意思是沒有結果)。由於TABLE2.Col1中有一個NULL。

雖然改變了查詢

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

給了我正確的結果。

從那時起我開始在每個地方使用NOT EXISTS。

6

我有一張表,它有大約120,000條記錄,只需要選擇那些不存在(與一個varchar列匹配)在其他四個表中約1500,4000,400,200,200行的所有參與者表有關於Varchar列的唯一索引。

NOT IN花了約10分鐘,NOT EXISTS花了4秒。

我有可能有一些不轉動的部分可能已經到了10分鐘,貢獻了一個遞歸查詢,但其他選項以4秒解釋,至少對我來說,NOT EXISTS好得多或者至少INEXISTS不完全相同,並且在繼續使用代碼之前總是值得檢查。