我總是默認爲NOT EXISTS
。
執行計劃可能是在目前相同的,但如果任一列在未來改變,以允許NULL
S中NOT IN
版本將需要做更多的工作(即使沒有NULL
s爲實際存在的數據)和NOT IN
的語義如果NULL
s 是目前不太可能是你想要的。
當Products.ProductID
或[Order Details].ProductID
都不允許NULL
時NOT IN
將被視爲與以下查詢相同。
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
確切的計劃可能會有所不同,但對於我的示例數據,我會得到以下內容。
![Neither NULL](https://i.stack.imgur.com/lCTsG.png)
相當普遍的誤解似乎是相關子查詢始終是「壞」相比,聯接。當它們強制執行嵌套循環計劃(子查詢逐行計算)時,它們當然可以是這樣,但此計劃包含反半連接邏輯運算符。反半連接不限於嵌套循環,但可以使用散列或合併(如本例中)連接。
/*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].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)
這樣做的原因是,正確的語義,如果[Order Details]
包含任何NULL
ProductId
s是不返回任何結果。請參閱額外的反半連接和行計數後臺來驗證添加到計劃中的這一點。
![One NULL](https://i.stack.imgur.com/mPYhd.png)
如果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)
的原因,一個是因爲NULL
Products.ProductId
不應該將結果返回除了如果NOT IN
子查詢完全沒有返回結果(即[Order Details]
表爲空)。在這種情況下,它應該。在我的樣本數據計劃中,這是通過添加另一個反半連接來實現的,如下所示。
![Both NULL](https://i.stack.imgur.com/8XAh1.png)
這樣做的效果示於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 IN
在NULL
列列上唯一可能的執行計劃。對於針對AdventureWorks2008
數據庫的查詢,查詢爲This article shows another one。
對於NOT IN
在NOT NULL
列或NOT EXISTS
針對可爲空或不可空列的情況,它給出以下計劃。
![Not EXists](https://i.stack.imgur.com/nahUD.png)
當列更改爲NULL
-able的NOT IN
計劃現在看起來像
![Not In - Null](https://i.stack.imgur.com/8o9PQ.png)
它增加了一個額外的內部連接運營商的計劃。該設備是explained here。將Sales.SalesOrderDetail.ProductID = <correlated_product_id>
上的前一個單相關索引搜索轉換爲每個外行的兩個搜索。另外一個是WHERE Sales.SalesOrderDetail.ProductID IS NULL
。
因爲這是在反半連接下,如果那個返回任何行,第二次搜索將不會發生。但是,如果Sales.SalesOrderDetail
不包含任何NULL
ProductID
s,它將使所需的查找操作數量加倍。
您是否嘗試使用左連接的計劃,其中爲空? – Sebas 2012-06-17 20:37:15
我不知道數據庫是否有所不同,但是在我對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
可能重複[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