Quelle requête SQL est la plus rapide? Filtrer sur les critères de jointure ou la clause Where?

98

Comparez ces 2 requêtes. Est-il plus rapide de mettre le filtre sur les critères de jointure ou dans la WHEREclause. J'ai toujours pensé que c'était plus rapide sur les critères de jointure car cela réduisait le jeu de résultats le plus tôt possible, mais je ne sais pas avec certitude.

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

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

ÉDITER

J'ai fait quelques tests et les résultats montrent que c'est en fait très proche, mais la WHEREclause est en fait légèrement plus rapide! =)

Je suis tout à fait d'accord qu'il est plus logique d'appliquer le filtre sur l' WHEREarticle, j'étais simplement curieux de connaître les implications sur le rendement.

TEMPS ÉCOULÉ O CRITÈRES: 143016 ms
TEMPS ÉCOULÉ CRITÈRES DE JOINT : 143256 ms

TESTER

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
la source
10
En fonction des données, les critères WHERE vs JOIN peuvent renvoyer différents ensembles de résultats.
OMG Ponies
4
@OMG Ponies est très vrai, mais souvent ce n'est pas aussi le cas.
Jon Erickson
2
Je n'appellerais pas la différence inférieure à 5% comme une différence - ce sont les mêmes. Vous voulez une signification pour une différence de 2 %%. Mieux vaut exécuter les tests 1000 fois pour vous assurer que ce n'est pas seulement aléatoire.
TomTom
L'avantage est de filtrer les données avant de rejoindre, donc s'il s'agissait de x.ID, vous auriez plus de chances de voir une amélioration qu'avec un a.ID
MikeT

Réponses:

65

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

Logiquement, vous devez effectuer l'opération qui a encore du sens si vous remplacez INNER JOINpar 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

La première requête ne renverra aucune correspondance réelle pour a.idautre que 1, donc la dernière syntaxe (avec WHERE) est logiquement plus cohérente.

Quassnoi
la source
Quand je dessine les décors, j'ai compris pourquoi le deuxième cas est plus cohérent. Dans l'ancienne requête, la contrainte a.id = 1s'applique uniquement à l'intersection, et non à la partie gauche excluant l'intersection.
FtheBuilder
1
Dans le premier exemple, il peut y avoir des lignes où a.id != 1, l'autre n'aura que des lignes où a.id = 1.
FtheBuilder
1
Votre langage n'est pas clair. "Logiquement, vous devriez rendre l'opération qui a encore du sens si ..." et "logiquement plus cohérente" n'ont pas de sens. Pouvez-vous reformuler?
philipxy
24

Pour les jointures internes, peu importe où vous mettez 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 une autre affaire, car l'emplacement du filtre modifie la sémantique de la requête.

Remus Rusanu
la source
Ainsi, dans les jointures internes, il calcule d'abord le filtre, puis joint la sortie du filtre avec l'autre table ou joint-t-il d'abord les deux tables, puis applique le filtre?
Ashwin
@Remus Rusanu - pouvez-vous expliquer comment la sémantique est modifiée en cas de jointure externe? J'obtiens des résultats différents en fonction de la position du filtre, mais je n'arrive pas à comprendre pourquoi
Ananth
3
@Ananth avec une jointure externe, vous obtenez des valeurs NULL pour toutes les colonnes de la table jointe où la condition JOIN ne correspond pas. Les filtres ne satisferont pas la valeur NULL et élimineront les lignes, transformant ainsi la jointure OUTER en une jointure INNER.
Remus Rusanu
@Ananth J'ai réalisé mes optimisations requises en fonction de votre commentaire. Ma modification était de WHERE x.TableAID = a.ID ou x.TableAID est nul à ON x.TableAID = a.ID. La modification de l'emplacement du filtre sur une jointure OUTER indique au compilateur de filtrer puis de joindre plutôt que de joindre puis de filtrer. Il a également pu utiliser l'index sur cette colonne car il ne devait pas nécessairement correspondre à Null. La réponse à la requête est passée de 61 secondes à 2 secondes.
Ben Gripka le
10

En ce qui concerne les deux méthodes.

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

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

Gérez les performances quand c'est un problème. Ensuite, vous pouvez examiner ces «optimisations».

Jour de Robin
la source
2

Avec n'importe quel optimiseur de requêtes à un centime .... ils sont identiques.

TomTom
la source
Je suis sûr que, quelle que soit la charge de travail réelle, ils ne sont pas identiques. Si vous n'avez presque pas de données, la question est sans valeur.
eKek0
2
Vérifiez-le sous une charge de travail réelle. Fondamentalement, s'ils génèrent le même plan d'exécution, ils ... ont des performances identiques. Au moins pour les cas normaux / simples (c'est-à-dire pas celui joignant 14 tables), je suis tout à fait sûr qu'ils sont identiques;)
TomTom
1

Dans postgresql, ce sont les mêmes. Nous le savons car si vous faites explain analyzesur chacune des requêtes, le plan s'avère être le même. Prenons 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 les deux le même coût minimum et maximum ainsi que le même plan de requête. Notez également que même dans la requête du haut, team_score_2 est appliqué en tant que «filtre».

Peter Graham
la source
0

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

Joseph Mastey
la source
0

Règle n ° 0: exécutez quelques benchmarks et voyez! La seule façon de vraiment 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 un JOIN et une clause WHERE pour voir quelles différences ressortent.

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

3Dave
la source
Mais uniquement pour les jointures internes. L'ensemble de résultats sera très différent pour les jointures out.
HLGEM
Bien sûr. Heureusement, l'exemple fourni utilise des jointures internes.
3Dave
1
Malheureusement, la question concerne les jointures, pas les jointures internes.
Paul
Oui David, la question concerne les jointures. L'exemple prenant en charge la question utilise des jointures internes.
Paul
0

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

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

David M
la source
0

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 il peut être très différent en fonction de la taille des données, du matériel du serveur, etc.

eKek0
la source