【问题标题】:How does SQL join work?SQL 连接是如何工作的?
【发布时间】:2010-10-01 03:34:43
【问题描述】:

我试图了解连接在内部是如何工作的。以下两个查询的运行方式有什么区别?

For example

(A)

Select * 
FROM TABLE1
FULL JOIN TABLE2 ON TABLE1.ID = TABLE2.ID
FULL JOIN TABLE3 ON TABLE1.ID = TABLE3.ID

And

(B)

Select * 
FROM TABLE1
FULL JOIN TABLE2 ON TABLE1.ID = TABLE2.ID
FULL JOIN TABLE3 ON TABLE2.ID = TABLE3.ID

编辑:我在这里谈论的是 oracle。 考虑表 2 和表 3 中存在但表 1 中不存在的一些记录,查询 A 将为该记录提供两行,但 B 将仅提供一行。

【问题讨论】:

  • FULL 与 FULL OUTER 相同。 OUTER 是模棱两可的(是 LEFT、RIGHT 还是 FULL?) - 但大多数人会将其解释为 FULL。
  • 全外连接返回两个表中的所有行,表中与连接条件不匹配的任何列都为 NULL。有点像 LEFT OUTER JOIN 和 RIGHT OUTER JOIN 的组合。

标签: sql database oracle join


【解决方案1】:

您的 DBMS 优化器将确定执行查询的最佳方式。通常这是通过“基于成本的优化”来完成的,其中考虑了许多不同的查询计划并选择了最有效的一个。 如果您的两个查询在逻辑上是相同的,那么无论您以何种方式编写优化器,都很可能最终会使用相同的查询计划。事实上,如今根据 SQL 中如此细微的差异生成不同的查询计划,这将是一个糟糕的优化器。

但是,完全外连接是另一回事(至少在 Oracle 中),因为连接列的方式会影响结果。即 2 个查询不可可互换。

您可以在 SQL Plus 中使用 AUTOTRACE 来查看不同的计划:

SQL> select *
  2  from t1
  3  full join t2 on t2.id = t1.id
  4  full join t3 on t3.id = t2.id;

        ID         ID         ID
---------- ---------- ----------
                    1          1

1 row selected.


Execution Plan
----------------------------------------------------------

---------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)|
---------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     3 |   117 |    29  (11)|
|   1 |  VIEW                   |      |     3 |   117 |    29  (11)|
|   2 |   UNION-ALL             |      |       |       |            |
|*  3 |    HASH JOIN OUTER      |      |     2 |   142 |    15  (14)|
|   4 |     VIEW                |      |     2 |    90 |    11  (10)|
|   5 |      UNION-ALL          |      |       |       |            |
|*  6 |       HASH JOIN OUTER   |      |     1 |    91 |     6  (17)|
|   7 |        TABLE ACCESS FULL| T1   |     1 |    52 |     2   (0)|
|   8 |        TABLE ACCESS FULL| T2   |     1 |    39 |     3   (0)|
|*  9 |       HASH JOIN ANTI    |      |     1 |    26 |     6  (17)|
|  10 |        TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)|
|  11 |        TABLE ACCESS FULL| T1   |     1 |    13 |     2   (0)|
|  12 |     TABLE ACCESS FULL   | T3   |     1 |    26 |     3   (0)|
|* 13 |    HASH JOIN ANTI       |      |     1 |    26 |    15  (14)|
|  14 |     TABLE ACCESS FULL   | T3   |     1 |    13 |     3   (0)|
|  15 |     VIEW                |      |     2 |    26 |    11  (10)|
|  16 |      UNION-ALL          |      |       |       |            |
|* 17 |       HASH JOIN OUTER   |      |     1 |    39 |     6  (17)|
|  18 |        TABLE ACCESS FULL| T1   |     1 |    26 |     2   (0)|
|  19 |        TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)|
|* 20 |       HASH JOIN ANTI    |      |     1 |    26 |     6  (17)|
|  21 |        TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)|
|  22 |        TABLE ACCESS FULL| T1   |     1 |    13 |     2   (0)|
---------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("T3"."ID"(+)="T2"."ID")
   6 - access("T2"."ID"(+)="T1"."ID")
   9 - access("T2"."ID"="T1"."ID")
  13 - access("T3"."ID"="T2"."ID")
  17 - access("T2"."ID"(+)="T1"."ID")
  20 - access("T2"."ID"="T1"."ID")

SQL> select *
  2  from t1
  3  full join t2 on t2.id = t1.id
  4  full join t3 on t3.id = t1.id;

        ID         ID         ID
---------- ---------- ----------
                    1
                               1

2 rows selected.


Execution Plan
----------------------------------------------------------

---------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)|
---------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     3 |   117 |    29  (11)|
|   1 |  VIEW                   |      |     3 |   117 |    29  (11)|
|   2 |   UNION-ALL             |      |       |       |            |
|*  3 |    HASH JOIN OUTER      |      |     2 |   142 |    15  (14)|
|   4 |     VIEW                |      |     2 |    90 |    11  (10)|
|   5 |      UNION-ALL          |      |       |       |            |
|*  6 |       HASH JOIN OUTER   |      |     1 |    91 |     6  (17)|
|   7 |        TABLE ACCESS FULL| T1   |     1 |    52 |     2   (0)|
|   8 |        TABLE ACCESS FULL| T2   |     1 |    39 |     3   (0)|
|*  9 |       HASH JOIN ANTI    |      |     1 |    26 |     6  (17)|
|  10 |        TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)|
|  11 |        TABLE ACCESS FULL| T1   |     1 |    13 |     2   (0)|
|  12 |     TABLE ACCESS FULL   | T3   |     1 |    26 |     3   (0)|
|* 13 |    HASH JOIN ANTI       |      |     1 |    26 |    15  (14)|
|  14 |     TABLE ACCESS FULL   | T3   |     1 |    13 |     3   (0)|
|  15 |     VIEW                |      |     2 |    26 |    11  (10)|
|  16 |      UNION-ALL          |      |       |       |            |
|* 17 |       HASH JOIN OUTER   |      |     1 |    39 |     6  (17)|
|  18 |        TABLE ACCESS FULL| T1   |     1 |    26 |     2   (0)|
|  19 |        TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)|
|* 20 |       HASH JOIN ANTI    |      |     1 |    26 |     6  (17)|
|  21 |        TABLE ACCESS FULL| T2   |     1 |    13 |     3   (0)|
|  22 |        TABLE ACCESS FULL| T1   |     1 |    13 |     2   (0)|
---------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("T3"."ID"(+)="T1"."ID")
   6 - access("T2"."ID"(+)="T1"."ID")
   9 - access("T2"."ID"="T1"."ID")
  13 - access("T3"."ID"="T1"."ID")
  17 - access("T2"."ID"(+)="T1"."ID")
  20 - access("T2"."ID"="T1"."ID")

实际上,查询计划除了谓词信息之外都是相同的

【讨论】:

  • 是的,但是如何执行也将决定我们得到的输出。考虑表 2 和表 3 中存在但表 1 中不存在的一些记录,查询 A 将为该记录提供两行,但 B 将仅提供一行。
  • 执行方法永远不会影响正确格式的 SQL 语句的最终结果。如果是这样,那么您的 DBMS 有一个错误。他们会给你不同的结果的原因是因为他们提出了两个不同的问题。
  • 没错,我没有注意到 FULL 关键字,我在考虑内部连接。
【解决方案2】:
【解决方案3】:

您表示对“内部”感兴趣,然后询问了一个说明“语义”的示例。我在回答语义。

考虑这些表格。

Table1 : 1, 4, 6
Table2 : 2, 4, 5
Table3 : 3, 5, 6

两个示例首先执行相同的连接,所以我将在此处执行。

FirstResult = T1 FULL JOIN T2 : (T1, T2)
(1, null)
(4, 4)
(6, null)
(null, 2)
(null, 5)

示例 (A)

FirstResult FULL JOIN T3 ON FirstItem : (T1, T2, T3)

(1, null, null)
(4, 4, null)
(6, null, 6)   <----
(null, 2, null)
(null, 5, null)   <----
(null, null, 3)

示例(B)

FirstResult FULL JOIN T3 ON SecondItem : (T1, T2, T3)
(1, null, null)
(4, 4, null)
(6, null, null)   <----
(null, 2, null)
(null, 5, 5)   <----
(null, null, 3)

这从逻辑上向您展示了如何从连接中生成结果。

对于“内部”,有一种叫做查询优化器的东西,它会产生相同的结果——但它会做出实现选择以快速进行计算/io。这些选择包括:

  • 首先访问哪些表
  • 使用索引或表扫描查看表
  • 要使用的连接实现类型(嵌套循环、合并、散列)。

另请注意:由于优化器做出了这些选择,并根据它认为的最优值更改这些选择 - 结果的顺序可能会发生变化。结果的默认顺序始终是“最简单的”。如果您不想要默认排序,则需要在查询中指定排序。

要准确了解优化器将如何处理查询(此时,因为它可以改变主意),您需要查看执行计划。

【讨论】:

    【解决方案4】:

    使用查询 A,您获得的内容包括表 1 中的条目以及 table3 中的相应条目,而 table3 中没有相应条目(t2 列的空值)

    使用查询 B 时,您不会获得这些条目,因为您只能通过 table2 进入 table3。如果 table2 中没有对应的条目,则 table2.id 将为 null,永远不会匹配 table3.id

    【讨论】:

      猜你喜欢
      • 2016-01-28
      • 2023-03-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-13
      相关资源
      最近更新 更多