Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

NON IN vs NON ESISTE

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.