Винаги по подразбиране е NOT EXISTS
.
Плановете за изпълнение може да са същите в момента, но ако някоя от колоните бъде променена в бъдеще, за да позволи NULL
е NOT IN
версията ще трябва да свърши повече работа (дори ако няма NULL
s всъщност присъстват в данните) и семантиката на NOT IN
ако NULL
с са настоящето е малко вероятно да са тези, които искате.
Когато нито 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)
Точният план може да варира, но за моите примерни данни получавам следното.
Доста често срещано погрешно схващане изглежда е, че корелираните подзаявки винаги са "лоши" в сравнение с присъединяванията. Те със сигурност могат да бъдат, когато налагат план за вложени цикли (подзаявка се оценява ред по ред), но този план включва логически оператор против полуприсъединяване. Анти-полусъединяванията не са ограничени до вложени цикли, но могат да използват и хеш или сливане (както в този пример).
/*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
-може заявката след това става
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)
Причината за това е, че правилната семантика if [Order Details]
съдържа произволен NULL
ProductId
s е да не връща резултати. Вижте допълнителната шпула за анти-полусъединяване и броене на редове, за да проверите това, което е добавено към плана.
Ако Products.ProductID
също се променя, за да стане NULL
-може заявката след това става
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]
масата е празна). В този случай трябва. В плана за моите примерни данни това е реализирано чрез добавяне на друго анти полусъединяване, както е по-долу.
Ефектът от това е показан в публикацията в блога, вече свързана от Бъкли. В примера броят на логическите четения се увеличава от около 400 на 500 000.
Освен това фактът, че единичен NULL
може да намали броя на редовете до нула прави оценката на мощността много трудна. Ако SQL Server предполага, че това ще се случи, но всъщност нямаше NULL
редове в данните, останалата част от плана за изпълнение може да бъде катастрофално по-лоша, ако това е само част от по-голяма заявка, с неподходящи вложени цикли, причиняващи многократно изпълнение на скъпо поддърво, например.
Това не е единственият възможен план за изпълнение за NOT IN
на NULL
-възможна колона обаче. Тази статия показва още една за заявка срещу AdventureWorks2008
база данни.
За NOT IN
на NOT NULL
колона или NOT EXISTS
срещу колона с нула или без нула дава следния план.
Когато колоната се промени на NULL
-възможност NOT IN
сега планът изглежда така
Той добавя допълнителен оператор за вътрешно присъединяване към плана. Това устройство е обяснено тук. Всичко е там, за да преобразува предишния единичен корелиран индекс за търсене в Sales.SalesOrderDetail.ProductID = <correlated_product_id>
до две търсения на външен ред. Допълнителният е на WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Тъй като това е под анти полусъединяване, ако това върне някакви редове, второто търсене няма да се случи. Ако обаче Sales.SalesOrderDetail
не съдържа NULL
ProductID
s това ще удвои броя на необходимите операции за търсене.