web-dev-qa-db-fra.com

Quelle requête SQL est plus rapide? Filtrer sur Critères d'adhésion ou clause Where?

Comparez ces 2 requêtes. Est-il plus rapide de placer le filtre sur les critères de jointure ou dans la clause was. J'ai toujours pensé qu'il est plus rapide sur les critères de jointure car il réduit le jeu de résultats le plus tôt possible, mais je n'en suis pas sûr.

Je vais construire des tests pour voir, mais je voulais aussi avoir des opinions sur ce qui serait plus clair à lire également.

Requête 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? */

Requête 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

MODIFIER

J'ai effectué quelques tests et les résultats montrent qu'il est en fait très proche, mais la clause WHERE est en fait légèrement plus rapide! =)

Je suis absolument d'accord qu'il est plus logique d'appliquer le filtre sur la clause WHERE, j'étais juste curieux quant aux implications en termes de performances.

TEMPS ÉCOULÉ OERE LES CRITÈRES: 143016 ms
TEMPS ÉCOULÉ JOINDRE LES CRITÈRES: 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
83
Jon Erickson

En termes de performances, ils sont les mêmes (et produisent les mêmes plans)

Logiquement, vous devez effectuer l'opération qui a toujours du sens si vous remplacez INNER JOIN avec un LEFT JOIN.

Dans votre cas, cela ressemblera à ceci:

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

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

L'ancienne requête ne renverra aucune correspondance réelle pour a.id autre que 1, donc la dernière syntaxe (avec WHERE) est logiquement plus cohérente.

58
Quassnoi

Pour les jointures internes, peu importe où vous placez vos critères. Le compilateur SQL transformera les deux en un plan d'exécution dans lequel le filtrage se produit sous la jointure (c'est-à-dire comme si les expressions de filtre apparaissaient dans la condition de jointure).

Les jointures externes sont différentes, car la place du filtre modifie la sémantique de la requête.

19
Remus Rusanu

En ce qui concerne les deux méthodes.

  • JOIN/ON sert à joindre des tables
  • O is est pour filtrer les résultats

Bien que vous puissiez les utiliser différemment, cela me semble toujours une odeur.

Gérez les performances en cas de problème. Ensuite, vous pouvez examiner ces "optimisations".

7
Robin Day

Avec n'importe quel optimiseur de requête, un centime .... ils sont identiques.

3
TomTom

En postgresql, ce sont les mêmes. Nous le savons parce que si vous le faites explain analyze pour chacune des requêtes, le plan est le même. Prenez cet exemple:

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

Ils ont tous deux le même coût min et max ainsi que le même plan de requête. Notez également que même dans la requête supérieure, team_score_2 est appliqué en tant que "filtre".

0
Peter Graham

Je suppose que le premier, car il fait un filtre plus spécifique sur les données. Mais vous devriez voir le plan d'exécution , comme pour toute optimisation, car cela peut être très différent selon la taille des données, le matériel du serveur, etc.

0
eKek0

Est-ce plus rapide? Essayez-le et voyez.

Quel est le plus facile à lire? Le premier me semble plus "correct", car la condition déplacée n'a rien à voir avec la jointure.

0
David M

Il est très peu probable que le placement de cette jointure soit le facteur décisif pour les performances. Je ne connais pas intimement la planification d'exécution de tsql, mais il est probable qu'ils seront optimisés automatiquement pour des plans similaires.

0
Joseph Mastey

Règle n ° 0: exécutez quelques repères et voyez! La seule façon de savoir ce qui sera le plus rapide est de l'essayer. Ces types de benchmarks sont très faciles à réaliser à l'aide du profileur SQL.

Examinez également le plan d'exécution de la requête écrite avec une clause JOIN et avec une clause WHERE pour voir quelles différences ressortent.

Enfin, comme d'autres l'ont dit, ces deux devraient être traités de manière identique par tout optimiseur décent, y compris celui intégré à SQL Server.

0
3Dave