NON IN vs NON ESISTE


538

Quale di queste domande è più veloce?

NON ESISTE:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE NOT EXISTS (
    SELECT 1 
    FROM Northwind..[Order Details] od 
    WHERE p.ProductId = od.ProductId)

O NON IN:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
    SELECT ProductID 
    FROM Northwind..[Order Details])

Il piano di esecuzione della query afferma che entrambi fanno la stessa cosa. In tal caso, qual è il modulo raccomandato?

Questo si basa sul database NorthWind.

[Modificare]

Ho appena trovato questo utile articolo: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Penso che resterò con NOT EXISTS.


3
hai provato il piano usando un join sinistro dove è null?
Sebas,

1
NOT IN e NOT EXISTS non sono identici. Dai
Ameya Gokhale,

2
Mi chiedo se i database differiscono, ma nel mio ultimo benchmark rispetto a PostgreSQL, questa NOT INquery: SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)è quasi 30 volte più veloce di questa NOT EXISTS:SELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
Phương Nguyễn


1
@rcdmk Hai controllato la data delle domande?
ilitirit,

Risposte:


693

Ho sempre predefinito NOT EXISTS.

I piani di esecuzione possono essere gli stessi al momento, ma se uno colonna viene modificata in futuro per consentire NULLs la NOT INversione avrà bisogno di lavorare di più (anche se non NULLs sono effettivamente presenti nei dati) e la semantica di NOT INse NULLs sono presenti è improbabile che siano quelli che vuoi comunque.

Quando nessuno dei due Products.ProductIDo lo [Order Details].ProductIDconsente NULL, NOT INverrà trattato in modo identico alla seguente query.

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.

Né NULL

Un malinteso ragionevolmente comune sembra essere che le sottoquery correlate siano sempre "cattive" rispetto ai join. Possono certamente esserlo quando forzano un piano di cicli nidificati (sottoquery valutata riga per riga) ma questo piano include un operatore logico anti semi join. I join anti semi non sono limitati ai loop nidificati ma possono utilizzare anche 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è NULLpossibile, la query diventa

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 qualche NULL ProductIds è di non restituire risultati. Vedi lo spool extra anti semi join e conteggio righe per verificare ciò che viene aggiunto al piano.

Un NULL

Se Products.ProductIDviene modificato anche per diventare NULL-able, la query diventa

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 è che a NULL Products.ProductIdnon deve essere restituito a nei risultati, tranne se la NOT INsotto query non restituisce alcun risultato (ovvero la [Order Details]tabella è vuota). Nel qual caso dovrebbe. Nel piano per i miei dati di esempio questo è implementato aggiungendo un altro anti semi join come di seguito.

Entrambi NULL

L'effetto di questo è 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 NULLpossa ridurre il conteggio delle righe a zero rende molto difficile la stima della cardinalità. Se SQL Server suppone che ciò accada, ma in realtà non c'erano NULLrighe nei dati, il resto del piano di esecuzione potrebbe essere catastroficamente peggiore, se questo fa solo parte di una query più grande, con cicli nidificati inappropriati che causano l'esecuzione ripetuta di un sub costoso albero per esempio .

Questo non è l'unica possibile piano di esecuzione per una NOT INsu una NULLcolonna -able tuttavia. Questo articolo ne mostra un altro per una query sul AdventureWorks2008database.

Per la colonna on o contro NOT INuna NOT NULLcolonna NOT EXISTSnullable o non nullable fornisce il seguente piano.

Non esiste

Quando la colonna cambia in NULL-able il NOT INpiano ora assomiglia

Not In - Null

Aggiunge un ulteriore operatore di join interno al piano. Questo apparato è spiegato qui . È tutto lì per convertire la precedente ricerca dell'indice correlato singolo Sales.SalesOrderDetail.ProductID = <correlated_product_id>in due ricerche per riga esterna. Quello aggiuntivo è attivo WHERE Sales.SalesOrderDetail.ProductID IS NULL.

Poiché questo è sotto un anti semi join se quello restituisce delle righe, la seconda ricerca non avrà luogo. Tuttavia, se Sales.SalesOrderDetailnon contiene alcun NULL ProductIDnumero, raddoppierà il numero di operazioni di ricerca richieste.


4
Posso chiederti come si ottiene il grafico di profilatura come mostrato?
xis

5
@xis Questi sono piani di esecuzione aperti in SQL Sentry plan explorer. È inoltre possibile visualizzare graficamente i piani di esecuzione in SSMS.
Martin Smith,

Lo apprezzo per il solo motivo che: NOT EXISTSfunziona nel modo in cui mi aspetto NOT INdi funzionare (cosa che non funziona).
levininja,

Con NOT EXISTS, provo a utilizzare SELECT 1 come NOT EXISTS (SELECT 1 DA qualcosa di DOVE qualcosa) in modo che il database non debba effettivamente restituire colonne dal disco. Usare EXPLAIN per determinare se questo fa la differenza nel tuo caso è probabilmente una buona idea.
Mayur Patel,

4
@Mayur Non è necessario in SQL Server. stackoverflow.com/questions/1597442/…
Martin Smith,

84

Inoltre, tenere presente che NOT IN non equivale a NOT EXISTS quando si tratta di null.

Questo post lo spiega molto bene

http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/

Quando la subquery restituisce anche solo un null, NOT IN non corrisponderà a nessuna riga.

La ragione di ciò può essere trovata guardando i dettagli di cosa significhi effettivamente l'operazione NOT IN.

Diciamo che a scopo illustrativo ci sono 4 righe nella tabella chiamata t, c'è una colonna chiamata ID con i valori 1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t)

è equivalente a

WHERE SomeValue != (SELECT AVal FROM t WHERE ID=1)
AND SomeValue != (SELECT AVal FROM t WHERE ID=2)
AND SomeValue != (SELECT AVal FROM t WHERE ID=3)
AND SomeValue != (SELECT AVal FROM t WHERE ID=4)

Supponiamo inoltre che AVal sia NULL dove ID = 4. Quindi! = Il confronto restituisce SCONOSCIUTO. La tabella di verità logica per AND afferma che SCONOSCIUTO e VERO è SCONOSCIUTO, SCONOSCIUTO e FALSO è FALSO. Non esiste alcun valore che può essere AND'd con UNKNOWN per produrre il risultato TRUE

Quindi, se una riga di quella sottoquery restituisce NULL, l'intero operatore NOT IN valuterà FALSE o NULL e non verrà restituito alcun record


24

Se il pianificatore dell'esecuzione dice che sono uguali, sono uguali. Usa quello che renderà più ovvia la tua intenzione - in questo caso, il secondo.


3
il tempo del pianificatore di esecuzione può essere lo stesso, ma i risultati dell'esecuzione possono differire, quindi c'è una differenza. NOT IN produrrà risultati imprevisti se hai NULL nel tuo set di dati (vedi la risposta di Buckley). Meglio usare NOT EXISTS come predefinito.
Nanonerd,

15

In realtà, credo che questo sarebbe il più veloce:

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null

2
Potrebbe non essere il più veloce quando l'ottimizzatore sta facendo il suo lavoro, ma sicuramente sarà più veloce quando non lo è.
Cade Roux,

2
Potrebbe aver semplificato la sua richiesta anche per questo post
Kip

1
Accetto L'unione esterna sinistra è spesso più veloce di una sottoquery.
HLGEM,

7
@HLGEM Disagree. Nella mia esperienza il caso migliore per LOJ è che sono gli stessi e SQL Server converte LOJ in un anti semi join. Nel peggiore dei casi SQL Server LEFT JOINs tutto e filtra i NULL dopo i quali può essere molto più inefficiente. Esempio di questo in fondo a questo articolo
Martin Smith,

12

Ho una tabella che ha circa 120.000 record e ho bisogno di selezionare solo quelli che non esistono (abbinati a una colonna varchar) in altre quattro tabelle con numero di righe circa 1500, 4000, 40000, 200. Tutte le tabelle coinvolte hanno un indice univoco sulla Varcharcolonna interessata .

NOT INci sono voluti circa 10 minuti, ci sono NOT EXISTSvoluti 4 secondi.

Ho una query ricorsiva che potrebbe avere una sezione non sintonizzata che potrebbe aver contribuito ai 10 minuti, ma l'altra opzione richiede 4 secondi spiega, almeno per me che NOT EXISTSè molto meglio o almeno quello INe EXISTSnon sono esattamente gli stessi e valgono sempre una controlla prima di andare avanti con il codice.


8

Nel tuo esempio specifico sono gli stessi, perché l'ottimizzatore ha capito che cosa stai cercando di fare è lo stesso in entrambi gli esempi. Ma è possibile che in esempi non banali l'ottimizzatore non possa farlo, e in tal caso ci sono motivi per preferire l'uno all'altro in occasione.

NOT INdovrebbe essere preferito se stai testando più righe nella selezione esterna. La sottoquery all'interno dell'istruzione NOT INpuò essere valutata all'inizio dell'esecuzione e la tabella temporanea può essere verificata rispetto a ciascun valore nella selezione esterna, piuttosto che rieseguire la sottoselezione ogni volta che sarebbe necessario con l' NOT EXISTSistruzione.

Se la sottoquery deve essere correlata con la selezione esterna, allora NOT EXISTSpotrebbe essere preferibile, poiché l'ottimizzatore potrebbe scoprire una semplificazione che impedisce alla creazione di qualsiasi tabella temporanea di svolgere la stessa funzione.


6

Stavo usando

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)

e ho scoperto che stava dando risultati sbagliati (per errore intendo nessun risultato). Dato che c'era un NULL in TABLE2.Col1.

Durante la modifica della query in

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)

mi ha dato i risultati corretti.

Da allora ho iniziato a utilizzare NOT EXISTS ovunque.


5

Sono molto simili ma non uguali.

In termini di efficienza, ho scoperto che il join sinistro è un'istruzione null più efficiente (quando è necessario selezionare un'abbondanza di righe)


2

Se l'ottimizzatore dice che sono gli stessi, considera il fattore umano. Preferisco vedere NON ESISTE :)


1

Questa è un'ottima domanda, quindi ho deciso di scrivere un articolo molto dettagliato su questo argomento sul mio blog.

Modello di tabella del database

Supponiamo di avere le seguenti due tabelle nel nostro database, che formano una relazione da una a molte tabelle.

Tabelle EXISTS SQL

La studenttabella è padre e student_gradeè la tabella figlio poiché ha una colonna Chiave esterna student_id che fa riferimento alla colonna Chiave primaria id nella tabella studente.

Il student tablecontiene i seguenti due record:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

E la student_gradetabella memorizza i voti che gli studenti hanno ricevuto:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL EXISTS

Diciamo che vogliamo ottenere tutti gli studenti che hanno ricevuto un voto di 10 in classe di matematica.

Se siamo interessati solo all'identificatore dello studente, possiamo eseguire una query come questa:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Ma l'applicazione è interessata a visualizzare il nome completo di un student, non solo l'identificatore, quindi abbiamo bisogno anche di informazioni dalla studenttabella.

Per filtrare i studentrecord che hanno un grado 10 in matematica, possiamo usare l'operatore EXISTS SQL, in questo modo:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Quando eseguiamo la query sopra, possiamo vedere che è selezionata solo la riga Alice:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

La query esterna seleziona le studentcolonne delle righe che siamo interessati a restituire al client. Tuttavia, la clausola WHERE utilizza l'operatore EXISTS con una subquery interna associata.

L'operatore EXISTS restituisce true se la sottoquery restituisce almeno un record e false se non è selezionata alcuna riga. Il motore di database non deve eseguire interamente la sottoquery. Se viene abbinato un singolo record, l'operatore EXISTS restituisce true e viene selezionata l'altra riga della query associata.

La sottoquery interna è correlata perché la colonna student_id della student_gradetabella è confrontata con la colonna id della tabella studente esterna.

SQL NON ESISTE

Consideriamo che vogliamo selezionare tutti gli studenti che non hanno un voto inferiore a 9. Per questo, possiamo usare NOT EXISTS, che annulla la logica dell'operatore EXISTS.

Pertanto, l'operatore NOT EXISTS restituisce true se la sottoquery sottostante non restituisce alcun record. Tuttavia, se un singolo record corrisponde alla sottoquery interna, l'operatore NOT EXISTS restituirà false e l'esecuzione della sottoquery può essere interrotta.

Per abbinare tutti i record degli studenti a cui non è associato student_grade con un valore inferiore a 9, possiamo eseguire la seguente query SQL:

SELECT
    id, first_name, last_name
FROM
    student
WHERE NOT EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade < 9
)
ORDER BY id

Quando eseguiamo la query sopra, possiamo vedere che solo il record di Alice è abbinato:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Pertanto, il vantaggio di utilizzare gli operatori SQL EXISTS e NOT EXISTS è che l'esecuzione della sottoquery interna può essere arrestata fino a quando viene trovato un record corrispondente.


-1

Dipende..

SELECT x.col
FROM big_table x
WHERE x.key IN( SELECT key FROM really_big_table );

non sarebbe relativamente lento non è molto per limitare la dimensione di ciò che la query controlla per vedere se la loro chiave è inserita. In questo caso sarebbe preferibile EXISTS.

Ma, a seconda dell'ottimizzatore del DBMS, questo potrebbe non essere diverso.

Come esempio di quando EXISTS è migliore

SELECT x.col
FROM big_table x
WHERE EXISTS( SELECT key FROM really_big_table WHERE key = x.key);
  AND id = very_limiting_criteria

1
INe EXISTS ottieni lo stesso piano in SQL Server . La domanda riguarda comunque NOT INvs. NOT EXISTS
Martin Smith,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.