使用 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】:
请注意与外部连接的区别。在JOIN
的ON
条件中添加了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.IsApproved
或b.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? [复制]
使用 WHERE 子句中的过滤器优化 OUTER JOIN 查询。(查询规划器)