Ce n'est absolument pas une réponse canonique mais j'ai remarqué que pour les plans de requête de boucles imbriquées montrés dans le SQL Fiddle, il était possible d'appliquer le plan de la requête 2 à la requête 1 avec l'utilisation de l' USE PLAN
indice, mais la tentative d'opération inverse échoue avec
Le processeur de requêtes n'a pas pu produire de plan de requête car l'indication USE PLAN contient un plan qui n'a pas pu être vérifié pour être légal pour la requête. Supprimez ou remplacez l'indication USE PLAN. Pour une meilleure probabilité de réussite du forçage de plan, vérifiez que le plan fourni dans l'indicateur USE PLAN est généré automatiquement par SQL Server pour la même requête.
La désactivation de la règle de transformation de l'optimiseur ReorderLOJN
empêche également l'indicateur de plan précédemment réussi de réussir.
Expérimenter avec de plus grandes quantités de données montre que SQL Server est certainement capable de transformer (A LOJ B) LOJ C
à A LOJ (B LOJ C)
naturellement aussi bien mais je ne vois aucune preuve que l'inverse est vrai.
Un cas très artificiel où la première requête fonctionne mieux que la seconde est
DROP TABLE MyGrandChild , MyChild, MyParent
CREATE TABLE MyParent
(Id int)
CREATE TABLE MyChild
(Id int PRIMARY KEY
,ParentId int,
Filler char(8000) NULL)
CREATE TABLE MyGrandChild
(Id int
,ParentId int)
INSERT INTO MyChild
(Id, ParentId)
SELECT TOP (100000) ROW_NUMBER() OVER (ORDER BY @@SPID),
ROW_NUMBER() OVER (ORDER BY @@SPID)
FROM master..spt_values v1, master..spt_values
INSERT INTO MyGrandChild
(Id, ParentId)
OUTPUT INSERTED.Id INTO MyParent
SELECT TOP (3000) Id, Id AS ParentId
FROM MyChild
ORDER BY Id
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT gc.Id AS gcId,
gc.ParentId AS gcpId,
c.Id AS cId,
c.ParentId AS cpId,
p.Id AS pId
FROM MyGrandChild AS gc
LEFT OUTER JOIN MyChild AS c
ON c.[Id] = gc.[ParentId]
LEFT OUTER JOIN MyParent AS p
ON p.[Id] = c.[ParentId]
SELECT gc.Id AS gcId,
gc.ParentId AS gcpId,
c.Id AS cId,
c.ParentId AS cpId,
p.Id AS pId
FROM MyGrandChild AS gc
LEFT OUTER JOIN( MyChild AS c
LEFT OUTER JOIN MyParent AS p
ON p.[Id] = c.[ParentId])
ON c.[Id] = gc.[ParentId]
Ce qui donne des plans
Pour moi, la requête 1 avait un temps écoulé de 108 ms contre 1163 ms pour la requête 2.
Requête 1
Table 'Worktable'. Scan count 0, logical reads 0
Table 'MyChild'. Scan count 0, logical reads 9196
Table 'MyGrandChild'. Scan count 1, logical reads 7
Table 'MyParent'. Scan count 1, logical reads 5
Requête 2
Table 'MyParent'. Scan count 1, logical reads 15000
Table 'MyChild'. Scan count 0, logical reads 9000
Table 'MyGrandChild'. Scan count 1, logical reads 7
On peut donc supposer provisoirement que la première syntaxe ("non imbriquée") est potentiellement bénéfique car elle permet de considérer plus d'ordres de jointure potentiels, mais je n'ai pas fait de tests suffisamment exhaustifs pour avoir une grande confiance en cela en règle générale.
Il peut être tout à fait possible de trouver des contre-exemples où Query 2 fonctionne mieux. Essayez les deux et regardez les plans d'exécution.