Qual consulta SQL é mais rápida? Filtrar por critérios de adesão ou cláusula Where?

97

Compare essas 2 consultas. É mais rápido colocar o filtro nos critérios de junção ou na WHEREcláusula. Sempre achei que é mais rápido nos critérios de junção porque reduz o conjunto de resultados o mais rápido possível, mas não sei ao certo.

Vou construir alguns testes para ver, mas também gostaria de obter opiniões sobre quais seriam mais claras de ler também.

Consulta 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? */

Consulta 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

EDITAR

Fiz alguns testes e os resultados mostram que está muito próximo, mas a WHEREcláusula é um pouco mais rápida! =)

Eu concordo totalmente que faz mais sentido aplicar o filtro na WHEREcláusula, eu estava apenas curioso para saber as implicações de desempenho.

TEMPO REALIZADO ONDE CRITÉRIOS: 143016 ms
CRITÉRIOS DE REGISTRO DE TEMPO REALIZADO : 143256 ms

TESTE

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
Jon Erickson
fonte
9
Dependendo dos dados, os critérios WHERE vs JOIN podem retornar conjuntos de resultados diferentes.
Pôneis OMG
3
@OMG Pôneis muito verdadeiros, mas muitas vezes também não.
Jon Erickson
2
Eu não chamaria a diferença abaixo de 5% como uma diferença - eles são iguais. Se você deseja significância para uma diferença de 2 %%, é melhor executar os testes 1000 vezes para garantir que não seja apenas aleatório.
TomTom de
A vantagem é filtrar os dados antes de ingressar, então se fosse x.ID, você teria mais chances de ver melhorias do que com um a.ID
MikeT

Respostas:

64

Em termos de desempenho, eles são os mesmos (e produzem os mesmos planos)

Logicamente, você deve fazer a operação que ainda faz sentido se substituir INNER JOINpor a LEFT JOIN.

No seu caso, será assim:

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

ou isto:

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

A primeira consulta não retornará nenhuma correspondência real para a.iddiferente de 1, portanto, a última sintaxe (com WHERE) é logicamente mais consistente.

Quassnoi
fonte
Quando desenho os conjuntos, entendi por que o segundo caso é mais consistente. Na consulta anterior, a restrição a.id = 1se aplica apenas à interseção, não à parte esquerda excluindo a interseção.
FtheBuilder
1
No primeiro exemplo pode haver linhas onde a.id != 1, o outro só terá linhas onde a.id = 1.
FtheBuilder
1
Sua linguagem não é clara. "Logicamente, você deve fazer a operação que ainda faz sentido se ..." e "logicamente mais consistente" não fizerem sentido. Você pode reformular?
philipxy
23

Para junções internas, não importa onde você coloca seus critérios. O compilador SQL transformará ambos em um plano de execução no qual a filtragem ocorre abaixo da junção (ou seja, como se as expressões de filtro aparecessem na condição de junção).

Junções externas são uma questão diferente, pois o lugar do filtro muda a semântica da consulta.

Remus Rusanu
fonte
Portanto, em junções internas, ele primeiro calcula o filtro e depois junta a saída do filtro com a outra tabela ou primeiro junta as duas tabelas e depois aplica o filtro?
Ashwin
@Remus Rusanu - você poderia explicar como a semântica é alterada no caso de junção externa? Obtenho resultados diferentes com base na posição do filtro, mas não consigo entender o porquê
Ananth
3
@Ananth com uma junção externa você obtém NULLs para todas as colunas da tabela junta onde a condição JOIN não corresponde. Os filtros não irão satisfazer o NULL e eliminar as linhas, transformando a junção OUTER em efeito em uma junção INNER.
Remus Rusanu
10

Até onde vão os dois métodos.

  • JOIN / ON é para unir mesas
  • WHERE é para filtrar resultados

Embora você possa usá-los de maneira diferente, sempre parece um cheiro para mim.

Lide com o desempenho quando for um problema. Então, você pode examinar essas "otimizações".

Robin Day
fonte
2

Com qualquer otimizador de consulta que custa um centavo ... eles são idênticos.

TomTom
fonte
Tenho certeza de que, com qualquer carga de trabalho real, eles não são idênticos. Se você quase não tem dados, a pergunta é inútil.
eKek0
2
Confira em carga de trabalho real. Basicamente - se eles geram o mesmo plano de execução, eles ... têm desempenho idêntico. Pelo menos para casos normais / simples (ou seja, não aquele que junta 14 mesas), estou certo de que são idênticos;)
TomTom
1

No postgresql, eles são iguais. Sabemos disso porque se você fizer explain analyzeem cada uma das consultas, o plano acaba sendo o mesmo. Veja este exemplo:

# 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)

Ambos têm o mesmo custo mínimo e máximo, bem como o mesmo plano de consulta. Além disso, observe que mesmo na consulta superior, team_score_2 é aplicado como um 'Filtro'.

Peter Graham
fonte
0

É realmente improvável que o posicionamento dessa junção seja o fator decisivo para o desempenho. Não estou intimamente familiarizado com o planejamento de execução para tsql, mas é provável que eles sejam otimizados automaticamente para planos semelhantes.

Joseph Mastey
fonte
0

Regra # 0: Execute alguns benchmarks e veja! A única maneira de realmente saber qual será o mais rápido é experimentando. Esses tipos de benchmarks são muito fáceis de executar usando o SQL Profiler.

Além disso, examine o plano de execução da consulta escrita com uma cláusula JOIN e WHERE para ver quais diferenças se destacam.

Finalmente, como outros já disseram, esses dois devem ser tratados de forma idêntica por qualquer otimizador decente, incluindo aquele embutido no SQL Server.

3Dave
fonte
Mas apenas para junções internas. O conjunto de resultados será muito diferente para as associações externas.
HLGEM
Claro. Felizmente, o exemplo fornecido usa junções internas.
3Dave
1
Infelizmente, a questão é sobre junções, não junções internas.
Paul,
Sim David, a questão é sobre junções. O exemplo que dá suporte à pergunta usa junções internas.
Paul,
0

É mais rápido? Experimente e veja.

O que é mais fácil de ler? O primeiro para mim parece mais "correto", já que a condição movida não tem nada a ver com a junção.

David M
fonte
0

Acho que é o primeiro, porque faz um filtro mais específico sobre os dados. Mas você deve ver o plano de execução , como acontece com qualquer otimização, porque pode ser muito diferente dependendo do tamanho dos dados, hardware do servidor, etc.

eKek0
fonte