Jag har alltid som standard FINNS INTE
.
Utförandeplanerna kan vara desamma för tillfället men om någon av kolumnerna ändras i framtiden för att tillåta NULL
är NULL
s faktiskt finns i datan) och semantiken för NOT IN
om NULL
s är närvarande är sannolikt inte de du vill ha ändå.
När varken Products.ProductID
eller [Beställningsinformation].ProduktID
tillåt NULL
är
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
Den exakta planen kan variera men för mina exempeldata får jag följande.
En ganska vanlig missuppfattning verkar vara att korrelerade underfrågor alltid är "dåliga" jämfört med joins. De kan säkert vara det när de tvingar fram en kapslad loopplan (underfrågan utvärderas rad för rad), men den här planen innehåller en logisk anti-semi-join-operator. Anti-semi-joins är inte begränsade till kapslade loopar utan kan också använda hash- eller merge-kopplingar (som i det här exemplet).
/*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
Om [Beställningsinformation].ProduktID
är NULL
-kan blir frågan
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)
Anledningen till detta är att korrekt semantik om [Beställningsdetaljer]
innehåller någon NULL
ProductId
s är att inte returnera några resultat. Se den extra anti semi join och radräkningsspolen för att verifiera detta som läggs till i planen.
Om Products.ProductID
ändras också till att bli NULL
-kan blir frågan
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)
Anledningen till det är att en NULL
Products.ProductId
ska inte returneras i resultaten förutom om NOT IN
underfrågan skulle inte returnera några resultat alls (d.v.s. [Beställningsinformation]
tabellen är tom). I så fall borde det. I planen för mina exempeldata implementeras detta genom att lägga till ytterligare en anti semi-join enligt nedan.
Effekten av detta visas i blogginlägget som redan länkats av Buckley. I exemplet där ökar antalet logiska läsningar från cirka 400 till 500 000.
Dessutom det faktum att en enda NULL
kan minska radantalet till noll gör kardinalitetsuppskattningen mycket svår. Om SQL Server antar att detta kommer att hända men i själva verket fanns det ingen NULL
rader i data resten av exekveringsplanen kan vara katastrofalt värre, om detta bara är en del av en större fråga, med olämpliga kapslade loopar som orsakar upprepad exekvering av ett dyrt underträd till exempel.
Detta är inte den enda möjliga exekveringsplanen för en NOT IN
på en NULL
-kan kolumn dock. Den här artikeln visar en annan för en fråga mot AdventureWorks2008
databas.
För NOT IN
på en NOT NULL
kolumnen eller FINNS INTE
mot antingen en nullbar eller icke nullbar kolumn ger den följande plan.
När kolumnen ändras till NULL
-kan NOT IN
planen ser nu ut
Det lägger till en extra inre sammanfogningsoperatör till planen. Denna apparat förklaras här. Allt finns där för att konvertera den tidigare enstaka korrelerade indexsökningen på Sales.SalesOrderDetail.ProductID =
till två sökningar per yttre rad. Den ytterligare finns på WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Eftersom detta är under en anti semi-join om den returnerar några rader kommer den andra sökningen inte att ske. Men om Sales.SalesOrderDetail
innehåller ingen NULL
Produkt-ID
s det kommer att fördubbla antalet sökoperationer som krävs.