隐式内部连接 ​​- 它们是不是相等?

Posted

技术标签:

【中文标题】隐式内部连接 ​​- 它们是不是相等?【英文标题】:implicit inner joins - are they equal?隐式内部连接 ​​- 它们是否相等? 【发布时间】:2020-01-26 18:52:29 【问题描述】:

在我看来,这两个 SELECT 是完全相等的(但我想将第一个重写为第二个,以在我的情况下帮助优化器)——无论表 a、b、c、d 中的任何数据,两个 SELECT 都将产生完全相同的相同的结果。你同意吗?谢谢!

create table a (id number);
create table b (id number);
create table c (id number);
create table d (id number);

--Q1
select * from a,b,c,d
where a.id = b.id
and   a.id = c.id 
and   a.id = d.id;

--Q2
select * from a,b,c,d
where a.id = b.id
and   a.id = c.id 
and   a.id = d.id
      -- Q2 differs from Q1 only in the next 3 lines
and   b.id = c.id
and   b.id = d.id
and   c.id = d.id;

【问题讨论】:

从不FROM 子句中使用逗号。 始终使用正确、明确、标准 JOIN 语法。期间。 +1 表示 Gordon 的评论 .comas 产生了所有 4 个表的笛卡尔积,无缘无故地导致了糟糕的性能。使用join 是的,这些都是等价的。您正在加入同一 id 列上的所有表。传递属性:a=b, b=c --> a=c @Bonzay,逗号连接经过优化,可以执行与显式连接完全相同的操作。但是,仍然糟糕的编程。现代、明确的JOIN 语法更易于编写(没有错误)、更易于阅读(和维护),并且在需要时更易于转换为外连接。 我认为任何现代优化器都不会为这两个查询生成不同的计划。您使用的是哪种 DBMS 产品? 【参考方案1】:

我将解决这些不平等是否总是正确的问题。答案是“不”,不是在 SQL 中。在大多数情况下,它们是等价的。隐式类型转换会出现问题。

特别是,如果a.id 是一个数字而其他列是字符串,那么您会遇到以下情况:

1 = '1'        -- true
1 = '1.00'     -- true
'1' = '1.00'   -- false

你可以在这个 dbfiddle 上看到 this。使用JOINs 进行设置很简单,但由于我不会编写在FROM 子句中包含逗号的代码,所以我将把这个练习留给你。

实际上,用于连接的 id 应该是相同的类型。如果不是,您甚至不能声明外键关系。除了最佳实践之外,这两个查询不会自动等效。

注意:如果您使用正确、明确、标准 JOIN 语法,这同样适用,我强烈建议您专门学习和使用。

【讨论】:

非常感谢您确认当 id 属于同一类型时两个查询是相等的,也感谢您对类型转换以及它如何使查询不同的非常有趣的注释。是的,我用标准语法编写查询。在这里,我正在重写一个旧的生产查询,我想做最小的更改。因此,对于我的示例,我还使用了我继承的代码使用的旧语法,因为在旧语法中它可能与新语法不同。如果您只想添加条件而不重写旧的怪物查询,请称我为懦夫 :-)【参考方案2】:

是的,结果将完全相同。从代数的角度考虑,如果 a = b 且 b = c,则 a = c。这意味着第二个查询中的最后三个条件是多余的。

我会说最好还是坚持原来的查询。充其量,查询优化器将需要执行更多的比较。

【讨论】:

你可以在这里玩和测试:dbfiddle.uk/…【参考方案3】:

人们会期望 Oracle 应用 transitive closure 的规则并使用您提供的信息来证明多余的谓词是不必要的 (and it often does so, as I've shown in this blog post),但它似乎并没有这样做案例(没有约束或索引)。获取这两个查询的执行计划:

SELECT s.sql_id, p.*
FROM v$sql s, TABLE (
  dbms_xplan.display_cursor (
    s.sql_id, s.child_number, 'ALLSTATS LAST'
  )
) p
WHERE s.sql_text LIKE '%a,b,c,d%'

产量:

SQL_ID  d54mttn9psd29, child number 0
-------------------------------------
--Q2
 select * from a,b,c,d
 where a.id = b.id
 and   a.id = c.id 
 and 
  a.id = d.id
       -- Q2 differs from Q1 only in the next 3 lines
 
and   b.id = c.id
 and   b.id = d.id
 and   c.id = d.id
 
Plan hash value: 3564259801
 
-------------------------------------------------------------------------
| Id  | Operation            | Name | E-Rows |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |        |       |       |          |
|*  1 |  HASH JOIN           |      |      1 |  1068K|  1068K|  240K (0)|
|*  2 |   HASH JOIN          |      |      1 |  1209K|  1209K|  505K (0)|
|*  3 |    HASH JOIN         |      |      1 |  1506K|  1506K|  461K (0)|
|   4 |     TABLE ACCESS FULL| A    |      1 |       |       |          |
|   5 |     TABLE ACCESS FULL| B    |      1 |       |       |          |
|   6 |    TABLE ACCESS FULL | C    |      1 |       |       |          |
|   7 |   TABLE ACCESS FULL  | D    |      1 |       |       |          |
-------------------------------------------------------------------------

SQL_ID  3zzwv0z5tq84f, child number 0
-------------------------------------
--Q1
 select * from a,b,c,d
 where a.id = b.id
 and   a.id = c.id 
 and 
  a.id = d.id
 
Plan hash value: 255250992
 
---------------------------------------------------------------------------
| Id  | Operation              | Name | E-Rows |  OMem |  1Mem | Used-Mem |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT       |      |        |       |       |          |
|*  1 |  HASH JOIN             |      |      1 |  1068K|  1068K|  178K (0)|
|   2 |   MERGE JOIN CARTESIAN |      |      1 |       |       |          |
|   3 |    MERGE JOIN CARTESIAN|      |      1 |       |       |          |
|   4 |     TABLE ACCESS FULL  | B    |      1 |       |       |          |
|   5 |     BUFFER SORT        |      |      1 | 73728 | 73728 |          |
|   6 |      TABLE ACCESS FULL | C    |      1 |       |       |          |
|   7 |    BUFFER SORT         |      |      1 | 73728 | 73728 |          |
|   8 |     TABLE ACCESS FULL  | D    |      1 |       |       |          |
|   9 |   TABLE ACCESS FULL    | A    |      1 |       |       |          |
---------------------------------------------------------------------------

这令人惊讶。在没有对空表计算统计信息的情况下,E-Rows 值是正确的,但内存估计值相差甚远,并且两个查询的内存估计值不同。

这并不意味着一种方法总是比另一种更好甚至不同,但它可能。例如。当我将实际数据添加到表中并计算统计数据时:

INSERT INTO a SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000;
INSERT INTO b SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000;
INSERT INTO c SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000;
INSERT INTO d SELECT LEVEL FROM dual CONNECT BY LEVEL <= 10000;

BEGIN
  DBMS_STATS.GATHER_TABLE_STATS('TEST', 'A');
  DBMS_STATS.GATHER_TABLE_STATS('TEST', 'B');
  DBMS_STATS.GATHER_TABLE_STATS('TEST', 'C');
  DBMS_STATS.GATHER_TABLE_STATS('TEST', 'D');
END;

然后,我得到了与哈希连接相同的执行计划,而不是合并连接:

SQL_ID  d54mttn9psd29, child number 0
-------------------------------------
--Q2
 select * from a,b,c,d
 where a.id = b.id
 and   a.id = c.id 
 and 
  a.id = d.id
       -- Q2 differs from Q1 only in the next 3 lines
 
and   b.id = c.id
 and   b.id = d.id
 and   c.id = d.id
 
Plan hash value: 2782485219
 
-------------------------------------------------------------------------
| Id  | Operation            | Name | E-Rows |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |        |       |       |          |
|*  1 |  HASH JOIN           |      |      1 |  1836K|  1836K| 2032K (0)|
|*  2 |   HASH JOIN          |      |      1 |  2161K|  2161K| 2145K (0)|
|   3 |    TABLE ACCESS FULL | C    |  10000 |       |       |          |
|*  4 |    HASH JOIN         |      |  10000 |  2546K|  2546K| 2211K (0)|
|   5 |     TABLE ACCESS FULL| A    |  10000 |       |       |          |
|   6 |     TABLE ACCESS FULL| B    |  10000 |       |       |          |
|   7 |   TABLE ACCESS FULL  | D    |  10000 |       |       |          |
-------------------------------------------------------------------------

SQL_ID  3zzwv0z5tq84f, child number 0
-------------------------------------
--Q1
 select * from a,b,c,d
 where a.id = b.id
 and   a.id = c.id 
 and 
  a.id = d.id
 
Plan hash value: 388154631
 
-------------------------------------------------------------------------
| Id  | Operation            | Name | E-Rows |  OMem |  1Mem | Used-Mem |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |      |        |       |       |          |
|*  1 |  HASH JOIN           |      |  10000 |  2546K|  2546K| 2099K (0)|
|   2 |   TABLE ACCESS FULL  | D    |  10000 |       |       |          |
|*  3 |   HASH JOIN          |      |  10000 |  2546K|  2546K| 2146K (0)|
|   4 |    TABLE ACCESS FULL | C    |  10000 |       |       |          |
|*  5 |    HASH JOIN         |      |  10000 |  2546K|  2546K| 2211K (0)|
|   6 |     TABLE ACCESS FULL| A    |  10000 |       |       |          |
|   7 |     TABLE ACCESS FULL| B    |  10000 |       |       |          |
-------------------------------------------------------------------------

这里的学习和往常一样。不要相信你的直觉。不要相信互联网上的任何旧博客文章。查看执行计划并进行衡量。

我为此使用了 Oracle Database 18c Express Edition。

【讨论】:

以上是关于隐式内部连接 ​​- 它们是不是相等?的主要内容,如果未能解决你的问题,请参考以下文章

Java中比较字符串的内容是不是相等内部是如何比较的?还有比较字符串与比较它的哈希值哪个快?

js 隐式转换

内部类隐式持有外部类

隐式等待和显式等待之间的内部工作区别是啥

对缺失数据进行内部连接时的 SQL 删除

jquery隐式迭代(遍历内部dom元素) jquery设置样式方法