Ik gebruik altijd NOT EXISTS
.
De uitvoeringsplannen kunnen op dit moment hetzelfde zijn, maar als een van beide kolommen in de toekomst wordt gewijzigd om NULL
toe te staan s de NOT IN
versie zal meer werk moeten doen (zelfs als er geen NULL
is s daadwerkelijk aanwezig zijn in de data) en de semantiek van NOT IN
if NULL
s zijn Het is toch onwaarschijnlijk dat het cadeau is wat je wilt.
Wanneer geen van beide Products.ProductID
of [Order Details].ProductID
sta NULL
toe s de NOT IN
wordt op dezelfde manier behandeld als de volgende vraag.
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
Het exacte plan kan variëren, maar voor mijn voorbeeldgegevens krijg ik het volgende.
Een redelijk algemene misvatting lijkt te zijn dat gecorreleerde subquery's altijd "slecht" zijn in vergelijking met joins. Dat kan zeker wanneer ze een geneste lusplan forceren (subquery wordt rij voor rij geëvalueerd), maar dit plan bevat een logische operator tegen semi-join. Anti-semi-joins zijn niet beperkt tot geneste lussen, maar kunnen ook hash- of merge-joins (zoals in dit voorbeeld) gebruiken.
/*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
If [Order Details].ProductID
is NULL
-able de query wordt dan
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)
De reden hiervoor is dat de juiste semantiek als [Order Details]
bevat een NULL
ProductId
s is om geen resultaten te retourneren. Bekijk de extra anti-semi-join- en rijtellingspoel om te controleren of deze aan het plan is toegevoegd.
Als Products.ProductID
is ook veranderd in NULL
-able de query wordt dan
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)
De reden daarvoor is dat een NULL
Products.ProductId
mag niet worden geretourneerd in de resultaten behalve als de NOT IN
subquery zou helemaal geen resultaten opleveren (d.w.z. de [Order Details]
tafel is leeg). In dat geval zou het moeten. In het plan voor mijn voorbeeldgegevens wordt dit geïmplementeerd door nog een anti-semi-join toe te voegen, zoals hieronder.
Het effect hiervan is te zien in de blogpost die al door Buckley is gelinkt. In het voorbeeld neemt het aantal logische reads toe van ongeveer 400 tot 500.000.
Bovendien het feit dat een enkele NULL
kan het aantal rijen tot nul terugbrengen, maakt het schatten van de kardinaliteit erg moeilijk. Als SQL Server ervan uitgaat dat dit zal gebeuren, maar in feite waren er geen NULL
rijen in de gegevens kan de rest van het uitvoeringsplan catastrofaal slechter zijn, als dit slechts een onderdeel is van een grotere query, met ongepaste geneste lussen die bijvoorbeeld de herhaalde uitvoering van een dure subboom veroorzaken.
Dit is niet het enige mogelijke uitvoeringsplan voor een NOT IN
op een NULL
- staat kolom echter. Dit artikel toont een andere voor een zoekopdracht tegen de AdventureWorks2008
database.
Voor de NOT IN
op een NOT NULL
kolom of de NOT EXISTS
tegen een nullable of niet-nullable kolom geeft het het volgende plan.
Wanneer de kolom verandert in NULL
-in staat de NOT IN
plan ziet er nu uit als
Het voegt een extra inner join-operator toe aan het plan. Dit apparaat wordt hier uitgelegd. Het is er allemaal om de vorige enkele gecorreleerde indexzoekopdracht te converteren op Sales.SalesOrderDetail.ProductID = <correlated_product_id>
tot twee zoekopdrachten per buitenste rij. De extra is op WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Omdat dit onder een anti-semi-join valt, zal de tweede zoekactie niet plaatsvinden. Maar als Sales.SalesOrderDetail
bevat geen NULL
ProductId
s het verdubbelt het aantal benodigde zoekbewerkingen.