44

假设我有以下 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);

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

4

5 回答 5

46

请注意与外部连接的区别。一个查询,其中b.IsApproved(在右表,Bar)的过滤器被添加到ON条件中JOIN

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); 

由于 for 'failed' 外连接Bar(即没有b.BarIdfor a 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 JOINon 条件的效果是在(b.IsApproved = 1)为 false 时将任何正确的表列清空,即按照通常应用于LEFT JOIN条件 on的相同规则(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,b.IsApproved它展示了过滤器的不同位置相对于JOIN.

于 2012-04-24T11:53:59.760 回答
32

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

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


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

于 2012-04-24T11:50:54.910 回答
6
SELECT * FROM Foo f
INNER JOIN Bar b ON b.BarId = f.BarId
WHERE b.IsApproved = 1;

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

于 2012-04-24T11:51:45.890 回答
0

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

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

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

于 2012-04-24T19:45:02.207 回答
0

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

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

于 2014-10-15T11:37:01.220 回答