J'ai quatre tables, TopLevelParent, deux tables de niveau intermédiaire MidParentA et MidParentB, et une table enfant qui peut avoir un parent de MidParentA ou MidParentB (l'un ou l'autre midParent doit être en place). Les deux tables de niveau intermédiaire ont une table parent de TopLevelParent.
La table de niveau supérieur ressemble à ceci:
TopLevelId | Name
--------------------------
1 | name1
2 | name2
Les tables MidParent ressemblent à ceci:
MidParentAId | TopLevelParentId | MidParentBId | TopLevelParentId |
------------------------------------ ------------------------------------
1 | 1 | 1 | 1 |
2 | 1 | 2 | 1 |
La table enfant ressemble à ceci:
ChildId | MidParentAId | MidParentBId
--------------------------------
1 | 1 | NULL
2 | NULL | 2
J'ai utilisé la jointure gauche suivante dans une procédure stockée plus grande qui expire, et il semble que l'opérateur OR sur la dernière jointure gauche est le coupable:
SELECT *
FROM TopLevelParent tlp
LEFT JOIN MidParentA a ON tlp.TopLevelPatientId = a.TopLevelPatientId
LEFT JOIN MidParentB a ON tlp.TopLevelPatientId = b.TopLevelPatientId
LEFT JOIN Child c ON c.ParentAId = a.ParentAId OR c.ParentBId = b.ParentBId
Existe-t-il un moyen plus performant de faire cette jointure?
Voici ce que j'ai fait à la fin, qui a réduit le temps d'exécution de 52 à 4 secondes.
SELECT *
FROM (
SELECT tpl.*, a.MidParentAId as 'MidParentId', 1 as 'IsMidParentA'
FROM TopLevelParent tpl
INNER JOIN MidParentA a ON a.TopLevelParentId = tpl.TopLevelParentID
UNION
SELECT tpl.*, b.MidParentBId as 'MidParentId', 0 as 'IsMidParentA'
FROM TopLevelParent tpl
INNER JOIN MidParentB b ON b.TopLevelParentId = tpl.TopLevelParentID
UNION
SELECT tpl.*, 0 as 'MidParentId', 0 as 'IsMidParentA'
FROM TopLevelParent tpl
WHERE tpl.TopLevelParentID NOT IN (
SELECT pa.TopLevelParentID
FROM TopLevelParent tpl
INNER JOIN MidParentA a ON a.TopLevelParentId = tpl.TopLevelParentID
UNION
SELECT pa.TopLevelParentID
FROM TopLevelParent tpl
INNER JOIN MidParentB b ON h.TopLevelParentId = tpl.TopLevelParentID
)
) tpl
LEFT JOIN MidParentA a ON a.TopLevelParentId = tpl.TopLevelParentID
LEFT JOIN MidParentB b ON b.TopLevelParentId = tpl.TopLevelParentID
LEFT JOIN
(
SELECT [ChildId]
,[MidParentAId] as 'MidParentId'
,1 as 'IsMidParentA'
FROM Child c
WHERE c.MidParentAId IS NOT NULL
UNION
SELECT [ChildId]
,[MidParentBId] as 'MidParentId'
,0 as 'IsMidParentA'
FROM Child c
WHERE c.MidParentBId IS NOT NULL
) AS c
ON c.MidParentId = tpl.MidParentId AND c.IsMidParentA = tpl.IsMidParentA
Cela élimine l'analyse de table qui se produisait, car j'ai fait correspondre l'enregistrement de niveau supérieur à son parent de niveau intermédiaire à l'avance s'il existe, et l'ai tamponné sur cet enregistrement.
J'ai également fait la même chose avec l'enregistrement enfant, ce qui signifie que je peux simplement joindre l'enregistrement enfant à l'enregistrement de niveau supérieur sur MidParentId, et j'utilise l'indicateur de bit IsMidParentA pour différencier où il y a deux MidParentIds identiques (c'est-à-dire un ID de 1 pour IsMidParentA et IsMidParentB).
Merci à tous ceux qui ont pris le temps de répondre.
Étant donné le peu de la requête est exposée; une règle de base très approximative consiste à remplacer un Or par un Union pour éviter la numérisation de la table.
Select..
LEFT JOIN Child c ON c.ParentAId = a.ParentAId
union
Select..
left Join Child c ON c.ParentBId = b.ParentBId
Vous devez prendre soin d'utiliser les prédicats dans On.
"Il est très important de comprendre que, avec les jointures externes, les clauses ON et WHERE jouent des rôles très différents, et par conséquent, elles ne sont pas interchangeables. La clause WHERE joue toujours un rôle de filtrage simple - à savoir, elle conserve les cas réels et les supprime cas faux et inconnus. Utilisez quelque chose comme ceci et utilisez des prédicats dans la clause where. Cependant, la clause ON ne joue pas un rôle de filtrage simple; il s'agit plutôt d'un rôle de correspondance. En d'autres termes, une ligne du côté préservé sera a renvoyé si le prédicat ON y trouve une correspondance ou non. Le prédicat ON détermine donc uniquement les lignes du côté non préservé qui sont mises en correspondance avec les lignes du côté préservé, et non le renvoi des lignes du côté préservé. " ** Examen 70-461: interrogation de Microsoft SQL Server 2012
une autre façon de l'écrire:
LEFT JOIN Child c ON c.ParentAId = COALESCE(a.ParentAId, b.ParentBId)
Modifier
Une approche possible consiste à interroger d'abord le MidParentA puis le MidParentB puis UNION
les résultats:
SELECT tlp.*,
a.MidParentAId,
null MidParentBId,
c.ChildId
FROM TopLevelParent tlp
LEFT JOIN MidParentA a ON tlp.TopLevelPatientId = a.TopLevelPatientId
LEFT JOIN Child c ON c.MidParentAId = a.MidParentAId
UNION
SELECT tlp.*,
null MidParentAId,
b.MidParentBId,
c.ChildId
FROM TopLevelParent tlp
LEFT JOIN MidParentB b ON tlp.TopLevelPatientId = b.TopLevelPatientId
LEFT JOIN Child c ON c.MidParentBId = b.MidParentBId
Une démo en SQLFiddle