我有四个表,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
是否有更高效的方法来执行此连接?
考虑到暴露的查询很少;一个非常粗略的经验法则是用 Union 替换 Or 以避免表扫描。
Select..
LEFT JOIN Child c ON c.ParentAId = a.ParentAId
union
Select..
left Join Child c ON c.ParentBId = b.ParentBId
这就是我最终所做的,将执行时间从 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)。
感谢所有花时间回答的人。
您应该注意在 On 内使用谓词。
“理解这一点非常重要:对于外连接,ON 和 WHERE 子句扮演着非常不同的角色,因此它们不可互换。WHERE 子句仍然扮演着一个简单的过滤角色——即,它保留真实的情况并丢弃错误和未知的情况。使用这样的内容并在 where 子句中使用谓词。但是,ON 子句不起到简单的过滤作用;相反,它更多的是匹配作用。换句话说,保留一侧的行。无论 ON 谓词是否找到匹配,都会返回,因此 ON 谓词仅确定非保留端的哪些行与保留端的行匹配,而不是是否返回保留端的行。” **考试 70-461:查询 Microsoft SQL Server 2012
另一种写法:
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
中的演示
只是为这个答案的未来观察者添加一些内容 - 有时如上所述的 UNION 并不合适,因为 JOIN 可能位于需要大量复制的大查询中间。这就是 APPLY 派上用场的地方,因为您可以使用它而无需复制整个外部查询,因为它可以访问外部查询中的列。 注意:这仅参考 SQL Server。
SELECT *
FROM TopLevelParent tlp
LEFT JOIN MidParentA a
ON tlp.TopLevelPatientId = a.TopLevelPatientId
LEFT JOIN MidParentB a
ON tlp.TopLevelPatientId = b.TopLevelPatientId
OUTER APPLY (
SELECT * FROM Child WHERE Child.ParentAId = a.ParentAId
UNION
SELECT * FROM Child WHERE Child.ParentBId = b.ParentBId
) c
我面临着类似的问题,如下所示:
SELECT Y.name ... FROM X
JOIN Y ON X (y.value = 'foo' AND X.id = Y.foo_id...) OR
(y.value = 'bar' AND some other complex stuff)
并通过像这样重写来解决它:
SELECT Y.name .... FROM X
LEFT JOIN Y AS yfoo on (yfoo.value = 'foo' AND yfoo.foo_id = x.Id)
LEFT JOIN Y AS ybar on (ybar.value = 'bar' AND some other complex stuff)
JOIN Y ON Y.id IN (yfoo.id, ybar.id) -- 'magic' happens here.
不再是使用 OR 进行一次连接,而是将其变成了三个连接。将每个条件置于单独的联接和最终联接中,以从第一个联接或第二个联接中获取一个匹配行。主要是为了避免诸如
ISNULL(yfoo.name, ybar.name)
之类的东西