Som standard er jeg altid EKSISTERER IKKE
.
Udførelsesplanerne kan være de samme i øjeblikket, men hvis en af kolonnerne ændres i fremtiden for at tillade NULL
er NULL
s faktisk er til stede i dataene) og semantikken for NOT IN
hvis NULL
s er tilstedeværende er usandsynligt at være dem, du ønsker alligevel.
Når hverken Products.ProductID
eller [Ordredetaljer].ProductID
tillad NULL
er
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
Den nøjagtige plan kan variere, men for mine eksempeldata får jeg følgende.
En rimelig almindelig misforståelse synes at være, at korrelerede underforespørgsler altid er "dårlige" sammenlignet med joinforbindelser. Det kan de bestemt være, når de tvinger en indlejret sløjfeplan (underforespørgsel evalueret række for række), men denne plan indeholder en anti-semi join logisk operator. Anti semi joins er ikke begrænset til indlejrede loops, men kan også bruge hash eller merge (som i dette eksempel) joins.
/*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
Hvis [Ordredetaljer].ProductID
er NULL
-able forespørgslen bliver derefter
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)
Grunden til dette er, at den korrekte semantik, hvis [Order Details]
indeholder enhver NULL
ProductId
s er at returnere ingen resultater. Se den ekstra anti-semi join- og rækketællerspole for at bekræfte dette, som er føjet til planen.
Hvis Products.ProductID
er også ændret til at blive NULL
-able forespørgslen bliver derefter
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)
Grunden til det er fordi en NULL
Products.ProductId
skal ikke returneres i resultaterne undtagen hvis NOT IN
underforespørgsel skulle ikke returnere nogen resultater overhovedet (dvs. [Order Details]
tabellen er tom). I så fald burde det. I planen for mine prøvedata er dette implementeret ved at tilføje en anden anti semi join som nedenfor.
Effekten af dette er vist i blogindlægget, der allerede er linket til af Buckley. I eksemplet stiger antallet af logiske læsninger fra omkring 400 til 500.000.
Derudover det faktum, at en enkelt NULL
kan reducere rækkeantallet til nul gør det meget vanskeligt at estimere kardinalitet. Hvis SQL Server antager, at dette vil ske, men der faktisk ikke var nogen NULL
rækker i dataene resten af udførelsesplanen kan være katastrofalt værre, hvis dette blot er en del af en større forespørgsel, med uhensigtsmæssige indlejrede løkker, der for eksempel forårsager gentagen eksekvering af et dyrt undertræ.
Dette er ikke den eneste mulige udførelsesplan for en NOT IN
på en NULL
-stand kolonne dog. Denne artikel viser en anden for en forespørgsel mod AdventureWorks2008
database.
For NOT IN
på en NOT NULL
kolonne eller EKSISTERER IKKE
mod enten en nullbar eller ikke-nullbar kolonne giver den følgende plan.
Når kolonnen ændres til NULL
-i stand til NOT IN
planen ser nu ud
Det tilføjer en ekstra indre joinoperatør til planen. Dette apparat er forklaret her. Det hele er der for at konvertere den tidligere enkelte korrelerede indekssøgning på Sales.SalesOrderDetail.ProductID =
til to søgninger pr. yderste række. Den yderligere er på WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Da dette er under en anti semi join, hvis den ene returnerer nogen rækker, vil den anden søgning ikke forekomme. Men hvis Sales.SalesOrderDetail
indeholder ingen NULL
Produkt-id
s det vil fordoble antallet af krævede søgeoperationer.