加入条件“ON”vs“WHERE”

时间:2014-02-26 14:37:49

标签: sql join oracle11g

 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”子句中指定连接条件,在内部,左外部,右外部连接中获得相同的结果。请指教。

3 个答案:

答案 0 :(得分:6)

那么,你所谓的“等价”并不等同于外连接。我们以左连接为例。

加入条件:

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 WHERE:

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子句是在之后应用的行过滤器联接已经完成。

答案 1 :(得分:4)

对于内部联接,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"

答案 2 :(得分:2)

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

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


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

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