Zawsze domyślnie NOT EXISTS
.
Plany wykonania mogą być w tej chwili takie same, ale jeśli którakolwiek kolumna zostanie zmieniona w przyszłości, aby umożliwić NULL
s NOT IN
wersja będzie musiała wykonać więcej pracy (nawet jeśli nie ma NULL
s są faktycznie obecne w danych) i semantyki NOT IN
jeśli NULL
są są teraźniejszość i tak prawdopodobnie nie będą tymi, których chcesz.
Gdy żaden Products.ProductID
lub [Order Details].ProductID
zezwól na NULL
s NOT IN
zostanie potraktowany identycznie jak następujące zapytanie.
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
Dokładny plan może się różnić, ale dla moich przykładowych danych otrzymuję następujące.
Wydaje się, że dość powszechnym błędnym przekonaniem jest to, że skorelowane zapytania podrzędne są zawsze „złe” w porównaniu do złączeń. Z pewnością mogą być, gdy wymuszają plan zagnieżdżonych pętli (podkwerenda oceniana wiersz po wierszu), ale ten plan zawiera operator logiczny zapobiegający sprzężeniu semi. Anti semijoin nie ogranicza się do zagnieżdżonych pętli, ale może również używać skrótów lub łączeń scalających (jak w tym przykładzie).
/*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
Jeśli [Order Details].ProductID
jest NULL
-able zapytanie staje się wtedy
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)
Powodem tego jest to, że poprawna semantyka, jeśli [Order Details]
zawiera dowolny NULL
ProductId
s nie zwraca żadnych wyników. Zobacz dodatkową szpulę anty-semi-join i licznika rzędów, aby sprawdzić, czy jest to dodane do planu.
Jeśli Products.ProductID
zmienia się również na NULL
-able zapytanie staje się wtedy
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)
Powodem tego jest to, że NULL
Products.ProductId
nie powinny być zwracane w wynikach z wyjątkiem jeśli NOT IN
zapytanie podrzędne miało nie zwracać żadnych wyników (np. [Order Details]
tabela jest pusta). W takim razie powinno. W planie dla moich przykładowych danych jest to zaimplementowane przez dodanie kolejnego anty-semijoin, jak poniżej.
Efekt tego widać w poście na blogu, do którego już link Buckley. W tym przykładzie liczba odczytów logicznych wzrosła z około 400 do 500 000.
Dodatkowo fakt, że pojedynczy NULL
może zmniejszyć liczbę wierszy do zera, co bardzo utrudnia oszacowanie kardynalności. Jeśli SQL Server zakłada, że tak się stanie, ale w rzeczywistości nie było NULL
wierszy w danych, reszta planu wykonania może być katastrofalnie gorsza, jeśli jest to tylko część większego zapytania, z nieodpowiednimi pętlami zagnieżdżonymi powodującymi powtarzające się wykonywanie kosztownego poddrzewa.
To nie jedyny możliwy plan wykonania dla NOT IN
na NULL
-możliwa kolumna jednak. W tym artykule przedstawiono kolejne zapytanie dotyczące AdventureWorks2008
baza danych.
Dla NOT IN
na NOT NULL
kolumna lub NOT EXISTS
w stosunku do kolumny z wartością null lub nie do wartości null daje następujący plan.
Gdy kolumna zmieni się na NULL
-może NOT IN
plan wygląda teraz tak
Dodaje do planu dodatkowy operator sprzężenia wewnętrznego. To urządzenie zostało wyjaśnione tutaj. Wszystko po to, aby przekonwertować poprzednie pojedyncze wyszukiwanie indeksu skorelowanego na Sales.SalesOrderDetail.ProductID = <correlated_product_id>
do dwóch wyszukiwań w zewnętrznym rzędzie. Dodatkowa znajduje się na WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Ponieważ jest to pod anty semi join, jeśli zwróci jakiekolwiek wiersze, drugie wyszukiwanie nie nastąpi. Jeśli jednak Sales.SalesOrderDetail
nie zawiera żadnego NULL
ProductId
s podwoi liczbę wymaganych operacji wyszukiwania.