Vždy mám výchozí hodnotu NOT EXISTS
.
Prováděcí plány mohou být v tuto chvíli stejné, ale pokud bude některý sloupec v budoucnu změněn tak, aby umožňoval NULL
je NOT IN
verze bude muset udělat více práce (i když žádná NULL
s jsou skutečně přítomné v datech) a sémantika NOT IN
pokud NULL
s jsou přítomné pravděpodobně stejně nebudou ty, které chcete.
Když ani Products.ProductID
nebo [Order Details].ProductID
povolit NULL
je NOT IN
bude zpracováno stejně jako následující dotaz.
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
Přesný plán se může lišit, ale pro můj příklad dat dostanu následující.
Zdá se, že přiměřeně běžnou mylnou představou je, že korelované dílčí dotazy jsou vždy „špatné“ ve srovnání se spojeními. Určitě mohou být, když vynutí plán vnořených smyček (dílčí dotaz hodnocený řádek po řádku), ale tento plán obsahuje logický operátor proti semi spojení. Anti semi spojení nejsou omezena na vnořené smyčky, ale mohou také používat hash nebo merge (jako v tomto příkladu).
/*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
Pokud [Order Details].ProductID
je NULL
-able se pak dotaz stane
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)
Důvodem je správná sémantika if [Order Details]
obsahuje jakékoli NULL
ProductId
s je nevracet žádné výsledky. Podívejte se na extra anti semi spojení a cívku počtu řádků, abyste si ověřili, že je to přidáno do plánu.
Pokud Products.ProductID
se také změní na NULL
-able se pak dotaz stane
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)
Důvodem je, že NULL
Products.ProductId
by neměly být vráceny ve výsledcích s výjimkou pokud NOT IN
dílčí dotaz neměl vrátit vůbec žádné výsledky (tj. [Order Details]
tabulka je prázdná). V tom případě by mělo. V plánu pro moje ukázková data je to implementováno přidáním dalšího anti semi spojení, jak je uvedeno níže.
Účinek tohoto je ukázán v blogovém příspěvku, na který již odkazoval Buckley. V tomto příkladu se počet logických čtení zvýšil z přibližně 400 na 500 000.
Navíc skutečnost, že jeden NULL
může snížit počet řádků na nulu, což velmi ztěžuje odhad mohutnosti. Pokud SQL Server předpokládá, že k tomu dojde, ale ve skutečnosti nebyly žádné NULL
řádků v datech zbytek prováděcího plánu může být katastrofálně horší, pokud je to jen část většího dotazu, s nevhodnými vnořenými smyčkami, které způsobí opakované spouštění například drahého podstromu.
Toto není jediný možný plán provedení pro NOT IN
na NULL
-schopný sloup však. Tento článek ukazuje další pro dotaz na AdventureWorks2008
databáze.
Pro NOT IN
na NOT NULL
nebo NOT EXISTS
proti sloupci s možnou hodnotou null nebo bez možnosti null poskytuje následující plán.
Když se sloupec změní na NULL
-povolit NOT IN
plán nyní vypadá takto
Do plánu přidává další operátor vnitřního spojení. Toto zařízení je vysvětleno zde. Je to vše pro převod předchozího hledání jediného korelovaného indexu na Sales.SalesOrderDetail.ProductID = <correlated_product_id>
na dvě hledání na vnější řadu. Další je na WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Vzhledem k tomu, že se jedná o anti semi spojení, pokud tento vrátí nějaké řádky, druhé hledání nenastane. Pokud však Sales.SalesOrderDetail
neobsahuje žádné NULL
ProductId
s zdvojnásobí počet požadovaných operací hledání.