隐式游标
如前所述,DML操作和单行SELECT语句会使用隐式游标,它们是:
* 插入操作:INSERT。
* 更新操作:UPDATE。
* 删除操作:DELETE。
* 单行查询操作:SELECT ... INTO ...。
当系统使用一个隐式游标时,可以通过隐式游标的属性来了解操作的状态和结果,进而控制程序的流程。隐式游标可以使用名字SQL来访问,但要注意,通过SQL游标名总是只能访问前一个DML操作或单行SELECT操作的游标属性。所以通常在刚刚执行完操作之后,立即使用SQL游标名来访问属性。游标的属性有四种,如下所示:
sql%found (布尔类型,默认值为null)
sql%notfound(布尔类型,默认值为null)
sql%rowcount(数值类型默认值为0)
sql%isopen(布尔类型)
当执行一条DML语句后,DML语句的结果保存在四个游标属性中,这些属性用于控制程序流程或者了解程序的状态。当运行DML语句时,PL/SQL打开一个内建游标并处理结果,游标是维护查询结果的内存中的一个区域,游标在运行DML语句时打开,完成后关闭。隐式游标只使用SQL%FOUND,SQL%NOTFOUND,SQL%ROWCOUNT三个属性.SQL%FOUND,SQL%NOTFOUND是布尔值,SQL%ROWCOUNT是整数值。
SQL%FOUND和SQL%NOTFOUND
在执行任何DML语句前SQL%FOUND和SQL%NOTFOUND的值都是NULL,在执行DML语句后,SQL%FOUND的属性值将是:
. TRUE :INSERT
. TRUE :DELETE和UPDATE,至少有一行被DELETE或UPDATE.
. TRUE :SELECT INTO至少返回一行
当SQL%FOUND为TRUE时,SQL%NOTFOUND为FALSE。
SQL%ROWCOUNT
在执行任何DML语句之前,SQL%ROWCOUNT的值都是NULL,对于SELECT INTO语句,如果执行成功,SQL%ROWCOUNT的值为1,如果没有成功或者没有操作(如update、insert、delete为0条),SQL%ROWCOUNT的值为0,而对于update和delete来说表示游标所检索数据库行的个数即更新或者删除的行数。
SQL%ISOPEN
SQL%ISOPEN是一个布尔值,如果游标打开,则为TRUE, 如果游标关闭,则为FALSE.对于隐式游标而言SQL%ISOPEN总是FALSE,这是因为隐式游标在DML语句执行时打开,结束时就立即关闭。
最后我们来说一下隐式游标中SELECT..INTO 语句,当执行的时候会有三种可能:
(1).结果集只含有一行,且select是成功的
(2).没有查询到任何结果集,引发NO_DATA_FOUND异常
(3).结果集中含有两行或者更多行,引发TOO_MANY_ROWS异常。
例子:
BEGIN UPDATE exchangerate SET rate=7 where quarter='2011Q1'; DBMS_output.put_line('游标所影响的行数:'||SQL%rowcount); if SQL%NotFound then DBMS_output.put_line('NotFound为真'); DBMS_output.put_line('NofFound为假'); end if; if SQL%Found then DBMS_output.put_line('Found为真'); else DBMS_output.put_line('Found为假'); end if; if SQL%isopen then DBMS_output.put_line('isOpen为真'); else DBMS_output.put_line('isOpen为假'); end if; END;
动态游标
与游标一样,动态游标(游标变量)也是一个指向多行查询结果集合中当前数据行的指针。但与游标不同的是,游标变量是动态的,而游标是静态的。
游标只能与指定的查询相连,即固定指向一个查询的内存处理区域,而游标变量则可与不同的查询语句相连,它可以指向不同查询语句的内存处理区域(但不能同时指向多个内存处理区域,在某一时刻只能与一个查询语句相连),只要这些查询语句的返回类型兼容即可。
- DECLARE
- --定义一个游标数据类型
- TYPE emp_cursor_type IS REF CURSOR;
- --声明一个游标变量
- c1 EMP_CURSOR_TYPE;
- --声明两个记录变量
- v_emp_record employees%ROWTYPE;
- v_reg_record regions%ROWTYPE;
- BEGIN
- OPEN c1 FOR SELECT * FROM employees WHERE department_id = 20;
- LOOP
- FETCH c1 INTO v_emp_record;
- EXIT WHEN c1%NOTFOUND;
- DBMS_OUTPUT.PUT_LINE(v_emp_record.first_name||'的雇佣日期是'
- ||v_emp_record.hire_date);
- END LOOP;
- --将同一个游标变量对应到另一个SELECT语句
- OPEN c1 FOR SELECT * FROM regions WHERE region_id IN(1,2);
- LOOP
- FETCH c1 INTO v_reg_record;
- EXIT WHEN c1%NOTFOUND;
- DBMS_OUTPUT.PUT_LINE(v_reg_record.region_id||'表示'
- ||v_reg_record.region_name);
- END LOOP;
- CLOSE c1;
- END;
显式游标:
游标的定义和操作
游标的使用分成以下4个步骤。
1.声明游标
在DECLEAR部分按以下格式声明游标:
CURSOR 游标名[(参数1 数据类型[,参数2 数据类型...])]
IS SELECT语句;
参数是可选部分,所定义的参数可以出现在SELECT语句的WHERE子句中。如果定义了参数,则必须在打开游标时传递相应的实际参数。
SELECT语句是对表或视图的查询语句,甚至也可以是联合查询。可以带WHERE条件、ORDER BY或GROUP BY等子句,但不能使用INTO子句。在SELECT语句中可以使用在定义游标之前定义的变量。
2.打开游标
在可执行部分,按以下格式打开游标:
OPEN 游标名[(实际参数1[,实际参数2...])];
打开游标时,SELECT语句的查询结果就被传送到了游标工作区。
3.提取数据
在可执行部分,按以下格式将游标工作区中的数据取到变量中。提取操作必须在打开游标之后进行。
FETCH 游标名 INTO 变量名1[,变量名2...];
或
FETCH 游标名 INTO 记录变量;
游标打开后有一个指针指向数据区,FETCH语句一次返回指针所指的一行数据,要返回多行需重复执行,可以使用循环语句来实现。控制循环可以通过判断游标的属性来进行。
下面对这两种格式进行说明:
第一种格式中的变量名是用来从游标中接收数据的变量,需要事先定义。变量的个数和类型应与SELECT语句中的字段变量的个数和类型一致。
第二种格式一次将一行数据取到记录变量中,需要使用%ROWTYPE事先定义记录变量,这种形式使用起来比较方便,不必分别定义和使用多个变量。
定义记录变量的方法如下:
变量名 表名|游标名%ROWTYPE;
其中的表必须存在,游标名也必须先定义。
4.关闭游标
CLOSE 游标名;
显式游标打开后,必须显式地关闭。游标一旦关闭,游标占用的资源就被释放,游标变成无效,必须重新打开才能使用。
现在通过一个例子来学习一下显示游标的使用方法:
有一个表原来结构是如下的
create table EXCHANGERATE ( QUARTER VARCHAR2(20), RATE NUMBER(10,4), DESCRIPTION VARCHAR2(900), ID VARCHAR2(10) not null, CURRENCY VARCHAR2(100) )
这是一个汇率表里面维护着的是季度 币种和汇率的关系,现在有一个新的需求是在原来表的基础上增加一列名字为currentmonth,变为季度、季度中月份、 币种和汇率的关系,
并且使原来每个季度对应的币种和汇率变成每个季度 对应该季度月份 币种和汇率,每个月的默认值为原来季度对应的值。
例如 原来 2013Q2 CNY 6.2
现在我们要变为2013Q2 2013-04 CNY 6.2 2013Q2 2013-05 CNY 6.2
2013Q2 2013-06 CNY 6.2 三条记录。
通过分析以上需求,我们首先要增加一列:
alter table exchangerate add currentmonth varchar2(20);
然后我们通过在匿名块中通过显示游标来实现以上需求:
1 declare 2 3 v_year varchar2(20); 4 v_month number; 5 p_rate exchangerate%rowtype; 6 cursor c_rate is select * from exchangerate t where t.currentmonth is null; 7 begin 8 open c_rate; 9 loop 10 fetch c_rate into p_rate; 11 v_year:=substr(p_rate.quarter, 0, 4); 12 v_month:=(to_number(substr(p_rate.quarter,6,1)) - 1) * 3; 13 for i in 1 .. 3 loop 14 15 insert into exchangerate(id,quarter,currentmonth,rate,currency,Description) 16 values(SEQUENCE_EXCHANGERATE.nextval,p_rate.quarter, 17 to_char(to_date(v_year||(v_month+i),'yyyyMM'),'yyyy-MM'),p_rate.rate,p_rate.currency,p_rate.description); 18 19 end loop; 20 exit when c_rate%notfound; 21 end loop; 22 close c_rate; 23 end; 24 / 25 复制代码 26 我们把上面的例子有游标的for循环来改写一下。 27 28 29 30 显式游标的for循环 31 32 复制代码 33 declare 34 35 v_year varchar2(20); 36 v_month number; 37 38 cursor c_rate is select * from exchangerate t where t.currentmonth is null; 39 40 begin 41 42 for p_rate in c_rate loop 43 44 v_year:=substr(p_rate.quarter, 0, 4); 45 46 v_month:=(to_number(substr(p_rate.quarter,6,1)) - 1) * 3; 47 48 for i in 1 .. 3 loop 49 50 insert into exchangerate(id,quarter,currentmonth,rate,currency,Description) 51 values(SEQUENCE_EXCHANGERATE.nextval,p_rate.quarter, 52 to_char(to_date(v_year||(v_month+i),'yyyyMM'),'yyyy-MM'),p_rate.rate,p_rate.currency,p_rate.description); 53 54 end loop; 55 56 end loop; 57 58 end; 59 60 /
我们可以看到游标FOR循环确实很好的简化了游标的开发,我们不在需要open、fetch和close语句,不在需要用%FOUND属性检测是否到最后一条记录,这一切Oracle隐式的帮我们完成了。
隐式游标的for循环
1 declare 2 3 v_year varchar2(20); 4 v_month number; 5 6 begin 7 8 for p_rate in (select * from exchangerate t where t.currentmonth is null) loop 9 10 v_year:=substr(p_rate.quarter, 0, 4); 11 v_month:=(to_number(substr(p_rate.quarter,6,1)) - 1) * 3; 12 for i in 1 .. 3 loop 13 14 insert into exchangerate(id,quarter,currentmonth,rate,currency,Description) 15 values(SEQUENCE_EXCHANGERATE.nextval,p_rate.quarter, 16 to_char(to_date(v_year||(v_month+i),'yyyyMM'),'yyyy-MM'),p_rate.rate,p_rate.currency,p_rate.description); 17 18 end loop; 19 20 end loop; 21 22 end; 23 /
显示游标中游标参数的传递
例子:就以上面的表来说 加入我们在定义游标时不确定查询条件中的值,这时我们可以通过游标参数来解决
1 declare 2 3 v_year varchar2(20); 4 v_month number; 5 p_rate exchangerate%rowtype; 6 7 cursor c_rate(p_quarter varchar2) --声明游标带参数 8 is 9 select * from exchangerate t where t.quarter<=p_quarter; 10 11 begin 12 open c_rate(p_quarter=>'2011Q3');--打开游标,传递参数值 13 loop 14 15 fetch c_rate into p_rate; 16 17 update exchangerate set rate=p_rate.rate+1 where id=p_rate.id; 18 19 20 exit when c_rate%notfound; 21 22 end loop; 23 24 close c_rate; 25 26 end;
游标变量
游标是数据库中一个命名的工作区,当游标被声明后,他就与一个固定的SQL想关联,在编译时刻是已知的,是静态的.它永远指向一个相同的查询工作区.
游标变量是动态的可以在运行时刻与不同的SQL语句关联,在运行时可以取不同的SQL语句.它可以引用不同的工作区.
如何定义游标类型
TYPE ref_type_name IS REF CURSOR
[RETURN return_type];
声明游标变量
cursor_name ref_type_name;
ref_type_name 是后面声明游标变量时要用到的我们的游标类型(自定义游标类型,即CURSOR是系统默认的,ref_type_name是我们定义的 );
return_type代表数据库表中的一行,或一个记录类型
TYPE ref_type_name IS REF CURSOR RETURN EMP%TYPE
RETURN 是可选的,如果有是强类型,可以减少错误,如果没有return是弱引用,有较好的灵活性.
游标变量的操作
例子:
1 declare 2 3 v_year varchar2(20); 4 v_month number; 5 p_rate exchangerate%rowtype; 6 7 type rate is ref cursor;--定义游标变量 8 c_rate rate; --声明游标变量 9 10 begin 11 12 13 open c_rate for select * from exchangerate t where t.quarter='2011Q3';--打开游标变量 loop 14 15 fetch c_rate into p_rate;--提取游标变量 16 17 update exchangerate set rate=p_rate.rate+1 where id=p_rate.id; 18 19 exit when c_rate%notfound; 20 21 end loop; 22 23 --将同一个游标变量对应到另一个SELECT语句 24 25 open c_rate for select * from exchangerate t where t.quarter='2011Q2';--打开游标变量 loop 26 27 fetch c_rate into p_rate;--提取游标变量 28 29 update exchangerate set rate=p_rate.rate-1 where id=p_rate.id; 30 31 exit when c_rate%notfound; 32 33 end loop; 34 close c_rate;--关闭游标变量 35 36 end;
游标表达式
Oracle在SQL语言中提供了一个强有力的工具:游标表达式。一个游标表达式从一个查询中返回一个内嵌的游标。在这个内嵌游标的结果集中,每一行数据包含了在SQL查询中的可允许的数值范围;它也能包含被其他子查询所产生的游标。
因此,你能够使用游标表达式来返回一个大的和复杂的,从一张或多张表获取的数据集合。游标表达式的复杂程度,取决于查询和结果集。然而,了解所有从Oracle RDBMS提取数据的可能途径,还有大有好处的。
你能够在以下任何一种情况使用游标表达式:
(1)、 显式游标声明
(2)、动态SQL查询。
(3)、REF CURSOR 声明和变量。
你不能在一个隐式查询中使用游标表达式。
游标表达式的语法是相当简单的:
CURSOR (查询语句)
当Oracle从父游标或外围游标那里检取包含游标表达式的数据行时,Oracle就会隐式地打开一个内嵌的游标,这个游标就是被上述的游标表达式所定义。在以下情况发生时,这个内迁游标将会被关闭:
(1)、你显式地关闭这个游标。
(2)、外围或父游标被重新执行,关闭或撤销。
(3)、当从父游标检取数据时,发生异常。内嵌游标就会与父游标一起被关闭。
使用游标表达式
你可以通过两种不同的,但是非常有用的方法来使用游标表达式:
1. 在一个外围查询中把字查询作为一列来检取数据。
2. 把一个查询转换成一个结果集,而这个结果集就可以被当成一个参数传递给一个流型或变换函数。
例子:
1 CREATE OR REPLACE PROCEDURE emp_report(p_locid NUMBER) 2 IS 3 TYPE refcursor IS REF CURSOR; 4 CURSOR all_in_one IS 5 SELECT l.city, CURSOR( 6 SELECT d.department_name, CURSOR ( 7 SELECT e.last_name 8 FROM employees e 9 WHERE e.DEPARTMENT_ID = d.DEPARTMENT_ID 10 ) as ename 11 FROM departments d 12 WHERE d.LOCATION_ID = l.LOCATION_ID 13 ) as dname 14 FROM locations l 15 WHERE l.location_id = p_locid; 16 departments_cur refcursor; 17 employees_cur refcursor; 18 v_city locations.city%type; 19 v_dname departments.department_name%type; 20 v_ename employees.last_name%type; 21 i integer :=1; 22 j integer :=1; 23 k integer :=1; 24 BEGIN 25 OPEN all_in_one; 26 LOOP 27 FETCH all_in_one INTO v_city, departments_cur; 28 EXIT WHEN all_in_one%NOTFOUND; 29 LOOP 30 FETCH departments_cur INTO v_dname, employees_cur; 31 EXIT WHEN departments_cur%NOTFOUND; 32 LOOP 33 FETCH employees_cur INTO v_ename; 34 EXIT WHEN employees_cur%NOTFOUND; 35 dbms_output.put_line(i || ' , ' || j || ' , ' || k || '----' || v_city || ' ,' || v_dname || ' ,' || v_ename ); 36 k := k + 1; 37 END LOOP; 38 j := j + 1; 39 END LOOP; 40 i := i + 1; 41 END LOOP; 42 END; 43 /