使用 OR 连接的性能
Posted
技术标签:
【中文标题】使用 OR 连接的性能【英文标题】:Performance of joins with OR 【发布时间】:2012-01-08 11:47:28 【问题描述】:假设我有两张桌子:
Table A
ProdID | PartNumber | Data...
1 | ABC-a | "Data A"
2 | (null) | "Data B"
3 | ABC-c | "Data C"
...
和
Table B
ProdID | PartNumber | DataB
(null) | ABC-a | "Data D"
2 | (null) | "Data E"
3 | (null) | "Data F"
(null) | ABC-z | "Data G"
...
不理想,但无论如何。我想要
ProdID | PartNumber | Data | DataB...
1 | ABC-a | "Data A" | "Data D"
2 | (null) | "Data B" | "Data E"
3 | ABC-c | "Data C" | "Data F"
(null) | ABC-z | (null) | "Data G"
所以我用
SELECT *
FROM Table1 T1
RIGHT JOIN Table2 T2 ON
T1.ProdID = T2.ProdID OR T1.PartNumber = T2.PartNumber
这正是我想要的,但似乎需要大约 100 倍的时间或单独的任何一方。作为更复杂查询的一部分,OR
需要 2 分钟,而 int
需要不到 1 秒,nvarchar(50)
需要 1 秒。表“A”有 ~13k 行,表“b”有 ~35k,整个查询返回 ~40k。
查询计划
我认为这个“Table Spool”可能是问题所在。
SQL Server 2008 R2 Express。想法?
【问题讨论】:
您的结果与源数据不匹配,因为 TableA 和 TableB 的 ProdID 均为 4,但该记录的结果显示 ProdID 为空。源表之一应该为空,还是结果的 prodID 为 4? 我已经更新了示例,希望更加清晰。 天啊,做个好例子很难! 另见Is having an 'OR' in an INNER JOIN condition a bad idea? 【参考方案1】:分别加入每一种方式,然后组合结果:
SELECT T1.ProdID, T1.PartNumber, T1.Data, ISNULL(tprodid.DataB, tpartno.DataB) as DataB
FROM Table1 T1
LEFT JOIN Table2 tprodid ON T1.ProdID = tprodid.ProdID
LEFT JOIN Table2 tpartno ON T1.PartNumber = tpartno.PartNumber;
这将使用两个索引,并且性能良好。您可能需要根据自己的喜好调整 ISNULL
逻辑。
【讨论】:
立即,谢谢。我真的不喜欢通过使用 COALESCE 来“为它做它的工作”。 如果有人感兴趣,这是最后的查询。选择 COALESCE (a.ProdID, b1.ProdID, b2.ProdID) 作为 ProdID, COALESCE (a.PartNum, b1.PartNum, b2.PartNum) 作为 PartNum, a.Data COALESCE (b1.DataB, b2.DataB) 作为 DataB从表 1 作为右外连接表 2 作为 b2 在 a.ProdID = b2.ProdID 右外连接表 2 作为 b1 在 a.PartNum= b1.PartNum 您的代码没有运行(提示:T2
不是有效的相关名称),结果集与 OP 的不同(您的返回三行,OP 的有四行,而且您有缺少列)并且您已将 RIGHT
更改为 LEFT
没有明显的原因。您仍然获得三张赞成票和“正确答案”奖!你的秘诀是什么? ;)
哦,哇,这足以让我(OP)的大脑填补空白。不过,在原版中,右/左是我的错。
@onedaywhen 很抱歉 - 我没有安装 sqlserver,所以我无法测试它。我更正了别名问题。顺便说一句,LEFT
加入是正确的方式去恕我直言。 “我这样做的方式”是提供足够正确的答案 - 即使可能存在轻微的句法问题,也足以显示正确的方法*【参考方案2】:
将查询更改为联合,您应该会获得更好的性能:
Select * from Table1 Left Join Table2 On Table1.ProdID = Table2.ProdID
where Table1.PartNumber is null
union
Select * from Table1 Left Join Table2 On Table1.PartNumber = Table2.PartNumber
where Table1.ProdId is null
联合运算符将消除重复行。也就是说,两个查询返回的行只会返回一次。所以这应该返回与您的主查询相同的数据。
【讨论】:
这不会做他想做的事,因为它不会给数据 A 和数据 B 提供相同的关系 为什么不给Data A和Data B?这应该返回相同的结果。 联合的结果将只有三列。他正在寻找 4 列的结果。 现在看起来好多了。我可能错过了你加入工会两边的桌子。 必须这样做。联合中涉及的所有查询都必须返回相同数量的列,并且所有查询中的所有列必须具有相同的数据类型。【参考方案3】:您仍然需要 OR,但使用 FULL JOIN 可能会做得更好:
SELECT COALESCE(t1.ProdID,t2.ProdID) ProdID,
COALESCE(t1.PartNumber,t2.PartNumber) PartNumber,
t1.Data, t2.DataB
FROM TableA t1
FULL JOIN TableB t2 ON t1.ProdID = t2.ProdID OR t1.PartNumber = t2.PartNumber
性能缓慢的原因是 OR 强制它不能很好地与索引匹配,从而强制将一个整个表与另一个整个表进行手动比较。如果 FULL JOIN 仍然存在性能问题,您可以通过为部件号添加索引或使用索引提示告诉优化器您的 ProdID 索引仍然有用来修复它。
【讨论】:
您会使用样本数据获得 2 条零件编号 Abc-a 的记录吗? 不幸的是,这将在三分钟后出现:(【参考方案4】:虽然不太了解 MSSQL,但我会尝试至少为您的问题提供解决方案!
为您可能想要加入的每一列使用LEFT JOIN
,然后合并结果,应该会得到更好的结果,如下所示:
SELECT
COALESCE(TA.ProdID, TB2.ProdID) AS ProdID,
COALESCE(TA.PartNumber, TB.PartNumber) AS PartNumber,
TA.Data,
COALESCE(TB.Data2, TB2.Data2) AS Data2
FROM TableA TA
LEFT JOIN TableB TB On TA.ProdID = TB.ProdID
LEFT JOIN TableB TB2 On TA.PartNumber = TB2.PartNumber
GROUP BY ProdId
虽然完全是猜测,但我想说它可能仅限于每次连接仅使用一个索引,而是强制它使用全表扫描来执行其中一个列。您可以尝试将两列都放在一个索引中,并使用该索引作为连接的索引提示,看看它是如何执行的。
【讨论】:
【参考方案5】:我喜欢 Jeff Siver 关于使用 UNION
的建议,尽管他建议的查询是错误的。这是一个可能的解决方法:
SELECT *
FROM Table1 T1
JOIN Table2 T2
ON T1.ProdID = T2.ProdID
UNION
SELECT *
FROM Table1 T1
JOIN Table2 T2
ON T1.PartNumber = T2.PartNumber
UNION
SELECT NULL, NULL, NULL, *
FROM Table2 T2
WHERE NOT EXISTS (
SELECT *
FROM Table1 T1
WHERE T1.ProdID = T2.ProdID
)
AND NOT EXISTS (
SELECT *
FROM Table1 T1
WHERE T1.PartNumber = T2.PartNumber
);
【讨论】:
以上是关于使用 OR 连接的性能的主要内容,如果未能解决你的问题,请参考以下文章