34

我有四个表,TopLevelParent,两个中级表 MidParentA 和 MidParentB,以及一个可以有 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

4 回答 4

32

鉴于公开的查询很少;一个非常粗略的经验法则是将 Or 替换为 Union 以避免表扫描。

Select..
LEFT JOIN Child c ON c.ParentAId = a.ParentAId 
union
Select..
left Join Child c ON c.ParentBId = b.ParentBId
于 2013-11-01T10:41:41.037 回答
8

这是我最后所做的,它使执行时间从 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 位标志来区分哪里有两个相同的 MidParentId(即 ID 为 1 IsMidParentA 和 IsMidParentB)。

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

于 2013-11-01T13:33:10.440 回答
7

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

“了解外连接非常重要,ON 和 WHERE 子句扮演非常不同的角色,因此,它们不能互换。WHERE 子句仍然扮演一个简单的过滤角色——即,它保留真实的情况并丢弃假和未知的情况。使用这样的东西,在where子句中使用谓词。但是,ON子句并没有起到简单的过滤作用,而是更多的匹配作用。换句话说,保留侧的一行将是返回 ON 谓词是否找到匹配项。因此,ON 谓词仅确定非保留端的哪些行与保留端的行匹配,而不是是否返回保留端的行。**考试 70-461:查询 Microsoft SQL Server 2012

于 2013-11-01T10:37:45.527 回答
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中的演示

于 2013-11-01T10:58:12.937 回答