Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1705757
  • 博文数量: 136
  • 博客积分: 10021
  • 博客等级: 上将
  • 技术积分: 3261
  • 用 户 组: 普通用户
  • 注册时间: 2007-01-22 11:26
文章分类

全部博文(136)

文章存档

2010年(1)

2009年(26)

2008年(109)

我的朋友

分类: Oracle

2008-05-23 00:11:44

在 PL/SQL 编码中,经常会从表中获取结果集,然后进行一些逻辑处理,再生成新的数据。我们想到的最直接的方法就是使用游标。从 Oracle8i 开始,出现了一个新的子句:BULK COLLECT。可以降低 SQL 引擎到 PL/SQL 引擎的上下文交换(context switch)次数,从而实现数据的高速检索。
 
1. 速度比较
 
从表中获取结果集,我们常用的方法就是使用游标循环,我们看看它的执行速度:

 

SQL> set serveroutput on

SQL> declare

  2    type tnt_ic01 is table of ic01%rowtype;

  3    nt_ic01 tnt_ic01 := tnt_ic01();

  4    n_startTime number;

  5  begin

  6    n_startTime := dbms_utility.get_time;

  7    for rec_ic01 in( select * from ic01 where rownum < 100000 ) loop

  8      nt_ic01.extend;

  9      nt_ic01( nt_ic01.last ) := rec_ic01;

 10    end loop;

 11    dbms_output.put_line( 'COUNT: ' || nt_ic01.count );

 12    dbms_output.put_line( 'USED TIEM: ' || ( dbms_utility.get_time - n_startTime ) );

 13  end;

 14  /

COUNT: 99999

USED TIEM: 255

 

PL/SQL 过程已成功完成。

 

我们再看一下完成同样的功能,BULK COLLECT 的执行速度:

 

SQL> declare

  2    type tnt_ic01 is table of ic01%rowtype;

  3    nt_ic01 tnt_ic01;

  4    n_startTime number;

  5  begin

  6    n_startTime := dbms_utility.get_time;

  7    select *

  8      bulk collect into nt_ic01

  9      from ic01

 10     where rownum < 100000;

 11    dbms_output.put_line( 'COUNT: ' || nt_ic01.count );

 12    dbms_output.put_line( 'USED TIEM: ' || ( dbms_utility.get_time - n_startTime ) );

 13  end;

 14  /

COUNT: 99999

USED TIEM: 159

 

PL/SQL 过程已成功完成。

 
BULK COLLECT 要比游标循环快得多。如果你的代码中有游标循环,并且你也正为这里执行缓慢而发愁,不妨试试 BULK COLLECT。
 
2. 没有 NO_DATA_FOUND 异常
 
不过要记得,SELECT ... BULK COLLECT INTO ... 不会引起 NO_DATA_FOUND 异常,虽然它的语法看起来和 SELECT ... INTO ... 类似。要判断 BULK COLLECT 是否获得数据,可以通过 COUNT 方法

 

SQL> declare

  2    type tnt_ic01 is table of ic01%rowtype;

  3    nt_ic01 tnt_ic01;

  4    rec_ic01 ic01%rowtype;

  5  begin

  6    --1. normal select, can cause no_data_found

  7    begin

  8      select *

  9        into rec_ic01

 10        from ic01

 11       where 1 = 0;

 12    exception

 13      when no_data_found then

 14        dbms_output.put_line( 'error1: no_data_found exception accured!' );

 15    end;

 16

 17    --2. bulk collect, won't cause no_data_found

 18    select *

 19      bulk collect into nt_ic01

 20      from ic01

 21     where 1 = 0;

 22    if nt_ic01.count = 0 then

 23      dbms_output.put_line( 'error2: There is no data in nt_ic01.' );

 24    end if;

 25  end;

 26  /

error1: no_data_found exception accured!

error2: There is no data in nt_ic01.

 

PL/SQL 过程已成功完成。

 

3. 其他使用
 
除了 SELECT INTO 外,还可以在 FETCH INTO 和 RETURNING INTO 子句中使用 BULK COLLECT:
 

-- 1. fetch collect into

SQL> declare

  2    cursor cur_ic01 is

  3      select *

  4        from ic01

  5       where rownum < 10;

  6

  7    type tnt_ic01 is table of cur_ic01%rowtype;

  8    nt_ic01 tnt_ic01;

  9  begin

 10    open cur_ic01;

 11    fetch cur_ic01 bulk collect into nt_ic01;

 12    close cur_ic01;

 13    dbms_output.put_line( 'COUNT: ' || nt_ic01.count );

 14  end;

 15  /

COUNT: 9

 

PL/SQL 过程已成功完成。

 

-- 2. returning into

SQL> select * from test_return;

 

AAC001               AAC003
-------------------- ----------
1304012080949        转入人7
1304012080950        转入人8
1304012080951        转入人9
1304012080952        转入

 

SQL> declare
  2    type tnt_return is table of test_return%rowtype;
  3    nt_return tnt_return;
  4  begin
  5    update test_return
  6       set aac003 = 'yuechaotian'
  7     where rownum < 3
  8    
returning aac001, aac003 bulk collect into nt_return;
  9
 10    for n_pointer in 1..nt_return.count loop
 11      dbms_output.put_line( 'AAC001=' || nt_return(n_pointer).aac001
 12                      || ' | AAC003=' || nt_return(n_pointer).aac003 );
 13    end loop;
 14  end;
 15  /
AAC001=1304012080949 | AAC003=yuechaotian
AAC001=1304012080950 | AAC003=yuechaotian

 

PL/SQL 过程已成功完成。

 

SQL> select * from test_return;

AAC001               AAC003
-------------------- ------------
1304012080949        yuechaotian
1304012080950        yuechaotian
1304012080951        转入人9
1304012080952        转入

SQL>

 
4. 联合数组中使用 BULK COLLECT
 
前面我们举的例子,都是用的嵌套表,其实也可以使用联合数组:

 

SQL> declare

  2    cursor cur_ic01 is

  3      select *

  4        from ic01

  5       where rownum < 10;

  6

  7    type tnt_ic01 is table of cur_ic01%rowtype index by pls_integer;

  8    nt_ic01 tnt_ic01;

  9  begin

 10    open cur_ic01;

 11    fetch cur_ic01 bulk collect into nt_ic01;

 12    close cur_ic01;

 13    dbms_output.put_line( 'COUNT: ' || nt_ic01.count );

 14  end;

 15  /

COUNT: 9

 

PL/SQL 过程已成功完成。

 

BULK COLLECT 生成的集合,下表是默认从1开始的数字,步进为1,所以联合数组的索引,不能使用 varchar2 类型。如果你不小心使用了 varchar2 类型作为联合数组的索引,就会出现下面的错误:

 

ERROR 位于第 11 :

ORA-06550: 11 , 36 :

PLS-00657: 实施约束: 不支持带有 VARCHAR2 关键字的关联性数组的批 SQL

 

5. VARRAY 数组中使用 BULK COLLECT

 

而对于 VARRAY 数组,BULK COLLECT 也适用,不过定义时,其长度必须得大于或等于结果集的长度:

 

SQL> declare

  2    cursor cur_ic01 is

  3      select *

  4        from ic01

  5       where rownum < 10;

  6

  7    type tnt_ic01 is varray(9) of ic01%rowtype;

  8    nt_ic01 tnt_ic01;

  9  begin

 10    open cur_ic01;

 11    fetch cur_ic01 bulk collect into nt_ic01;

 12    close cur_ic01;

 13    dbms_output.put_line( 'COUNT: ' || nt_ic01.count );

 14  end;

 15  /

COUNT: 9

 

PL/SQL 过程已成功完成。

 

如果 VARRAY 数组长度定义得过小,则会提示错误:

 

SQL> declare

  2    cursor cur_ic01 is

  3      select *

  4        from ic01

  5       where rownum < 10;

  6

  7    type tnt_ic01 is varray(8) of ic01%rowtype;

  8    nt_ic01 tnt_ic01;

  9  begin

 10    open cur_ic01;

 11    fetch cur_ic01 bulk collect into nt_ic01;

 12    close cur_ic01;

 13    dbms_output.put_line( 'COUNT: ' || nt_ic01.count );

 14  end;

 15  /

declare

*

ERROR 位于第 1 :

ORA-22165: 指定的索引 [9] 必须介于 [1] [8] 之间

ORA-06512: line 11

 

如果 VARRAY 数组长度定义得过大,没什么影响,而且也不会占用多余的内存空间:

 

SQL> declare

  2    cursor cur_ic01 is

  3      select *

  4        from ic01

  5       where rownum < 10;

  6

  7    type tnt_ic01 is varray(10) of ic01%rowtype;

  8    nt_ic01 tnt_ic01;

  9  begin

 10    open cur_ic01;

 11    fetch cur_ic01 bulk collect into nt_ic01;

 12    close cur_ic01;

 13    dbms_output.put_line( 'COUNT: ' || nt_ic01.count );

 14    dbms_output.put_line( 'LIMIT: ' || nt_ic01.limit );

 15  end;

 16  /

COUNT: 9

LIMIT: 10

 

PL/SQL 过程已成功完成。

 

不过推荐首选联合数组,或是嵌套表,在这里虽然可以使用 VARRAY 数组,但显然是不太合适的,就像steven 所介绍的那样。

 

6. 动态 SQL 中使用 BULK COLLECT

 

Oracle9i 之前,BULK COLLECT 只能用在静态 SQL 中,从 Oracle9i 开始,动态 SQL 中也可以使用 BULK COLLECT:

 

SQL> select * from v$version;

BANNER
----------------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE    10.2.0.1.0      Production
TNS for 32-bit Windows: Version 10.2.0.1.0 - Production
NLSRTL Version 10.2.0.1.0 - Production

SQL> declare

  2    type tnt_ic01 is table of ic01%rowtype;

  3    nt_ic01 tnt_ic01;

  4

  5    v_sql_cursor varchar2(1000);

  6  begin

  7    v_sql_cursor := 'select * from ic01 where rownum < 100000';

  8    execute immediate v_sql_cursor bulk collect into nt_ic01;

  9    dbms_output.put_line( 'COUNT: ' || nt_ic01.count );

 10  end;

 11  /

COUNT: 99999

 

PL/SQL 过程已成功完成。

 

SQL>

 

阅读(2754) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~