SQL Server左连接'或'运算符

Dec*_*lty 21 sql t-sql sql-server left-join

我有一个四个表,TopLevelParent,两个中级表MidParentA和MidParentB,以及一个子表,可以有一个父MidParentA或MidParentB(一个或另一个midParent必须到位).两个中级表都有一个TopLevelParent父表.

顶级表格如下所示:

TopLevelId | Name
--------------------------
1          | name1   
2          | name2   
Run Code Online (Sandbox Code Playgroud)

MidParent表如下所示:

MidParentAId | TopLevelParentId |           MidParentBId | TopLevelParentId |
------------------------------------       ------------------------------------
1            |        1         |           1            |        1         |
2            |        1         |           2            |        1         |
Run Code Online (Sandbox Code Playgroud)

子表看起来像这样:

ChildId | MidParentAId | MidParentBId
--------------------------------
1       |     1        |   NULL
2       |    NULL      |     2
Run Code Online (Sandbox Code Playgroud)

我在一个更大的存储过程中使用了下面的左连接,它是超时的,看起来最后一个左连接的OR运算符是罪魁祸首:

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
Run Code Online (Sandbox Code Playgroud)

是否有更高效的方式来加入?

u07*_*7ch 19

鉴于查询的公开程度很小; 一个非常粗略的经验法则是用联合替换Or以避免表扫描.

Select..
LEFT JOIN Child c ON c.ParentAId = a.ParentAId 
union
Select..
left Join Child c ON c.ParentBId = b.ParentBId
Run Code Online (Sandbox Code Playgroud)


Ami*_*arz 6

您应该注意在On内部使用谓词。

“重要的是要了解,通过外部联接,ON和WHERE子句扮演的角色非常不同,因此它们是不可互换的。WHERE子句仍然扮演着简单的过滤角色-即,它保留真实的情况并丢弃错误的和未知的情况。使用类似的东西并在where子句中使用谓词。但是,ON子句不扮演简单的过滤角色;它更像是匹配角色,换句话说,保留侧的行将是会返回ON谓词是否找到匹配的对象。因此,ON谓词仅确定非保留侧的行与保留侧的行匹配,而不确定是否从保留侧返回行。**考试70-461:查询Microsoft SQL Server 2012


Dec*_*lty 5

这是我最后所做的,这使执行时间从52秒减少到4秒。

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
Run Code Online (Sandbox Code Playgroud)

这消除了正在发生的表扫描,因为我已经将顶级记录与它的中级父级(如果存在)进行了匹配,并将其标记在该记录上。

我也对子记录进行了相同的操作,这意味着我可以将子记录加入MidParentId的顶级记录中,并使用IsMidParentA位标志来区分存在两个相同的MidParentId的位置(即,对于IsMidParentA和IsMidParentB)。

感谢所有花时间回答的人。

  • 这有点复杂 (9认同)
  • 事情迅速升级 (5认同)