web-dev-qa-db-fra.com

Jointure gauche de SQL Server avec l'opérateur «Ou»

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?

18
Declan McNulty

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.

4
Declan McNulty

É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
17
u07ch

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

5
Amir Keshavarz

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

1
mucio