更详细地解释 JOIN 与 LEFT JOIN 和 WHERE 条件性能建议

Posted

技术标签:

【中文标题】更详细地解释 JOIN 与 LEFT JOIN 和 WHERE 条件性能建议【英文标题】:Explain JOIN vs. LEFT JOIN and WHERE condition performance suggestion in more detail 【发布时间】:2014-09-12 15:24:00 【问题描述】:

在this candidate answer 中断言JOIN 在某些涉及一些WHERE 子句的情况下优于LEFT JOIN,因为它不会混淆查询计划器并且不是“毫无意义的”。断言/假设是对任何人都应该是显而易见的。

请进一步解释或提供链接以供进一步阅读。

【问题讨论】:

更好?它们用于不同的目的。 JOIN 是内连接,LEFT JOIN 是外连接(和 LEFT OUTER JOIN 一样)。根据您的意图,您将使用外部联接或内部联接。 WHERE 子句不应用于连接条件。它应该用于其他标准; IE。过滤。 我在那里读到的答案是“由于 LEFT JOIN(带有 WHERE)实际上是一个 INNER JOIN,所以只需使用一个 INNER JOIN。”我不确定它对 [特定] 查询计划器有多“混乱”,但它对于人类 [阅读:我的] 消费来说并不理想,IMOHO。 (例如,当 INNER 就足够时,使用 CROSS JOIN 也是如此。) 带有使用外连接表的 where 子句的左连接实际上是内连接。但是,如果 where 子句不使用外连接表,不,情况并非如此。 【参考方案1】:

实际上,WHERE 条件和JOIN 条件在 PostgreSQL 中是 100% 等效的。 (不过,最好使用显式的 JOIN 条件使查询更易于阅读和维护)。

同样适用于 LEFT JOIN 与连接右侧表上的 WHERE 条件相结合。 LEFT JOIN 的目的是保留连接左侧的所有行,而不考虑右侧的匹配项。如果未找到匹配项,则使用右侧列的 NULL 值扩展该行。 The manual:

LEFT OUTER JOIN

首先,执行内部连接。然后,对于 T1 中与 T2 中的任何行不满足连接条件的每一行,一个连接行 在 T2 的列中添加空值。因此,连接表 T1 中的每一行总是至少有一行。

如果您随后在右侧表格的列上应用WHERE 条件,该条件需要除NULL 值以外的其他值,则您会取消效果并强制转换LEFT [OUTER] JOIN 以像普通[INNER] JOIN 一样工作,只是(可能)由于更复杂的查询计划而更昂贵。

在具有许多连接表的查询中,Postgres(或任何 RDBMS)很难找到最佳(甚至是好的)查询计划。连接表的理论上可能的序列数量成因地 (!) 增加。 Postgres 使用"Generic Query Optimizer" 来执行任务,并且有一些设置会影响它。

如前所述,使用误导性的 LEFT JOIN 混淆查询,会使查询规划器的工作更加困难,会误导人类读者,并且通常会提示查询逻辑中的错误。

由此产生的问题的相关答案:

Why does null equal integer in WHERE? Query with LEFT JOIN not returning rows for count of 0 SQL query using outer join and limiting child records for each parent Left outer join acting like inner join Select rows which are not present in other table

等等

【讨论】:

如果真的“更贵”?现代查询规划器——当然,我主要使用 SQL Server——给我留下了深刻的印象。 贵不贵有什么不同。如果您要说表 X 上的值应该是某个值(不是空值),为什么还要对表 X 使用外连接?如果有的话,不要仅仅因为它没有意义就这样做。 @user2864740:在简单的情况下这并不重要。但是,它使查询优化器在复杂情况下的工作变得更加困难。但正如布赖恩所评论的那样:无论哪种方式都不要这样做。对于人类读者来说,这也很令人困惑。 @ErwinBrandstetter,感谢您提供了一个非常好的答案,很抱歉之前的混淆,Brian 的例子让事情变得非常清楚,所以我接受了他的回答。 @DwayneTowell:我添加了一些例子来说明。【参考方案2】:

考虑以下示例。我们有两张表,DEPARTMENTS 和 EMPLOYEES。

有些部门还没有员工。

此查询使用内部联接来查找部门员工 999 工作的部门(如果有),否则它不显示任何内容(甚至不显示员工或其姓名):

select a.department_id, a.department_desc, b.employee_id, b.employee_name
  from departments a
  join employees b
    on a.department_id = b.department_id
 where b.employee_id = '999'

下一个查询使用外部联接(部门和员工之间的左侧)并查找员工 999 工作的部门。但是,如果员工不在任何部门工作,它也不会显示员工的 ID 或他或她的姓名。这是因为在 WHERE 子句中使用了外部连接表。如果没有匹配的部门,则为空(不是 999,即使员工中存在 999)。

select a.department_id, a.department_desc, b.employee_id, b.employee_name
  from departments a
  left join employees b
    on a.department_id = b.department_id
 where b.employee_id = '999'

但是考虑一下这个查询:

select a.department_id, a.department_desc, b.employee_id, b.employee_name
  from departments a
  left join employees b
    on a.department_id = b.department_id
   and b.employee_id= '999'

现在条件在 on 子句中。所以即使这个员工没有在任何部门工作,他仍然会被退回(他的 ID 和姓名)。部门列将为空,但我们得到一个结果(员工方面)。

您可能认为您永远不想在 WHERE 子句中使用外连接表,但事实并非如此。但通常情况下,出于上述原因。

假设您希望所有部门都没有员工。然后你可以运行下面的语句,它确实使用了外连接,外连接表用在 where 子句中:

select a.department_id, a.department_desc, b.employee_id
  from departments a
  left join employees b
    on a.department_id = b.department_id
 where b.employee_id is null

^^ 显示没有员工的部门。

以上可能是您希望在 WHERE 子句而不是 ON 子句中使用外部联接表的唯一正当理由(我认为这是您的问题;内部联接和外部联接之间的区别是完全不同的主题)。

查看的一个好方法是:您使用外连接来允许空值。为什么你会使用外连接并说一个字段不应该为空并且应该等于'XYZ'?如果一个值必须是“XYZ”(不是空值),那么为什么要指示数据库允许空值返回呢?这就像说一件事,然后再覆盖它。

【讨论】:

这个答案中的第三个例子是错误的。在这种情况下,不会返回员工,该查询将返回 所有部门ID 为 999 的任何关联员工。如果 999 员工未与任何部门关联,则不会在结果中返回。 看起来像“但考虑这个查询”上面和下面的查询是一样的。 区别在于 WHERE 与 AND(留在 LEFT JOIN "ON" 的一部分)

以上是关于更详细地解释 JOIN 与 LEFT JOIN 和 WHERE 条件性能建议的主要内容,如果未能解决你的问题,请参考以下文章

最全解释Mysql 的join中on与where 过滤条件差异

sql语法:inner join on, left join on, right join on详细使用方法

sql语法:inner join on, left join on, right join on详细使用方法

超详细mysql left join,right join,inner join用法分析

超详细mysql left join,right join,inner join用法分析

Hibernate 中 LEFT JOIN 和 LEFT JOIN FETCH 的区别?