Quale query SQL è più veloce? Filtro in base ai criteri di adesione o alla clausola Where?


98

Confronta queste 2 query. È più veloce mettere il filtro sui criteri di join o nella WHEREclausola. Ho sempre pensato che sia più veloce nei criteri di join perché riduce il risultato impostato il più presto possibile, ma non lo so per certo.

Ho intenzione di costruire alcuni test da vedere, ma volevo anche ottenere opinioni su quali sarebbero anche più chiare da leggere.

Domanda 1

SELECT      *
FROM        TableA a
INNER JOIN  TableXRef x
        ON  a.ID = x.TableAID
INNER JOIN  TableB b
        ON  x.TableBID = b.ID
WHERE       a.ID = 1            /* <-- Filter here? */

Domanda 2

SELECT      *
FROM        TableA a
INNER JOIN  TableXRef x
        ON  a.ID = x.TableAID
        AND a.ID = 1            /* <-- Or filter here? */
INNER JOIN  TableB b
        ON  x.TableBID = b.ID

MODIFICARE

Ho eseguito alcuni test ei risultati mostrano che in realtà è molto vicino, ma in WHERErealtà la clausola è leggermente più veloce! =)

Sono assolutamente d'accordo sul fatto che abbia più senso applicare il filtro alla WHEREclausola, ero solo curioso delle implicazioni sulla performance.

TEMPO TRASCORSO DOVE CRITERI: 143016 ms
TEMPO TRASCORSO CRITERI DI PARTECIPAZIONE : 143256 ms

TEST

SET NOCOUNT ON;

DECLARE @num    INT,
        @iter   INT

SELECT  @num    = 1000, -- Number of records in TableA and TableB, the cross table is populated with a CROSS JOIN from A to B
        @iter   = 1000  -- Number of select iterations to perform

DECLARE @a TABLE (
        id INT
)

DECLARE @b TABLE (
        id INT
)

DECLARE @x TABLE (
        aid INT,
        bid INT
)

DECLARE @num_curr INT
SELECT  @num_curr = 1
        
WHILE (@num_curr <= @num)
BEGIN
    INSERT @a (id) SELECT @num_curr
    INSERT @b (id) SELECT @num_curr
    
    SELECT @num_curr = @num_curr + 1
END

INSERT      @x (aid, bid)
SELECT      a.id,
            b.id
FROM        @a a
CROSS JOIN  @b b

/*
    TEST
*/
DECLARE @begin_where    DATETIME,
        @end_where      DATETIME,
        @count_where    INT,
        @begin_join     DATETIME,
        @end_join       DATETIME,
        @count_join     INT,
        @curr           INT,
        @aid            INT

DECLARE @temp TABLE (
        curr    INT,
        aid     INT,
        bid     INT
)

DELETE FROM @temp

SELECT  @curr   = 0,
        @aid    = 50

SELECT  @begin_where = CURRENT_TIMESTAMP
WHILE (@curr < @iter)
BEGIN
    INSERT      @temp (curr, aid, bid)
    SELECT      @curr,
                aid,
                bid
    FROM        @a a
    INNER JOIN  @x x
            ON  a.id = x.aid
    INNER JOIN  @b b
            ON  x.bid = b.id
    WHERE       a.id = @aid
        
    SELECT @curr = @curr + 1
END
SELECT  @end_where = CURRENT_TIMESTAMP

SELECT  @count_where = COUNT(1) FROM @temp
DELETE FROM @temp

SELECT  @curr = 0
SELECT  @begin_join = CURRENT_TIMESTAMP
WHILE (@curr < @iter)
BEGIN
    INSERT      @temp (curr, aid, bid)
    SELECT      @curr,
                aid,
                bid
    FROM        @a a
    INNER JOIN  @x x
            ON  a.id = x.aid
            AND a.id = @aid
    INNER JOIN  @b b
            ON  x.bid = b.id
    
    SELECT @curr = @curr + 1
END
SELECT  @end_join = CURRENT_TIMESTAMP

SELECT  @count_join = COUNT(1) FROM @temp
DELETE FROM @temp

SELECT  @count_where AS count_where,
        @count_join AS count_join,
        DATEDIFF(millisecond, @begin_where, @end_where) AS elapsed_where,
        DATEDIFF(millisecond, @begin_join, @end_join) AS elapsed_join

10
A seconda dei dati, i criteri WHERE e JOIN possono restituire diversi set di risultati.
OMG Ponies

4
@OMG Ponies molto vero, ma molte volte non lo è altrettanto.
Jon Erickson

2
Non chiamerei differenza inferiore al 5% come differenza: sono la stessa cosa. Se vuoi un significato per una differenza del 2 %%, meglio eseguire i test 1000 volte per assicurarti che non sia solo casuale.
TomTom

Il vantaggio sta nel filtrare i dati prima di partecipare, quindi se fosse x.ID, sarebbe più probabile vedere un miglioramento rispetto a un a.ID
MikeT

Risposte:


65

Dal punto di vista delle prestazioni, sono gli stessi (e producono gli stessi piani)

Logicamente, dovresti eseguire l'operazione che ha ancora senso se sostituisci INNER JOINcon un fileLEFT JOIN .

Nel tuo caso questo sarà simile a questo:

SELECT  *
FROM    TableA a
LEFT JOIN
        TableXRef x
ON      x.TableAID = a.ID
        AND a.ID = 1
LEFT JOIN
        TableB b
ON      x.TableBID = b.ID

o questo:

SELECT  *
FROM    TableA a
LEFT JOIN
        TableXRef x
ON      x.TableAID = a.ID
LEFT JOIN
        TableB b
ON      b.id = x.TableBID
WHERE   a.id = 1

La prima query non restituirà alcuna corrispondenza effettiva per a.iddiverso da1 , quindi la seconda sintassi (con WHERE) è logicamente più coerente.


Quando disegno le serie ho capito perché il secondo caso è più coerente. Nella prima query, il vincolo si a.id = 1applica solo all'intersezione, non alla parte sinistra escludendo l'intersezione.
FtheBuilder

1
Nel primo esempio potrebbero esserci righe in cui a.id != 1, l'altro avrà solo righe in cui a.id = 1.
FtheBuilder

1
La tua lingua non è chiara. "Logicamente, dovresti rendere l'operazione che ha ancora senso se ..." e "logicamente più coerente" non ha senso. Puoi riformulare per favore?
philipxy

24

Per i join interni non importa dove metti i tuoi criteri. Il compilatore SQL trasformerà entrambi in un piano di esecuzione in cui il filtraggio avviene sotto il join (cioè come se le espressioni del filtro appaiano nella condizione di join).

Gli outer join sono una questione diversa, poiché la posizione del filtro cambia la semantica della query.


Quindi in inner join calcola prima il filtro e poi unisce l'output del filtro con l'altra tabella o prima unisce le due tabelle e poi applica il filtro?
Ashwin

@ Remus Rusanu - potresti per favore approfondire come la semantica viene modificata in caso di Outer-join? Ottengo risultati diversi in base alla posizione del filtro, ma non riesco a capire perché
Ananth

3
@Ananth con un outer join ottieni NULL per tutte le colonne della tabella unita in cui la condizione JOIN non corrisponde. I filtri non soddisfano il NULL ed eliminano le righe, trasformando il join ESTERNO in effetti in un join INTERNO.
Remus Rusanu

@Ananth ho ottenuto le ottimizzazioni richieste in base al tuo commento. La mia modifica è stata da WHERE x.TableAID = a.ID o x.TableAID è nullo a ON x.TableAID = a.ID. La modifica della posizione del filtro su un join ESTERNO consente al compilatore di sapere di filtrare, quindi di unire anziché di partecipare e quindi di filtrare. È stato anche in grado di utilizzare l'indice su quella colonna perché non doveva corrispondere a Null. La risposta alla query è stata modificata da 61 secondi a 2 secondi.
Ben Gripka

10

Per quanto riguarda i due metodi.

  • JOIN / ON è per unire le tabelle
  • WHERE serve per filtrare i risultati

Anche se puoi usarli in modo diverso, mi sembra sempre un odore.

Affronta le prestazioni quando è un problema. Quindi puoi esaminare tali "ottimizzazioni".


2

Con qualsiasi Query Optimizer vale un centesimo ... sono identici.


Sono abbastanza sicuro che, con qualsiasi carico di lavoro reale, non siano identici. Se quasi non disponi di dati, la domanda è inutile.
eKek0

2
Controllalo sotto carico di lavoro reale. Fondamentalmente, se generano lo stesso piano di esecuzione, sono ... identici in termini di prestazioni. Almeno per i casi normali / semplici (cioè non quello che unisce 14 tavoli) sono abbastanza sicuro che siano identici;)
TomTom

1

In postgresql sono gli stessi. Lo sappiamo perché se lo fai explain analyzesu ciascuna delle domande, il piano risulta essere lo stesso. Prendi questo esempio:

# explain analyze select e.* from event e join result r on e.id = r.event_id and r.team_2_score=24;

                                                  QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=27.09..38.22 rows=7 width=899) (actual time=0.045..0.047 rows=1 loops=1)
   Hash Cond: (e.id = r.event_id)
   ->  Seq Scan on event e  (cost=0.00..10.80 rows=80 width=899) (actual time=0.009..0.010 rows=2 loops=1)
   ->  Hash  (cost=27.00..27.00 rows=7 width=8) (actual time=0.017..0.017 rows=1 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on result r  (cost=0.00..27.00 rows=7 width=8) (actual time=0.006..0.008 rows=1 loops=1)
               Filter: (team_2_score = 24)
               Rows Removed by Filter: 1
 Planning time: 0.182 ms
 Execution time: 0.101 ms
(10 rows)

# explain analyze select e.* from event e join result r on e.id = r.event_id where r.team_2_score=24;
                                                  QUERY PLAN                                                   
---------------------------------------------------------------------------------------------------------------
 Hash Join  (cost=27.09..38.22 rows=7 width=899) (actual time=0.027..0.029 rows=1 loops=1)
   Hash Cond: (e.id = r.event_id)
   ->  Seq Scan on event e  (cost=0.00..10.80 rows=80 width=899) (actual time=0.010..0.011 rows=2 loops=1)
   ->  Hash  (cost=27.00..27.00 rows=7 width=8) (actual time=0.010..0.010 rows=1 loops=1)
         Buckets: 1024  Batches: 1  Memory Usage: 9kB
         ->  Seq Scan on result r  (cost=0.00..27.00 rows=7 width=8) (actual time=0.006..0.007 rows=1 loops=1)
               Filter: (team_2_score = 24)
               Rows Removed by Filter: 1
 Planning time: 0.140 ms
 Execution time: 0.058 ms
(10 rows)

Entrambi hanno lo stesso costo minimo e massimo e lo stesso piano di query. Inoltre, nota che anche nella query principale il team_score_2 viene applicato come "Filtro".


0

È davvero improbabile che il posizionamento di questo join sia il fattore decisivo per le prestazioni. Non ho familiarità con la pianificazione dell'esecuzione per tsql, ma è probabile che vengano ottimizzati automaticamente per piani simili.


0

Regola # 0: esegui alcuni benchmark e guarda! L'unico modo per dire veramente quale sarà più veloce è provarlo. Questi tipi di benchmark sono molto facili da eseguire utilizzando il profiler SQL.

Inoltre, esaminare il piano di esecuzione per la query scritta con una clausola JOIN e con una clausola WHERE per vedere quali differenze risaltano.

Infine, come altri hanno detto, questi due dovrebbero essere trattati in modo identico da qualsiasi ottimizzatore decente, incluso quello integrato in SQL Server.


Ma solo per i join interni. I risultati impostati saranno molto diversi per gli out join.
HLGEM

Ovviamente. Fortunatamente, l'esempio fornito utilizza inner join.
3Dave il

1
Sfortunatamente la domanda riguarda i join, non i join interni.
Paul

Sì David, la domanda riguarda i join. L'esempio che supporta la domanda utilizza gli inner join.
Paul

0

È più veloce? Provalo e guarda.

Quale è più facile da leggere? Il primo mi sembra più "corretto", poiché la condizione di spostamento non ha nulla a che fare con l'unione.


0

Immagino che sia il primo, perché crea un filtro più specifico sui dati. Ma dovresti vedere il piano di esecuzione , come con qualsiasi ottimizzazione, perché può essere molto diverso a seconda della dimensione dei dati, dell'hardware del server, ecc.

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.