【问题标题】:Strange behaviour of full outer join in Oracle - how it could be explained?Oracle 中完全外连接的奇怪行为 - 如何解释?
【发布时间】:2012-03-31 20:11:03
【问题描述】:

我注意到 Oracle 11 中 FULL OUTER JOIN 的一种奇怪行为。我从 HR 模式连接表,特别是 EMPLOYEES 和 DEPARTMENTS。

例如,以下查询返回 123 行:

    SELECT * FROM employees e
    FULL JOIN departments d ON e.department_id = d.department_id

但是,难以理解的是 - 当我在 select 子句中放置一组特定列时,查询将返回 122 行(缺少的行是针对没有分配部门的员工 - 另外返回的行与内连接相比,左连接):

    SELECT first_name, last_name, department_name FROM employees e
    FULL JOIN departments d on e.department_id = d.department_id

即使我计算行数,它也会返回 122 (COUNT(*))!!!到底是怎么回事? SELECT *SELECT COUNT(*)有什么区别?

SELECT * ...的解释计划:

SELECT STATEMENT                                      122
  VIEW                 VW_FOJ_0                       122
    HASH JOIN                          FULL OUTER     122
      Access Predicates
        E.DEPARTMENT_ID = D.DEPARTMENT_ID
      TABLE ACCESS     DEPARTMENTS     FULL            27
      TABLE ACCESS     EMPLOYEES       FULL           107

对于SELECT COUNT(*) ...

SELECT STATEMENT                                             1
  SORT                                     AGGREGATE         1
    VIEW               VW_FOJ_0                            122
      HASH JOIN                            FULL OUTER      122
        Access Predicates
          E.DEPARTMENT_ID = D.DEPARTMENT_ID
        INDEX          DEPT_ID_PK          FAST FULL SCAN   27
        INDEX          EMP_DEPARTMENT_IX   FAST FULL SCAN  107

【问题讨论】:

  • 如果您对这些列使用union 会发生什么?使用 union all 时有什么不同吗?如果你算上group by first_name, last_name, department_name ,会得到什么?
  • SELECT * FROM employees e FULL JOIN departments d on e.department_id = d.department_id 返回 123 行,SELECT count(*) FROM employees e FULL JOIN departments d on e.department_id = d.department_id 返回 122 行?
  • 是的,正是——这就是我发布这个问题的原因。
  • 你能发布这些查询的解释计划吗?应该不同。
  • 第一个计划中的基数也很奇怪——它是 122,但查询返回 123 行。

标签: sql oracle oracle11g


【解决方案1】:

优化器不应该在第二个查询中选择使用 EMP.DEPT_ID 上的索引,因为它可以有 NULL 值。这就是导致它从结果中排除一行的原因。

目前我能想到的唯一非错误解释是您以某种方式在 DISABLE RELY 模式下创建了约束,因此优化器认为该字段不能包含 NULL。在这种情况下,考虑到约束中的错误信息,使用索引是正确的。但是,似乎 RELY 选项不适用于 NOT NULL 约束,所以我不明白这可能是什么问题。尽管如此,请仔细查看表上的所有约束。

除此之外,Oracle 网站上存在数量惊人的关于完全外连接的错误结果的错误。你可能会击中其中一个。在很多这样的情况下,解决方法是禁用“本机”完全外部连接,您可以使用以下语句为当前会话执行此操作:

alter session set "_optimizer_native_full_outer_join"=off; 

【讨论】:

  • +1:这看起来是最合理的解释。了解数据库版本会很有趣。
【解决方案2】:

(不能在评论中写)

结果符合执行计划。

count(*) 执行计划使用索引 EMP_DEPARTMENT_IX,其中包含来自employess 表的所有dept_id。但索引不包含空值。因此,这个执行计划将“丢失”具有空部门 ID 的 emp。

但是,应该解释一下为什么Oracle会在这种情况下选择这个执行计划

select first_name, last_name, department_name

select count(*)

反对

select *

【讨论】:

  • 是的,我同意。然而,在我看来,这是一种不一致。完全外连接在所有情况下都不能正常工作,除了选择 *。
  • @luckyjaca 你能用select emp_idselect first_nameselect e.department_idselect d.department_idselect d.department_name等做更多的测试吗?
  • 嘿...我已经按照您的建议进行了测试。尽管如此,它们都返回了 122 行——获得 123 行的唯一方法是通过 asterix 选择所有列。但是,最后我设法通过使用提示返回 123 行:/*+ no_index(e) */。足以使所有查询工作 - COUNT(*)first_name, last_name, department_name
  • 这真的很奇怪。提示不应影响结果。
  • 是的,我知道。根据您所写的另一件事-如果您查看第二个执行计划的最后一行,其中访问 EMP_DEPARTMENT_IX,它表示基数是 107-这意味着所有员工都已编入索引-即使是带有 null 的员工索引列中的值。
猜你喜欢
  • 2013-05-28
  • 1970-01-01
  • 1970-01-01
  • 2017-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-22
相关资源
最近更新 更多