SQL Server左连接'或'运算符

时间:2013-11-01 10:33:08

标签: sql sql-server tsql left-join

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

顶级表格如下所示:

TopLevelId | Name
--------------------------
1          | name1   
2          | name2   

MidParent表格如下所示:

MidParentAId | TopLevelParentId |           MidParentBId | TopLevelParentId |
------------------------------------       ------------------------------------
1            |        1         |           1            |        1         |
2            |        1         |           2            |        1         |

Child表看起来像这样:

ChildId | MidParentAId | MidParentBId
--------------------------------
1       |     1        |   NULL
2       |    NULL      |     2

我在一个较大的存储过程中使用了以下左连接,这是一个超时,看起来左边连接的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

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

4 个答案:

答案 0 :(得分:17)

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

Select..
LEFT JOIN Child c ON c.ParentAId = a.ParentAId 
union
Select..
left Join Child c ON c.ParentBId = b.ParentBId

答案 1 :(得分:5)

你应该注意在On中使用谓词。

“理解使用外连接时,非常重要的是ON和WHERE子句扮演非常不同的角色,因此它们不可互换.WHERE子句仍然扮演一个简单的过滤角色 - 即,它保持真实使用类似的东西并在where子句中使用谓词。然而,ON子句不起简单的过滤作用;相反,它更像是一个匹配的角色。换句话说,保留了一行无论ON谓词是否找到匹配,都将返回side。因此ON谓词只确定来自非保留端的哪些行与保留端的行匹配 - 而不是是否从保留端返回行。 **考试70-461:查询Microsoft SQL Server 2012

答案 2 :(得分:4)

这是我最后所做的,它将执行时间从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

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

我也对子记录做了同样的事情,这意味着我可以将子记录加入MidParentId的顶级记录,并使用IsMidParentA位标志来区分有两个相同MidParentIds的位置(即Id为IsMidParentA和IsMidParentB提供1。

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

答案 3 :(得分:1)

另一种写作方式:

<德尔> LEFT JOIN Child c ON c.ParentAId = COALESCE(a.ParentAId, b.ParentBId)

修改

一种可能的方法是首先查询MidParentA,然后查询MidParentB,然后查询UNION结果:

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 

SQLFiddle

中的演示