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
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.
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.
En ce qui concerne les deux méthodes.
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".
Avec n'importe quel optimiseur de requête, un centime .... ils sont identiques.
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".
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.
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.
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.
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.