SQL Server 使用“或”运算符左连接
Posted
技术标签:
【中文标题】SQL Server 使用“或”运算符左连接【英文标题】:SQL Server Left Join With 'Or' Operator 【发布时间】:2013-11-12 13:12:59 【问题描述】:我有四个表,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 |
子表如下所示:
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
有没有更高效的方法来执行此连接?
【问题讨论】:
好问题+好答案 【参考方案1】:鉴于公开的查询很少;一个非常粗略的经验法则是将 Or 替换为 Union 以避免表扫描。
Select..
LEFT JOIN Child c ON c.ParentAId = a.ParentAId
union
Select..
left Join Child c ON c.ParentBId = b.ParentBId
【讨论】:
这有帮助 :) 你可以有两个连接到 Child 而不是每个连接一个 OR 的一侧;然后使用 coalesce 从 C 或 D 获取一个值,您只需要一个值【参考方案2】:这是我最后所做的,它将执行时间从 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 IsMidParentA 和 IsMidParentB 为 1)。
感谢所有花时间回答的人。
【讨论】:
这是一些复杂的业务 事态迅速升级【参考方案3】:您应该注意在 On 中使用谓词。
“理解这一点非常重要,在外连接中,ON 和 WHERE 子句扮演着非常不同的角色,因此它们不能互换。WHERE 子句仍然扮演着简单的过滤角色——即,它保持真case 并丢弃错误和未知的情况。使用这样的东西并在 where 子句中使用谓词。但是,ON 子句并没有起到简单的过滤作用,而是更多的匹配作用。换句话说,保留中的一行side 将被返回,无论 ON 谓词是否找到匹配项。因此,ON 谓词仅确定非保留端的哪些行与保留端的行匹配,而不是是否返回保留端的行。 **考试 70-461:查询 Microsoft SQL Server 2012
【讨论】:
【参考方案4】:另一种写法:
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 中的演示
【讨论】:
原始查询有 c.ParentBId = b.ParentBId 而不是 c.parentAID = b.parentBID 所以这可能不正确 你是对的,乍一看我没有仔细阅读问题以上是关于SQL Server 使用“或”运算符左连接的主要内容,如果未能解决你的问题,请参考以下文章