使用 JOIN 时的 WHERE 子句与 ON

Posted

技术标签:

【中文标题】使用 JOIN 时的 WHERE 子句与 ON【英文标题】:WHERE Clause vs ON when using JOIN 【发布时间】:2012-05-05 01:11:26 【问题描述】:

假设我有以下 T-SQL 代码:

SELECT * FROM Foo f
INNER JOIN Bar b ON b.BarId = f.BarId;
WHERE b.IsApproved = 1;

下面的也返回相同的一组行:

SELECT * FROM Foo f
INNER JOIN Bar b ON (b.IsApproved = 1) AND (b.BarId = f.BarId);

这可能不是这里最好的案例示例,但是这两者之间有什么性能差异吗?

【问题讨论】:

这里有一个类似的问题:***.com/questions/2509987/… 机器会自己计算出来并适当优化。但是,对于需要在多年后调试\修改\支持您的代码的人,请将过滤条件保留在 WHERE 中,并将连接条件保留在 ON 中。 @KM。我并不总是知道如何区分什么是连接条件和什么是过滤器。例如in this answer我认为它在连接中更好,那么这是一个“连接条件”吗? Here's another example 我什至不知道如何重写等效的 where 子句。 连接条件为:tableA.column = tableB.column 过滤条件为tableA.Column=5。进行外部连接(左/右)时,您必须将过滤条件放在ON 内或以这种方式对WHERE 编码(tableA.Column=5 OR tableA.Column IS NULL) 【参考方案1】:

请注意与外部连接的区别。在JOINON 条件中添加了b.IsApproved 过滤器(在右侧表中,Bar)的查询:

SELECT * 
FROM Foo f 
LEFT OUTER JOIN Bar b ON (b.IsApproved = 1) AND (b.BarId = f.BarId); 

是否与将过滤器放在WHERE子句中一样:

SELECT * 
FROM Foo f 
LEFT OUTER JOIN Bar b ON (b.BarId = f.BarId)
WHERE (b.IsApproved = 1); 

由于“失败”外连接到Bar(即没有b.BarId 用于f.BarId),对于所有此类失败的连接行和这些行,这将使b.IsApproved 保留为NULL然后会被过滤掉。

另一种看待这个问题的方式是,对于第一个查询,LEFT OUTER JOIN Bar b ON (b.IsApproved = 1) AND (b.BarId = f.BarId) 将始终返回 LEFT 表行,因为 LEFT OUTER JOIN 保证即使连接失败也会返回 LEFT 表行。但是,将(b.IsApproved = 1) 添加到LEFT OUTER JOIN on 条件的效果是当(b.IsApproved = 1) 为假时将所有右表列清空,即按照通常应用于LEFT JOIN 条件(b.BarId = f.BarId) 的相同规则。

更新: 为了完成康拉德提出的问题,可选过滤器的等效 LOJ 将是:

SELECT * 
FROM Foo f 
LEFT OUTER JOIN Bar b ON (b.BarId = f.BarId)
WHERE (b.IsApproved IS NULL OR b.IsApproved = 1);

WHERE 子句需要考虑连接是否失败(NULL) 和过滤器是否要忽略的条件,以及连接成功的位置和必须应用过滤器的位置。 (b.IsApprovedb.BarId 可以测试NULL

我放了一个SqlFiddle together here,它展示了b.IsApproved 过滤器相对于JOIN 的不同位置之间的差异。

【讨论】:

非常好。如果将来自外部联接的过滤条件测试数据放入外部联接本身,您将获得比预期更多的行,因为无论 Bar 的状态或存在如何,都会返回所有 Foo。当过滤与连接分开指定时,首先连接两个表中的行,然后过滤器将不满足条件的表中的整行删除。 @nonnb 好的,但是如果您将第二个查询中的 WHERE 子句更正为 WHERE b.IsApproved = 1 or b.BarId is Null,则结果相同。现在你做哪一个? @nonnn 嗯,你在左连接版本中不需要OR (b.BarId IS NULL) ,只有在 WHERE 版本中,你想让它变得一样。【参考方案2】:

不,查询优化器足够聪明,可以为两个示例选择相同的执行计划。

您可以使用SHOWPLAN查看执行计划。


尽管如此,您应该将所有连接连接放在ON 子句上,并将所有限制放在WHERE 子句上。

【讨论】:

打败我。虽然出于偏好,我会选择 JOIN,因为它更具描述性。 谢谢!想象一下有 7 或 8 个 INNER JOINS 的情况。您的答案是否也适用于这些情况? @Ste IMO,实际上将所有内容都放在JOIN 中更令人困惑。使用JOIN 与查询中的表相关联。使用WHERE 过滤结果。当您混合使用两者并使用其中一种时,查询变得难以阅读。 @Yuck。公平点,我同意这种组合变得难以管理。 @Ste:我实际上通常更喜欢 JOIN 和 WHERE 的组合,前提是每个关键字的目的都在查询编写中强制执行。 JOIN 子句确定表如何链接到“宽”结果集,然后 WHERE 子句确定对所述结果的过滤。鉴于此,我觉得用 JOIN 和 WHERE 破译查询比用只有 JOIN 破译查询更容易,就像破译 JOIN 比只有 WHERE 子句定义连接和过滤条件的查询更容易一样。 【参考方案3】:
SELECT * FROM Foo f
INNER JOIN Bar b ON b.BarId = f.BarId
WHERE b.IsApproved = 1;

这是更好的形式。它易于阅读和修改。在商业世界中,这就是您想要的。但就性能而言,它们是相同的。

【讨论】:

在我目前的情况下,我赞成 WHERE 子句,但无法避免怀疑是否存在性能差异。谢谢!【参考方案4】:

在某些情况下,即使在最新版本的 MSSQL 上,优化器也不够智能 - 而且性能差异是巨大的。

但这是个例外,大多数时候 SQL Server 优化器会解决问题并得到正确的计划。

因此保持在 WHERE 子句上使用过滤器的策略,并在需要时进行优化。

【讨论】:

【参考方案5】:

我刚刚针对四个表运行了一个查询测试 - 一个具有三个 INNER JOIN 和总共四个参数的主表,并比较了两种方法的执行计划(使用 JOIN 的 ON 中的过滤条件,以及然后也在 WHERE 子句中)。

执行计划完全相同。我在 SQL Server 2008 R2 上运行它。

【讨论】:

以上是关于使用 JOIN 时的 WHERE 子句与 ON的主要内容,如果未能解决你的问题,请参考以下文章

`INNER JOIN` 过滤条件在查询中的位置; `ON` 或 `WHERE` 子句 [关闭]

为啥以及何时在 WHERE 子句中带有条件的 LEFT JOIN 不等于在 ON 中的相同 LEFT JOIN? [复制]

mysql left join的深入探讨

使用 WHERE 子句中的过滤器优化 OUTER JOIN 查询。(查询规划器)

根据 JOIN ON 参数或 WHERE 参数限制 SQL 结果

将索引与 where 和 join 子句一起使用