¿Qué consulta SQL es más rápida? ¿Filtrar por criterios de unión o cláusula Where?

98

Compare estas 2 consultas. ¿Es más rápido poner el filtro en los criterios de combinación o en la WHEREcláusula? Siempre he sentido que es más rápido en los criterios de combinación porque reduce el conjunto de resultados en el momento más rápido posible, pero no estoy seguro.

Voy a construir algunas pruebas para ver, pero también quería obtener opiniones sobre cuál sería más claro de leer.

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

Ejecuté algunas pruebas y los resultados muestran que en realidad está muy cerca, ¡pero la WHEREcláusula es en realidad un poco más rápida! =)

Estoy absolutamente de acuerdo en que tiene más sentido aplicar el filtro a la WHEREcláusula, solo tenía curiosidad por las implicaciones de rendimiento.

TIEMPO TRANSCURRIDO DONDE LOS CRITERIOS: 143016 ms
TIEMPO TRANSCURRIDO UNIR LOS CRITERIOS: 143256 ms

PRUEBA

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
fuente
10
Dependiendo de los datos, los criterios WHERE vs JOIN pueden devolver diferentes conjuntos de resultados.
OMG Ponies
4
@OMG Ponies es muy cierto, pero muchas veces no es así.
Jon Erickson
2
No llamaría a la diferencia por debajo del 5% como una diferencia, son lo mismo. Es mejor ejecutar las pruebas 1000 veces la significancia para una diferencia del 2 %% para asegurarse de que no sea simplemente aleatorio.
TomTom
El beneficio es filtrar los datos antes de unirse, por lo que si fuera x.ID, sería más probable que veas una mejora que con un a.ID
MikeT

Respuestas:

65

En cuanto al rendimiento, son iguales (y producen los mismos planes)

Lógicamente, debe realizar la operación que aún tiene sentido si reemplaza INNER JOINcon un LEFT JOIN.

En su caso, esto se verá así:

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 esto:

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 consulta anterior no devolverá coincidencias reales para a.idotro que 1, por lo que la última sintaxis (con WHERE) es lógicamente más coherente.

Quassnoi
fuente
Cuando dibujé los conjuntos entendí por qué el segundo caso es más consistente. En la consulta anterior, la restricción se a.id = 1aplica solo a la intersección, no a la parte izquierda que excluye la intersección.
FtheBuilder
1
En el primer ejemplo puede haber filas donde a.id != 1, el otro solo tendrá filas donde a.id = 1.
FtheBuilder
1
Tu idioma no es claro. "Lógicamente, debería hacer la operación que todavía tiene sentido si ..." y "lógicamente más coherente" no tienen sentido. ¿Puedes reformular?
philipxy
24

Para las uniones internas, no importa dónde pongas tus criterios. El compilador SQL transformará ambos en un plan de ejecución en el que el filtrado ocurre debajo de la combinación (es decir, como si las expresiones de filtro aparecieran en la condición de combinación).

Las combinaciones externas son un asunto diferente, ya que el lugar del filtro cambia la semántica de la consulta.

Remus Rusanu
fuente
Entonces, en las combinaciones internas, primero calcula el filtro y luego une la salida del filtro con la otra tabla o ¿primero une las dos tablas y luego aplica el filtro?
Ashwin
@Remus Rusanu: ¿podría explicar cómo se cambia la semántica en el caso de Outer-join? Obtengo resultados diferentes según la posición del filtro, pero no puedo entender por qué
Ananth
3
@Ananth con una combinación externa, obtiene NULL para todas las columnas de la tabla unida donde la condición JOIN no coincide. Los filtros no satisfarán el NULL y eliminarán las filas, convirtiendo la combinación EXTERIOR en efecto en una combinación INTERIOR.
Remus Rusanu
@Ananth Logré mis optimizaciones requeridas según tu comentario. Mi cambio fue de DONDE x.TableAID = a.ID o x.TableAID es nulo a ON x.TableAID = a.ID. Al cambiar la ubicación del filtro en una combinación EXTERIOR, el compilador debe saber filtrar y luego unirse en lugar de unirse y luego filtrar. También pudo usar el índice en esa columna porque no tenía que coincidir con Null. La respuesta a la consulta cambió de 61 segundos a 2 segundos.
Ben Gripka
10

En cuanto a los dos métodos.

  • JOIN / ON es para unir tablas
  • DONDE es para filtrar los resultados

Si bien puedes usarlos de manera diferente, siempre me parece un olor.

Ocúpese del rendimiento cuando sea un problema. Entonces puede examinar esas "optimizaciones".

Día de Robin
fuente
2

Con cualquier optimizador de consultas digno de un centavo ... son idénticos.

TomTom
fuente
Estoy bastante seguro de que, con cualquier carga de trabajo real, no son idénticos. Si casi no tiene datos, entonces la pregunta es inútil.
eKek0
2
Compruébelo bajo carga de trabajo real. Básicamente, si generan el mismo plan de ejecución, ... tienen un rendimiento idéntico. Al menos para casos normales / simples (es decir, no el que se une a 14 mesas) estoy bastante seguro de que son idénticos;)
TomTom
1

En postgresql son iguales. Lo sabemos porque si lo haces explain analyzeen cada una de las consultas, el plan resulta ser el mismo. Toma este ejemplo:

# 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 tienen el mismo costo mínimo y máximo, así como el mismo plan de consulta. Además, observe que incluso en la consulta superior, team_score_2 se aplica como un 'Filtro'.

Peter Graham
fuente
0

Es muy poco probable que la ubicación de esta unión sea el factor decisivo para el rendimiento. No estoy muy familiarizado con la planificación de la ejecución de tsql, pero es probable que se optimicen automáticamente para planes similares.

Joseph Mastey
fuente
0

Regla n. ° 0: ¡Ejecute algunos puntos de referencia y vea! La única forma de saber realmente cuál será más rápido es probándolo. Estos tipos de pruebas comparativas son muy fáciles de realizar utilizando el generador de perfiles SQL.

Además, examine el plan de ejecución para la consulta escrita con una JOIN y con una cláusula WHERE para ver qué diferencias se destacan.

Finalmente, como han dicho otros, estos dos deben ser tratados de manera idéntica por cualquier optimizador decente, incluido el integrado en SQL Server.

3Dave
fuente
Pero solo para uniones internas. El conjunto de resultados será muy diferente para las combinaciones externas.
HLGEM
Por supuesto. Afortunadamente, el ejemplo proporcionado usa combinaciones internas.
Salvar el
1
Lamentablemente, la pregunta se trata de combinaciones, no de combinaciones internas.
Paul
Sí David, la pregunta es sobre combinaciones. La muestra que apoya la pregunta usa combinaciones internas.
Paul
0

Es mas rapido? Pruébalo y verás.

¿Cuál es más fácil de leer? El primero me parece más "correcto", ya que la condición de movimiento no tiene nada que ver con la unión.

David M
fuente
0

Supongo que el primero, porque hace un filtro más específico sobre los datos. Pero debería ver el plan de ejecución , como con cualquier optimización, porque puede ser muy diferente según el tamaño de los datos, el hardware del servidor, etc.

eKek0
fuente