Best practice tra l'utilizzo di LEFT JOIN o NOT EXISTS


67

Esiste una procedura ottimale tra l'utilizzo di un formato LEFT JOIN o NOT EXISTS?

Quali sono i vantaggi dell'utilizzo l'uno rispetto all'altro?

Se nessuno, quale dovrebbe essere preferito?

SELECT *
FROM tableA A
LEFT JOIN tableB B
     ON A.idx = B.idx
WHERE B.idx IS NULL

SELECT *
FROM tableA A
WHERE NOT EXISTS
(SELECT idx FROM tableB B WHERE B.idx = A.idx)

Sto usando query in Access contro un database di SQL Server.


2
Per inciso, l'approccio apparentemente identico WHERE A.idx NOT IN (...) è non è identico a causa del comportamento di trivalente NULL(vale a dire NULL, non è uguale a NULL(né disuguale), quindi se avete qualsiasi NULL in tableBsi ottengono risultati inaspettati!)
Elaskanator

Risposte:


58

La differenza più grande non è nel join vs non esiste, è (come scritto), il SELECT *.

Nel primo esempio, ottieni tutte le colonne da entrambi A e B, mentre nel secondo esempio, ottieni solo le colonne A.

In SQL Server, la seconda variante è leggermente più veloce in un semplice esempio inventato:

Creare due tabelle di esempio:

CREATE TABLE dbo.A
(
    A_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);

CREATE TABLE dbo.B
(
    B_ID INT NOT NULL
        PRIMARY KEY CLUSTERED
        IDENTITY(1,1)
);
GO

Inserisci 10.000 righe in ogni tabella:

INSERT INTO dbo.A DEFAULT VALUES;
GO 10000

INSERT INTO dbo.B DEFAULT VALUES;
GO 10000

Rimuovi ogni 5a fila dalla seconda tabella:

DELETE 
FROM dbo.B 
WHERE B_ID % 5 = 1;

SELECT COUNT(*) -- shows 10,000
FROM dbo.A;

SELECT COUNT(*) -- shows  8,000
FROM dbo.B;

Eseguire le due SELECTvarianti dell'istruzione test :

SELECT *
FROM dbo.A
    LEFT JOIN dbo.B ON A.A_ID = B.B_ID
WHERE B.B_ID IS NULL;

SELECT *
FROM dbo.A
WHERE NOT EXISTS (SELECT 1
    FROM dbo.B
    WHERE b.B_ID = a.A_ID);

Piani di esecuzione:

inserisci qui la descrizione dell'immagine

La seconda variante non deve eseguire l'operazione di filtro poiché può utilizzare l'operatore anti-semi join sinistro.


23

Logicamente sono identici, ma NOT EXISTSè più vicino all'AntiSemiJoin che stai chiedendo ed è generalmente preferito. Inoltre, evidenzia meglio che non è possibile accedere alle colonne in B, perché viene utilizzato solo come filtro (invece di averle disponibili con valori NULL).

Molti anni fa (SQL Server 6.0 ish), è LEFT JOINstato più veloce, ma non è stato così per molto tempo. In questi giorni, NOT EXISTSè leggermente più veloce.


L'impatto maggiore in Access è che il JOINmetodo deve completare il join prima di filtrarlo, costruendo il set di join in memoria. Usandolo NOT EXISTScontrolla la riga ma non alloca spazio per le colonne. Inoltre, smette di cercare una volta trovata una riga. Le prestazioni variano un po 'di più in Access, ma una regola generale è che NOT EXISTStende ad essere un po' più veloce. Sarei meno propenso a dire che è "best practice", poiché ci sono più fattori coinvolti.


6

Un'eccezione che ho notato NOT EXISTSessere superiore (anche se marginalmente) a LEFT JOIN ... WHERE IS NULLquando si utilizzano i server collegati .

Dall'esame dei piani di esecuzione, sembra che l' NOT EXISTSoperatore venga eseguito in modo nidificato. Per cui viene eseguito su una riga per riga (che suppongo abbia senso).

Esempio di piano di esecuzione che dimostra questo comportamento: inserisci qui la descrizione dell'immagine


1
I server collegati sono brutali per questo tipo di cose. Un possibile approccio per risolvere quel problema è copiare i dati remoti sul collegamento al server collegato usando una clausola semplice e INSERT INTO #t (a,b,c) SELECT a,b,c FROM LinkedServer.database.dbo.table WHERE x=yquindi in esecuzione NOT EXISTS (...)contro quella copia temporanea del database.
Max Vernon,

2
Un po 'timoroso in questo momento per ottenere una risposta da Max Vernon sul mio post! Fanboy a parte. È divertente che tu lo abbia menzionato, poiché ho usato questo approccio esatto in diverse occasioni per ottenere il massimo da quelle situazioni tra server.
robopim,

1
Saluti, @pimbrouwers - grazie per il tuo gentile commento!
Max Vernon,

5

In generale, il motore creerà un piano di esecuzione basato essenzialmente su:

  1. Il numero di righe in A e B
  2. Se esiste un indice su A e / o B.
  3. Il numero previsto di righe dei risultati (e righe intermedie)
  4. Il modulo della query di input (ovvero la tua domanda)

Per (4):

Il piano "non esiste" incoraggia un piano basato sulla ricerca nella tabella B. Questa è una buona scelta quando la tabella A è piccola e la tabella B è grande (e un indice esiste su B).

Il piano "antijoin" è una buona scelta quando la tabella A è molto grande o la tabella B è molto piccola o nessun indice su B e restituisce un grande set di risultati.

Tuttavia è solo un "incoraggiamento", come un input ponderato. Un forte (1), (2), (3) spesso fa la scelta per (4) moot.

(Ignorando l'effetto del tuo esempio restituendo colonne diverse a causa del *, risolto dalla risposta @MaxVernon.).

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.