Di default ho sempre NOT EXISTS
.
I piani di esecuzione potrebbero essere gli stessi al momento, ma se una delle colonne verrà modificata in futuro per consentire NULL
s il NOT IN
la versione dovrà fare più lavoro (anche se nessun NULL
s sono effettivamente presenti nei dati) e la semantica di NOT IN
se NULL
s sono è improbabile che i presenti siano quelli che desideri comunque.
Quando né Products.ProductID
o [Order Details].ProductID
consenti NULL
s il NOT IN
verrà trattato in modo identico alla query seguente.
SELECT ProductID,
ProductName
FROM Products p
WHERE NOT EXISTS (SELECT *
FROM [Order Details] od
WHERE p.ProductId = od.ProductId)
Il piano esatto può variare, ma per i miei dati di esempio ottengo quanto segue.
Un malinteso ragionevolmente comune sembra essere che le sottoquery correlate siano sempre "cattive" rispetto ai join. Certamente possono essere quando forzano un piano di cicli annidati (sottoquery valutata riga per riga), ma questo piano include un operatore logico anti semi join. Gli anti semi join non sono limitati ai loop nidificati, ma possono anche utilizzare hash o merge (come in questo esempio).
/*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
Se [Order Details].ProductID
è NULL
-able la query diventa quindi
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)
La ragione di ciò è che la semantica corretta se [Order Details]
contiene qualsiasi NULL
ProductId
s non restituisce alcun risultato. Vedi lo spool extra anti semi join e il conteggio delle righe per verificare che venga aggiunto al piano.
Se Products.ProductID
è anche cambiato per diventare NULL
-able la query diventa quindi
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)
Il motivo è perché NULL
Products.ProductId
non deve essere restituito nei risultati tranne se il NOT IN
sub query non avrebbero restituito alcun risultato (ovvero il [Order Details]
la tabella è vuota). In tal caso dovrebbe. Nel piano per i miei dati di esempio questo viene implementato aggiungendo un altro anti semi join come di seguito.
L'effetto di ciò è mostrato nel post del blog già collegato da Buckley. Nell'esempio il numero di letture logiche aumenta da circa 400 a 500.000.
Inoltre il fatto che un singolo NULL
può ridurre il conteggio delle righe a zero rende molto difficile la stima della cardinalità. Se SQL Server presuppone che ciò accada, ma in realtà non c'era NULL
righe nei dati il resto del piano di esecuzione potrebbe essere catastroficamente peggiore, se questa è solo una parte di una query più ampia, con loop nidificati inappropriati che causano l'esecuzione ripetuta di un costoso sottoalbero, ad esempio.
Questo non è l'unico piano di esecuzione possibile per un NOT IN
su un NULL
-able colonna tuttavia. Questo articolo ne mostra un altro per una query su AdventureWorks2008
banca dati.
Per il NOT IN
su un NOT NULL
o la colonna NOT EXISTS
contro una colonna nullable o non nullable fornisce il piano seguente.
Quando la colonna cambia in NULL
-abilita il NOT IN
il piano ora assomiglia a
Aggiunge al piano un operatore di inner join aggiuntivo. Questo apparato è spiegato qui. È tutto lì per convertire la precedente ricerca dell'indice correlato singolo su Sales.SalesOrderDetail.ProductID = <correlated_product_id>
a due ricerche per riga esterna. Quello aggiuntivo è su WHERE Sales.SalesOrderDetail.ProductID IS NULL
.
Poiché questo è sotto un anti semi join, se quello restituisce qualsiasi riga, la seconda ricerca non si verificherà. Tuttavia, se Sales.SalesOrderDetail
non contiene alcun NULL
ProductId
s raddoppierà il numero di operazioni di ricerca richieste.