关于oracle中的反连接(r3笔记第95天)

时间:2022-05-04
本文章向大家介绍关于oracle中的反连接(r3笔记第95天),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

在之前的章节中见到讨论过oracle中的半连接 http://blog.itpub.net/23718752/viewspace-1334483/ 与半连接相对应的是反连接,简而言之半连接就是查询条件中的in,exists,反连接就是not in, not exists这种类型的连接。 在asktom中,tom也对大家关心的in,exists,not in, not exists的问题进行了大量的佐证和解释。因为问题是在2001年左右提出来的,当时还是oracle 8的时代,帖子也沉里许久,在2013年的时候,tom在自己的博客中做了全新的解释,说大家都在讨论十几年前的东西了。传统RBO中的in,exists鲜明的对比在CBO中也都做了统一的优化处理,使得我们能够更加专注于程序的逻辑实现。

Followup May 6, 2013 - 7pm UTC:

you are correct that things change and we actually discussed this in the body of this very very very long posting (13 years of posting ;) )

it is the difference between the CBO and RBO.

here is a more formal write up of it:

http://www.oracle.com/technetwork/issue-archive/2006/06-may/o36asktom-099107.html

对于反连接,我准备用下面的实例来进行简单的演示。 首先创建两个测试表,我们使用最熟悉的emp,dept表。为了保留原有的数据,我重新创建了两个新的表,因为dept表中的数据太少,我就特意添加了一列数据使得效果更加明显。 create table emp as select *from scott.emp; create table dept as select *from scott.dept; insert into dept values(50,'IT','BEIJING'); commit; emp表中的数据情况如下,deptno目前只分布在3个部门。 SQL> select deptno from emp group by deptno;

DEPTNO ---------- 30 20 10 dept表中有5个部们,那么部门40,50就是emp中不存在的。也就是目前还没有员工在deptno 40,50两个部门。 SQL> select deptno,dname from dept;

DEPTNO DNAME ---------- -------------- 10 ACCOUNTING 20 RESEARCH 30 SALES 40 OPERATIONS 50 IT

我们来使用常用的反连接形式。查得deptno 为40,50的记录。这也是我们预期的。 select *from dept where not exists(select null from emp where emp.deptno=dept.deptno);

DEPTNO DNAME LOC ---------- -------------- ------------- 50 IT BEIJING 40 OPERATIONS BOSTON 查看执行计划,能够清晰的看到对应的反连接表示anti,当然我们也可以通过hint /*+hash_aj*/来指定为hash 反连接。

Execution Plan ---------------------------------------------------------- Plan hash value: 474461924

--------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 215 | 8 (13)| 00:00:01 | |* 1 | HASH JOIN ANTI | | 5 | 215 | 8 (13)| 00:00:01 | | 2 | TABLE ACCESS FULL| DEPT | 5 | 150 | 4 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| EMP | 14 | 182 | 3 (0)| 00:00:01 | ---------------------------------------------------------------------------

我们来看看和not exists等价的not in 形式。注意我在子查询中添加了deptno is not null,如果没有这个条件,not in和not exists是不等价的。如果emp中存在deptno为空的记录,那么整个查询就会返回0行。

SQL> select *from dept where deptno not in (select deptno from emp where deptno is not null);

DEPTNO DNAME LOC ---------- -------------- ------------- 50 IT BEIJING 40 OPERATIONS BOSTON 执行计划如下。 Execution Plan ---------------------------------------------------------- Plan hash value: 810774822

--------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 215 | 8 (13)| 00:00:01 | |* 1 | HASH JOIN ANTI SNA| | 5 | 215 | 8 (13)| 00:00:01 | | 2 | TABLE ACCESS FULL| DEPT | 5 | 150 | 4 (0)| 00:00:01 | |* 3 | TABLE ACCESS FULL| EMP | 14 | 182 | 3 (0)| 00:00:01 | --------------------------------------------------------------------------- 下面的这种形式可能也是大家常犯的一个错误,就是会把原本的not in,not exists查询改写为下面的形式。结果就出乎意料了

select dept.* from dept,emp where dept.deptno!=emp.deptno; 。。。。

DEPTNO DNAME LOC ---------- -------------- ------------- 50 IT BEIJING 50 IT BEIJING 50 IT BEIJING 50 IT BEIJING 50 IT BEIJING 50 IT BEIJING 50 IT BEIJING 50 IT BEIJING 50 IT BEIJING 50 IT BEIJING 50 IT BEIJING

DEPTNO DNAME LOC ---------- -------------- ------------- 50 IT BEIJING

56 rows selected. 可以从执行计划中看到,直接是对emp,dept做了nested loop join,这种错误需要避免。 Execution Plan ---------------------------------------------------------- Plan hash value: 4192419542

--------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 56 | 2408 | 11 (0)| 00:00:01 | | 1 | NESTED LOOPS | | 56 | 2408 | 11 (0)| 00:00:01 | | 2 | TABLE ACCESS FULL| DEPT | 5 | 150 | 4 (0)| 00:00:01 | |* 3 | TABLE ACCESS FULL| EMP | 11 | 143 | 1 (0)| 00:00:01 | ---------------------------------------------------------------------------

-->not in ,not exists的等价实现 在实际的工作中,可能根据需要还会对not in ,not exists改写为其他的等价形式。比如使用下面的形式。 SQL> select dept.* from dept ,emp where dept.deptno=emp.deptno(+) and emp.deptno is null;

DEPTNO DNAME LOC ---------- -------------- ------------- 50 IT BEIJING 40 OPERATIONS BOSTON 执行计划如下: Execution Plan ---------------------------------------------------------- Plan hash value: 474461924

--------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 215 | 8 (13)| 00:00:01 | |* 1 | HASH JOIN ANTI | | 5 | 215 | 8 (13)| 00:00:01 | | 2 | TABLE ACCESS FULL| DEPT | 5 | 150 | 4 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| EMP | 14 | 182 | 3 (0)| 00:00:01 | ---------------------------------------------------------------------------

下面的这种形式化就对结果集进行了筛查。如果emp中的deptno为空,就设定一个不存在的deptno值。 select dept.* from dept where deptno not in (select nvl(deptno,'-1') from emp) SQL> /

DEPTNO DNAME LOC ---------- -------------- ------------- 50 IT BEIJING 40 OPERATIONS BOSTON 执行计划如下: Execution Plan ---------------------------------------------------------- Plan hash value: 810774822

--------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 215 | 8 (13)| 00:00:01 | |* 1 | HASH JOIN ANTI SNA| | 5 | 215 | 8 (13)| 00:00:01 | | 2 | TABLE ACCESS FULL| DEPT | 5 | 150 | 4 (0)| 00:00:01 | | 3 | TABLE ACCESS FULL| EMP | 14 | 182 | 3 (0)| 00:00:01 | --------------------------------------------------------------------------- 还可以使用集合来实现。不过这种方式使用的场景要少一些。这种方式直接把not in改换成了in的格式。 SQL> select *from dept where deptno in (select deptno from dept minus select deptno from emp);

DEPTNO DNAME LOC ---------- -------------- ------------- 40 OPERATIONS BOSTON 50 IT BEIJING 执行计划如下: Execution Plan ---------------------------------------------------------- Plan hash value: 3106111345

----------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 5 | 150 | 13 (16)| 00:00:01 | |* 1 | FILTER | | | | | | | 2 | TABLE ACCESS FULL | DEPT | 5 | 150 | 4 (0)| 00:00:01 | | 3 | MINUS | | | | | | | 4 | SORT UNIQUE NOSORT| | 1 | 13 | 5 (20)| 00:00:01 | |* 5 | TABLE ACCESS FULL| DEPT | 1 | 13 | 4 (0)| 00:00:01 | | 6 | SORT UNIQUE NOSORT| | 1 | 13 | 4 (25)| 00:00:01 | |* 7 | TABLE ACCESS FULL| EMP | 1 | 13 | 3 (0)| 00:00:01 | -----------------------------------------------------------------------------