【问题标题】:join condition "ON" vs in "WHERE"加入条件“ON”与“WHERE”
【发布时间】:2014-03-29 11:19:09
【问题描述】:
 SELECT *
 FROM Customers c
 INNER JOIN CustomerAccounts ca
 ON ca.CustomerID = c.CustomerID
 AND c.State = 'NY'
 INNER JOIN Accounts a
 ON ca.AccountID = a.AccountID
 AND a.Status = 1

等效:

 SELECT *
 FROM Customers c
 INNER JOIN CustomerAccounts ca
 ON ca.CustomerID = c.CustomerID
 INNER JOIN Accounts a
 ON ca.AccountID = a.AccountID
 WHERE c.State = 'NY'
 AND a.Status = 1

左加入:

 SELECT *
 FROM Customers c
 LEFT JOIN CustomerAccounts ca
 ON ca.CustomerID = c.CustomerID
 AND c.State = 'NY'
 LEFT JOIN Accounts a
 ON ca.AccountID = a.AccountID
 AND a.Status = 1

等效:

 SELECT *
 FROM Customers c
 LEFT JOIN CustomerAccounts ca
 ON ca.CustomerID = c.CustomerID
 LEFT JOIN Accounts a
 ON ca.AccountID = a.AccountID
 WHERE c.State = 'NY'
 AND a.Status = 1

右加入

 SELECT *
 FROM Customers c
 RIGHT JOIN CustomerAccounts ca
 ON ca.CustomerID = c.CustomerID
 AND c.State = 'NY'
 RIGHT JOIN Accounts a
 ON ca.AccountID = a.AccountID
 AND a.Status = 1

等效:

 SELECT *
 FROM Customers c
 RIGHT JOIN CustomerAccounts ca
 ON ca.CustomerID = c.CustomerID
 RIGHT JOIN Accounts a
 ON ca.AccountID = a.AccountID
 WHERE c.State = 'NY'
 AND a.Status = 1

在“WHERE”子句中指定连接条件与“ON 连接条件”有什么区别?

通过在“ON”子句和“WHERE”子句中指定连接条件,我们是否在内、左外、右外连接中得到相同的结果。请指教。

【问题讨论】:

    标签: sql join oracle11g


    【解决方案1】:

    嗯,你所说的“等价”并不是外连接的等价物。我们以左连接为例。

    JOIN 中的条件:

    SELECT * FROM Customers c
    LEFT JOIN CustomerAccounts ca ON ca.CustomerID = c.CustomerID AND c.State = 'NY'
    LEFT JOIN Accounts a ON ca.AccountID = a.AccountID AND a.Status = 1
    

    vs 在哪里:

    SELECT * FROM Customers c
    LEFT JOIN CustomerAccounts ca ON ca.CustomerID = c.CustomerID
    LEFT JOIN Accounts a ON ca.AccountID = a.AccountID
    WHERE c.State = 'NY'
    AND a.Status = 1
    

    将条件放入 WHERE 子句中有效地使连接 INNER 连接,因为 WHERE 子句是在 after 应用的行 filter连接已完成。

    【讨论】:

      【解决方案2】:

      对于内部联接,Oracle 将根据基于成本的优化器的分析选择使用哪些条件进行联接和过滤。您可能会从前两个查询中看到相同的执行计划。它不一定使用on 子句加入,然后使用where 子句进行过滤。 (无论如何,它都会将其重写为其内部格式,即 ANSI 之前的版本 - 如果跟踪查询,您可以看到 - 并且该格式没有区别)。

      您可以通过查看解释计划来证明这一点。一个有趣的演示是,如果您在两列上有外键关系,并将父级连接到子级,其中一个在 on 中,另一个在 where 中。

      create table parent (pid1 number, pid2 number,
        constraint parent_pk primary key (pid1, pid2));
      create table child (cid number, pid1 number not null, pid2 number not null,
        constraint child_pk primary key (cid),
        constraint child_fk_parent foreign key (pid1, pid2)
          references parent (pid1, pid2));
      create index child_fk_index on child (pid1, pid2);
      
      set autotrace on explain
      select *
      from parent p
      join child c on c.pid2 = p.pid2
      where c.pid1 = p.pid1;
      
      -----------------------------------------------------------------------------------------------
      | Id  | Operation                    | Name           | Rows  | Bytes | Cost (%CPU)| Time     |
      -----------------------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT             |                |     1 |    65 |     2   (0)| 00:00:01 |
      |   1 |  NESTED LOOPS                |                |       |       |            |          |
      |   2 |   NESTED LOOPS               |                |     1 |    65 |     2   (0)| 00:00:01 |
      |   3 |    TABLE ACCESS FULL         | PARENT         |     1 |    26 |     2   (0)| 00:00:01 |
      |*  4 |    INDEX RANGE SCAN          | CHILD_FK_INDEX |     1 |       |     0   (0)| 00:00:01 |
      |   5 |   TABLE ACCESS BY INDEX ROWID| CHILD          |     1 |    39 |     0   (0)| 00:00:01 |
      -----------------------------------------------------------------------------------------------
      
      Predicate Information (identified by operation id):
      ---------------------------------------------------
      
         4 - access("C"."PID1"="P"."PID1" AND "C"."PID2"="P"."PID2")
      

      计划显示用于访问的两列以及正在使用的索引。

      Oracle 不一定按照您期望的顺序加入 - from 中的表顺序不会限制 Oracle 对最佳计划的决定:

      select *
      from parent p
      join child c on c.pid2 = p.pid2
      where c.pid1 = p.pid1
      and c.cid = 1;
      
      ------------------------------------------------------------------------------------------
      | Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
      ------------------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT             |           |     1 |    65 |     1   (0)| 00:00:01 |
      |   1 |  NESTED LOOPS                |           |     1 |    65 |     1   (0)| 00:00:01 |
      |   2 |   TABLE ACCESS BY INDEX ROWID| CHILD     |     1 |    39 |     1   (0)| 00:00:01 |
      |*  3 |    INDEX UNIQUE SCAN         | CHILD_PK  |     1 |       |     1   (0)| 00:00:01 |
      |*  4 |   INDEX UNIQUE SCAN          | PARENT_PK |    82 |  2132 |     0   (0)| 00:00:01 |
      ------------------------------------------------------------------------------------------
      
      Predicate Information (identified by operation id):
      ---------------------------------------------------
      
         3 - access("C"."CID"=1)
         4 - access("C"."PID1"="P"."PID1" AND "C"."PID2"="P"."PID2")
      

      因此,对于内部连接,它们是等价的,但在 on 子句中分离定义关系的列会很有用,例如您期望它使用的键/索引中的列;以及在where 中过滤的任何内容。 Oracle 可能仍然没有达到您的预期,但它显示了您的意图并且在某种程度上是自我记录的。

      select *
      from child c
      join parent p on p.pid1 = c.pid1 and p.pid2 = c.pid2
      where c.cid = 1;
      

      ...尽管看起来完全不同,但执行计划与前一个相同:

      ------------------------------------------------------------------------------------------
      | Id  | Operation                    | Name      | Rows  | Bytes | Cost (%CPU)| Time     |
      ------------------------------------------------------------------------------------------
      |   0 | SELECT STATEMENT             |           |     1 |    65 |     1   (0)| 00:00:01 |
      |   1 |  NESTED LOOPS                |           |     1 |    65 |     1   (0)| 00:00:01 |
      |   2 |   TABLE ACCESS BY INDEX ROWID| CHILD     |     1 |    39 |     1   (0)| 00:00:01 |
      |*  3 |    INDEX UNIQUE SCAN         | CHILD_PK  |     1 |       |     1   (0)| 00:00:01 |
      |*  4 |   INDEX UNIQUE SCAN          | PARENT_PK |    82 |  2132 |     0   (0)| 00:00:01 |
      ------------------------------------------------------------------------------------------
      
      Predicate Information (identified by operation id):
      ---------------------------------------------------
      
         3 - access("C"."CID"=1)
         4 - access("P"."PID1"="C"."PID1" AND "P"."PID2"="C"."PID2")
      

      通过跟踪并查看跟踪文件,您可以看到它已转换为:

      Final query after transformations:******* UNPARSED QUERY IS *******
      SELECT "C"."CID" "CID","C"."PID1" "PID1","C"."PID2" "PID2","P"."PID1" "PID1",
      "P"."PID2" "PID2" FROM "STACKOVERFLOW"."CHILD" "C","STACKOVERFLOW"."PARENT" "P" 
      WHERE "C"."CID"=1 AND "P"."PID1"="C"."PID1" AND "P"."PID2"="C"."PID2"
      

      ...所以内部没有区别 - 所有条件都在 where 子句中。

      其他人已经解释了为什么这不适用于外连接,但由于我提到了旧格式,将外连接条件移动到 where 与在该条件中省略 (+) 大致相同旧语法。

      比较这些查询的转换;两个条件都在 on 子句中的外连接:

      select *
      from parent p
      left outer join child c on c.pid1 = p.pid1 and c.pid2 = p.pid2;
      
      Final query after transformations:******* UNPARSED QUERY IS *******
      SELECT "P"."PID1" "PID1","P"."PID2" "PID2","C"."CID" "CID","C"."PID1" "PID1",
      "C"."PID2" "PID2" FROM "STACKOVERFLOW"."PARENT" "P","STACKOVERFLOW"."CHILD" "C"
      WHERE "C"."PID2"(+)="P"."PID2" AND "C"."PID1"(+)="P"."PID1"
      

      ...以及“相同”查询,其中一个条件已移至where 子句:

      select *
      from parent p
      left outer join child c on c.pid1 = p.pid1
      where c.pid2 = p.pid2;
      
      Final query after transformations:******* UNPARSED QUERY IS *******
      SELECT "P"."PID1" "PID1","P"."PID2" "PID2","C"."CID" "CID","C"."PID1" "PID1",
      "C"."PID2" "PID2" FROM "STACKOVERFLOW"."PARENT" "P","STACKOVERFLOW"."CHILD" "C"
      WHERE "C"."PID2"="P"."PID2" AND "C"."PID1"="P"."PID1"
      

      请注意,第一个查询的两个条件都标有(+),而第二个查询都没有。跟踪中的详细信息显示了它关于(外部)连接消除的决定:

      OJE: Begin: find best directive for query block SEL$58A6D7F6 (#0)
      OJE: Considering outer-join elimination on query block SEL$58A6D7F6 (#0)
      OJE: considering predicate"C"."PID1"(+)="P"."PID1"
      
      rejected
      OJE:   outer-join not eliminated
      OJE: End: finding best directive for query block SEL$58A6D7F6 (#0)
      ...
      OJE: Begin: find best directive for query block SEL$9E43CB6E (#0)
      OJE: Considering outer-join elimination on query block SEL$9E43CB6E (#0)
      OJE: considering predicate"C"."PID2"="P"."PID2"
      
      OJE:      Converting outer join of CHILD and PARENT to inner-join.
      considered
      OJE: considering predicate"C"."PID1"="P"."PID1"
      
      rejected
      Registered qb: SEL$AE545566 0x2d07c338 (OUTER-JOIN REMOVED FROM QUERY BLOCK
      SEL$9E43CB6E; SEL$9E43CB6E; "C"@"SEL$1")
      

      外连接查询已经变成和这个内连接一样了:

      select *
      from parent p
      inner join child c on c.pid1 = p.pid1
      where c.pid2 = p.pid2;
      
      Final query after transformations:******* UNPARSED QUERY IS *******
      SELECT "P"."PID1" "PID1","P"."PID2" "PID2","C"."CID" "CID","C"."PID1" "PID1",
      "C"."PID2" "PID2" FROM "STACKOVERFLOW"."PARENT" "P","STACKOVERFLOW"."CHILD" "C"
      WHERE "C"."PID2"="P"."PID2" AND "C"."PID1"="P"."PID1"
      

      【讨论】:

      • 我理解你所说的......你说对于内部连接,在 ON 或 WHERE 子句中指定条件没有区别。所以它对外部连接也没有影响,对吧?
      • @user1118468 - 它在外连接上有很大的不同 - 正如 Bohemian 和 Guffa 所说,将外连接条件移动到 where 子句会将其变成内连接。使用旧语法很容易意外地做到这一点。有了明确的加入,你就没有太多的借口了。
      • @user1118468 - 我添加了显示当条件移动到where 时如何消除外连接的跟踪。有用的是,跟踪文件明确指出“将 CHILD 和 PARENT 的外连接转换为内连接”。
      【解决方案3】:

      右侧表中的任何条件(左连接中的第一个)都可以放在where 子句的连接中。内连接的所有条件也是如此。

      左侧表中的任何条件(第一个在右连接中,第二个在左连接中)都必须放在on 子句中。如果您将条件放在where 子句中,您实际上是在将外连接变为内连接。


      您的左连接示例不等效。在第二个中,您在 where 子句 (a.Status = 1) 中有左侧表的条件,因此它将用作内部联接。

      您的右连接示例不等效。在第二个中,您在 where 子句 (c.State = 'NY') 中有左侧表的条件,因此它将用作内部联接。

      【讨论】:

        猜你喜欢
        • 2016-11-07
        • 2011-10-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-01
        • 1970-01-01
        相关资源
        最近更新 更多