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