Oracle中游标Cursor基本用法详解
查询
select语句用于从数据库中查询数据,当在pl/sql中使用select语句时,要与into子句一起使用,查询的
返回值被赋予into子句中的变量,变量的声明是在delcare中。select into语法如下:
select [distict|all]{*|column[,column,...]} into (variable[,variable,...] |record) from {table|(sub-query)}[alias] where............
pl/sql中select语句只返回一行数据。如果超过一行数据,那么就要使用显式游标(对游标的讨论我们将
在后面进行),into子句中要有与select子句中相同列数量的变量。into子句中也可以是记录变量。
%type属性
在pl/sql中可以将变量和常量声明为内建或用户定义的数据类型,以引用一个列名,同时继承他的数据类
型和大小。这种动态赋值方法是非常有用的,比如变量引用的列的数据类型和大小改变了,如果使用了%type,
那么用户就不必修改代码,否则就必须修改代码。
例:
v_empno scott.emp.empno%type; v_salary emp.salary%type;
不但列名可以使用%type,而且变量、游标、记录,或声明的常量都可以使用%type。这对于定义相同数据类
型的变量非常有用。
delcare v_a number(5):=10; v_b v_a%type:=15; v_c v_a%type; begin dbms_output.put_line ('v_a='||v_a||'v_b='||v_b||'v_c='||v_c); end sql>/ v_a=10 v_b=15 v_c= pl/sql procedure successfully completed. sql>
其他dml语句
其它操作数据的dml语句是:insert、update、delete和lock table,这些语句在pl/sql中的语法与在sql中
的语法相同。我们在前面已经讨
论过dml语句的使用这里就不再重复了。在dml语句中可以使用任何在declare部分声明的变量,如果是嵌套
块,那么要注意变量的作用范围。
例:
create or replace procedure fire_employee (pempno in number) as v_ename emp.ename%type; begin select ename into v_ename from emp where empno=p_empno; insert into former_emp(empno,ename) values (p_empno,v_ename); delete from emp where empno=p_empno; update former_emp set date_deleted=sysdate where empno=p_empno; exception when no_data_found then dbms_output.put_line('employee number not found!'); end
dml语句的结果
当执行一条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,如果没有成功,sql%rowcount的值为0,同时产生一个异常no_data_found.
sql%isopen
sql%isopen是一个布尔值,如果游标打开,则为true, 如果游标关闭,则为false.对于隐式游标而言sql%
isopen总是false,这是因为隐式游
标在dml语句执行时打开,结束时就立即关闭。
事务控制语句
事务是一个工作的逻辑单元可以包括一个或多个dml语句,事物控制帮助用户保证数据的一致性。如果事务控制逻辑单元中的任何一个dml
语句失败,那么整个事务都将回滚,在pl/sql中用户可以明确地使用commit、rollback、savepoint以及
set transaction语句。
commit语句终止事务,永久保存数据库的变化,同时释放所有lock,rollback终止现行事务释放所有lock,
但不保存数据库的任何变化,savepoi
nt用于设置中间点,当事务调用过多的数据库操作时,中间点是非常有用的,set transaction用于设置事
务属性,比如read-write和隔离级等。
显式游标
查询返回结果超过一行时,就需要一个显式游标,此时用户不能使用select into语句。pl/sql管理隐式
游标,当查询开始时隐式游标打开,查询结束时隐式游标自动关闭。显式游标在pl/sql块的声明部分声明,在执行部分或异常处理部分打开,取数据,关闭。
使用游标
这里要做一个声明,我们所说的游标通常是指显式游标,因此从现在起没有特别指明的情况,我们所说的
游标都是指显式游标。要在程序中使用游标,必须首先声明游标。
声明游标
语法:
cursor cursor_name is select_statement;
在pl/sql中游标名是一个未声明变量,不能给游标名赋值或用于表达式中。
例:
delcare cursor c_emp is select empno,ename,salary from emp where salary>2000 order by ename; ........ begin
在游标定义中select语句中不一定非要表可以是视图,也可以从多个表或视图中选择的列,甚至可以使用*
来选择所有的列 。
打开游标
使用游标中的值之前应该首先打开游标,打开游标初始化查询处理。打开游标的语法是:
open cursor_name
cursor_name是在声明部分定义的游标名。
例:
open c_emp;
关闭游标
语法:
close cursor_name
例:
close c_emp;
从游标提取数据
从游标得到一行数据使用fetch命令。每一次提取数据后,游标都指向结果集的下一行。语法如下:
fetch cursor_name into variable[,variable,...]
对于select定义的游标的每一列,fetch变量列表都应该有一个变量与之相对应,变量的类型也要相同。
例:
set serveriutput on declare v_ename emp.ename%type; v_salary emp.salary%type; cursor c_emp is select ename,salary from emp; begin open c_emp; fetch c_emp into v_ename,v_salary; dbms_output.put_line('salary of employee'|| v_ename ||'is'|| v_salary); fetch c_emp into v_ename,v_salary; dbms_output.put_line('salary of employee'|| v_ename ||'is'|| v_salary); fetch c_emp into v_ename,v_salary; dbms_output.put_line('salary of employee'|| v_ename ||'is'|| v_salary); close c_emp; end
这段代码无疑是非常麻烦的,如果有多行返回结果,可以使用循环并用游标属性为结束循环的条件,以这
种方式提取数据,程序的可读性和简洁性都大为提高,下面我们使用循环重新写上面的程序:
set serveriutput on declare v_ename emp.ename%type; v_salary emp.salary%type; cursor c_emp is select ename,salary from emp; begin open c_emp; loop fetch c_emp into v_ename,v_salary; exit when c_emp%notfound; dbms_output.put_line('salary of employee'|| v_ename ||'is'|| v_salary); end
记录变量
定义一个记录变量使用type命令和%rowtype,关于%rowstype的更多信息请参阅相关资料。
记录变量用于从游标中提取数据行,当游标选择很多列的时候,那么使用记录比为每列声明一个变量要方
便得多。
当在表上使用%rowtype并将从游标中取出的值放入记录中时,如果要选择表中所有列,那么在select子句
中使用*比将所有列名列出来要安全得多。
例:
set serveriutput on declare r_emp emp%rowtype; cursor c_emp is select * from emp; begin open c_emp; loop fetch c_emp into r_emp; exit when c_emp%notfound; dbms_out.put.put_line('salary of employee'||r_emp.ename||'is'|| r_emp.salary); end loop; close c_emp; end;
%rowtype也可以用游标名来定义,这样的话就必须要首先声明游标:
set serveriutput on declare cursor c_emp is select ename,salary from emp; r_emp c_emp%rowtype; begin open c_emp; loop fetch c_emp into r_emp; exit when c_emp%notfound; dbms_out.put.put_line('salary of employee'||r_emp.ename||'is'|| r_emp.salary); end loop; close c_emp; end;
带参数的游标
与存储过程和函数相似,可以将参数传递给游标并在查询中使用。这对于处理在某种条件下打开游标的情
况非常有用。它的语法如下:
cursor cursor_name[(parameter[,parameter],...)] is select_statement;
定义参数的语法如下:
parameter_name [in] data_type[{:=|default} value]
与存储过程不同的是,游标只能接受传递的值,而不能返回值。参数只定义数据类型,没有大小。
另外可以给参数设定一个缺省值,当没有参数值传递给游标时,就使用缺省值。游标中定义的参数只是一个占位符,在别处引用该参数不一定可靠。
在打开游标时给参数赋值,语法如下:
open cursor_name[value[,value]....];
参数值可以是文字或变量。
例:
decalre cursor c_dept is select * from dept order by deptno; cursor c_emp (p_dept varachar2) is select ename,salary from emp where deptno=p_dept order by ename r_dept dept%rowtype; v_ename emp.ename%type; v_salary emp.salary%type; v_tot_salary emp.salary%type; begin open c_dept; loop fetch c_dept into r_dept; exit when c_dept%notfound; dbms_output.put_line('department:'|| r_dept.deptno||'-'||r_dept.dname); v_tot_salary:=0; open c_emp(r_dept.deptno); loop fetch c_emp into v_ename,v_salary; exit when c_emp%notfound; dbms_output.put_line('name:'|| v_ename||' salary:'||v_salary); v_tot_salary:=v_tot_salary+v_salary; end loop; close c_emp; dbms_output.put_line('toltal salary for dept:'|| v_tot_salary); end loop; close c_dept; end;
游标for循环
在大多数时候我们在设计程序的时候都遵循下面的步骤:
1、打开游标
2、开始循环
3、从游标中取值
4、检查那一行被返回
5、处理
6、关闭循环
7、关闭游标
可以简单的把这一类代码称为游标用于循环。但还有一种循环与这种类型不相同,这就是for循环,用于
for循环的游标按照正常的声明方式声明,它的优点在于不需要显式的打开、关闭、取数据,测试数据的存在、定义存放数据的变量等等。
游标for循环的语法如下:
for record_name in (corsor_name[(parameter[,parameter]...)] | (query_difinition) loop statements end loop;
下面我们用for循环重写上面的例子:
decalre cursor c_dept is select deptno,dname from dept order by deptno; cursor c_emp (p_dept varachar2) is select ename,salary from emp where deptno=p_dept order by ename v_tot_salary emp.salary%type; begin for r_dept in c_dept loop dbms_output.put_line('department:'|| r_dept.deptno||'-'||r_dept.dname); v_tot_salary:=0; for r_emp in c_emp(r_dept.deptno) loop dbms_output.put_line('name:' || v_ename || 'salary:' || v_salary); v_tot_salary:=v_tot_salary+v_salary; end loop; dbms_output.put_line('toltal salary for dept:'|| v_tot_salary); end loop; end;
在游标for循环中使用查询
在游标for循环中可以定义查询,由于没有显式声明所以游标没有名字,记录名通过游标查询来定义。
decalre v_tot_salary emp.salary%type; begin for r_dept in (select deptno,dname from dept order by deptno) loop dbms_output.put_line('department:'|| r_dept.deptno||'-'||r_dept.dname); v_tot_salary:=0; for r_emp in (select ename,salary from emp where deptno=p_dept order by ename) loop dbms_output.put_line('name:'|| v_ename||' salary:'||v_salary); v_tot_salary:=v_tot_salary+v_salary; end loop; dbms_output.put_line('toltal salary for dept:'|| v_tot_salary); end loop; end;
游标中的子查询
语法如下:
cursor c1 is select * from emp where deptno not in (select deptno from dept where dname!='accounting');
可以看出与sql中的子查询没有什么区别。
游标中的更新和删除
在pl/sql中依然可以使用update和delete语句更新或删除数据行。显式游标只有在需要获得多行数据的情
况下使用。pl/sql提供了仅仅使 用游标就可以执行删除或更新记录的方法。
update或delete语句中的where current of子串专门处理要执行update或delete操作的表中取出的最近的
数据。要使用这个方法,在声明游标 时必须使用for update子串,当对话使用for update子串打开一个游标时,所有返回集中的数据行都将处于行级(row-level)独占式锁定,其
他对象只能查询这些数据行,不能进行update、delete或select...for update操作。
语法:
for update [of [schema.]table.column[,[schema.]table.column]..
[nowait]
在多表查询中,使用of子句来锁定特定的表,如果忽略了of子句,那么所有表中选择的数据行都将被锁定。
如果这些数据行已经被其他会话锁定,那么正常情况下oracle将等待,直到数据行解锁。
在update和delete中使用where current of子串的语法如下:
where{current of cursor_name|search_condition}
例:
delcare cursor c1 is select empno,salary from emp where comm is null for update of comm; v_comm number(10,2); begin for r1 in c1 loop if r1.salary<500 then v_comm:=r1.salary*0.25; elseif r1.salary<1000 then v_comm:=r1.salary*0.20; elseif r1.salary<3000 then v_comm:=r1.salary*0.15; else v_comm:=r1.salary*0.12; end if; update emp; set comm=v_comm where current of c1l; end loop; end
-声明游标 --宗地表的调查日期landinfo_researchdate --复制到流程表的权属调查时间flow_reasearchtime declare cursor cur_sel_all is select landinfo_researchdate,landinfo_landno from t_leoa_landinfo; --定义游标 l_date t_leoa_landinfo.landinfo_researchdate%type; --声明变量分别保存t_leoa_landinfo的各列 l_landno t_leoa_landinfo.landinfo_landno%type; begin open cur_sel_all; loop --循环取数,并将游标数据填充到返回纪录集合中 fetch cur_sel_all into l_date,l_landno; exit when cur_sel_all%notfound; --循环退出条件 if cur_sel_all%found then --获取数据 update t_leoa_bookflow t2 set flow_reasearchtime = l_date where l_landno = t2.landinfo_landno; end if; end loop; close cur_sel_all; end;
下面再分享一下另外一则游标使用方法的代码,具体如下:
-- 声明游标;cursor cursor_name is select_statement --for 循环游标 --(1)定义游标 --(2)定义游标变量 --(3)使用for循环来使用这个游标 declare --类型定义 cursor c_job is select empno,ename,job,sal from emp where job='manager'; --定义一个游标变量v_cinfo c_emp%rowtype ,该类型为游标c_emp中的一行数据类型 c_row c_job%rowtype; begin for c_row in c_job loop dbms_output.put_line(c_row.empno||'-'||c_row.ename||'-'||c_row.job||'-'||c_row.sal); end loop; end; --fetch游标 --使用的时候必须要明确的打开和关闭 declare --类型定义 cursor c_job is select empno,ename,job,sal from emp where job='manager'; --定义一个游标变量 c_row c_job%rowtype; begin open c_job; loop --提取一行数据到c_row fetch c_job into c_row; --判读是否提取到值,没取到值就退出 --取到值c_job%notfound 是false --取不到值c_job%notfound 是true exit when c_job%notfound; dbms_output.put_line(c_row.empno||'-'||c_row.ename||'-'||c_row.job||'-'||c_row.sal); end loop; --关闭游标 close c_job; end; --1:任意执行一个update操作,用隐式游标sql的属性%found,%notfound,%rowcount,%isopen观察update语句的执行情况。 begin update emp set ename='aleark' where empno=7469; if sql%isopen then dbms_output.put_line('openging'); else dbms_output.put_line('closing'); end if; if sql%found then dbms_output.put_line('游标指向了有效行');--判断游标是否指向有效行 else dbms_output.put_line('sorry'); end if; if sql%notfound then dbms_output.put_line('also sorry'); else dbms_output.put_line('haha'); end if; dbms_output.put_line(sql%rowcount); exception when no_data_found then dbms_output.put_line('sorry no data'); when too_many_rows then dbms_output.put_line('too many rows'); end; declare empnumber emp.empno%type; empname emp.ename%type; begin if sql%isopen then dbms_output.put_line('cursor is opinging'); else dbms_output.put_line('cursor is close'); end if; if sql%notfound then dbms_output.put_line('no value'); else dbms_output.put_line(empnumber); end if; dbms_output.put_line(sql%rowcount); dbms_output.put_line('-------------'); select empno,ename into empnumber,empname from emp where empno=7499; dbms_output.put_line(sql%rowcount); if sql%isopen then dbms_output.put_line('cursor is opinging'); else dbms_output.put_line('cursor is closing'); end if; if sql%notfound then dbms_output.put_line('no value'); else dbms_output.put_line(empnumber); end if; exception when no_data_found then dbms_output.put_line('no value'); when too_many_rows then dbms_output.put_line('too many rows'); end; --2,使用游标和loop循环来显示所有部门的名称 --游标声明 declare cursor csr_dept is --select语句 select dname from depth; --指定行指针,这句话应该是指定和csr_dept行类型相同的变量 row_dept csr_dept%rowtype; begin --for循环 for row_dept in csr_dept loop dbms_output.put_line('部门名称:'||row_dept.dname); end loop; end; --3,使用游标和while循环来显示所有部门的的地理位置(用%found属性) declare --游标声明 cursor csr_testwhile is --select语句 select loc from depth; --指定行指针 row_loc csr_testwhile%rowtype; begin --打开游标 open csr_testwhile; --给第一行喂数据 fetch csr_testwhile into row_loc; --测试是否有数据,并执行循环 while csr_testwhile%found loop dbms_output.put_line('部门地点:'||row_loc.loc); --给下一行喂数据 fetch csr_testwhile into row_loc; end loop; close csr_testwhile; end; select * from emp --4,接收用户输入的部门编号,用for循环和游标,打印出此部门的所有雇员的所有信息(使用循环游标) --cursor cursor_name[(parameter[,parameter],...)] is select_statement; --定义参数的语法如下:parameter_name [in] data_type[{:=|default} value] declare cursor c_dept(p_deptno number) is select * from emp where emp.depno=p_deptno; r_emp emp%rowtype; begin for r_emp in c_dept(20) loop dbms_output.put_line('员工号:'||r_emp.empno||'员工名:'||r_emp.ename||'工资:'||r_emp.sal); end loop; end; select * from emp --5:向游标传递一个工种,显示此工种的所有雇员的所有信息(使用参数游标) declare cursor c_job(p_job nvarchar2) is select * from emp where job=p_job; r_job emp%rowtype; begin for r_job in c_job('clerk') loop dbms_output.put_line('员工号'||r_job.empno||' '||'员工姓名'||r_job.ename); end loop; end; select * from emp --6:用更新游标来为雇员加佣金:(用if实现,创建一个与emp表一摸一样的emp1表,对emp1表进行修改操作),并将更新前后的数据输出出来 --http://zheng12tian.iteye.com/blog/815770 create table emp1 as select * from emp; declare cursor csr_update is select * from emp1 for update of sal; empinfo csr_update%rowtype; saleinfo emp1.sal%type; begin for empinfo in csr_update loop if empinfo.sal<1500 then saleinfo:=empinfo.sal*1.2; elsif empinfo.sal<2000 then saleinfo:=empinfo.sal*1.5; elsif empinfo.sal<3000 then saleinfo:=empinfo.sal*2; end if; update emp1 set sal=saleinfo where current of csr_update; end loop; end; --7:编写一个pl/sql程序块,对名字以‘a'或‘s'开始的所有雇员按他们的基本薪水(sal)的10%给他们加薪(对emp1表进行修改操作) declare cursor csr_addsal is select * from emp1 where ename like 'a%' or ename like 's%' for update of sal; r_addsal csr_addsal%rowtype; saleinfo emp1.sal%type; begin for r_addsal in csr_addsal loop dbms_output.put_line(r_addsal.ename||'原来的工资:'||r_addsal.sal); saleinfo:=r_addsal.sal*1.1; update emp1 set sal=saleinfo where current of csr_addsal; end loop; end; --8:编写一个pl/sql程序块,对所有的salesman增加佣金(comm)500 declare cursor csr_addcomm(p_job nvarchar2) is select * from emp1 where job=p_job for update of comm; r_addcomm emp1%rowtype; comminfo emp1.comm%type; begin for r_addcomm in csr_addcomm('salesman') loop comminfo:=r_addcomm.comm+500; update emp1 set comm=comminfo where current of csr_addcomm; end loop; end; --9:编写一个pl/sql程序块,以提升2个资格最老的职员为manager(工作时间越长,资格越老) --(提示:可以定义一个变量作为计数器控制游标只提取两条数据;也可以在声明游标的时候把雇员中资格最老的两个人查出来放到游标中。) declare cursor crs_testcomput is select * from emp1 order by hiredate asc; --计数器 top_two number:=2; r_testcomput crs_testcomput%rowtype; begin open crs_testcomput; fetch crs_testcomput into r_testcomput; while top_two>0 loop dbms_output.put_line('员工姓名:'||r_testcomput.ename||' 工作时间:'||r_testcomput.hiredate); --计速器减一 top_two:=top_two-1; fetch crs_testcomput into r_testcomput; end loop; close crs_testcomput; end; --10:编写一个pl/sql程序块,对所有雇员按他们的基本薪水(sal)的20%为他们加薪, --如果增加的薪水大于300就取消加薪(对emp1表进行修改操作,并将更新前后的数据输出出来) declare cursor crs_upadatesal is select * from emp1 for update of sal; r_updatesal crs_upadatesal%rowtype; saladd emp1.sal%type; salinfo emp1.sal%type; begin for r_updatesal in crs_upadatesal loop saladd:= r_updatesal.sal*0.2; if saladd>300 then salinfo:=r_updatesal.sal; dbms_output.put_line(r_updatesal.ename||': 加薪失败。'||'薪水维持在:'||r_updatesal.sal); else salinfo:=r_updatesal.sal+saladd; dbms_output.put_line(r_updatesal.ename||': 加薪成功.'||'薪水变为:'||salinfo); end if; update emp1 set sal=salinfo where current of crs_upadatesal; end loop; end; --11:将每位员工工作了多少年零多少月零多少天输出出来 --近似 --ceil(n)函数:取大于等于数值n的最小整数 --floor(n)函数:取小于等于数值n的最大整数 --truc的用法 http://publish.it168.com/2005/1028/20051028034101.shtml declare cursor crs_workday is select ename,hiredate, trunc(months_between(sysdate, hiredate) / 12) as spandyears, trunc(mod(months_between(sysdate, hiredate), 12)) as months, trunc(mod(mod(sysdate - hiredate, 365), 12)) as days from emp1; r_workday crs_workday%rowtype; begin for r_workday in crs_workday loop dbms_output.put_line(r_workday.ename||'已经工作了'||r_workday.spandyears||'年,零'||r_workday.months||'月,零'||r_workday.days||'天'); end loop; end; --12:输入部门编号,按照下列加薪比例执行(用case实现,创建一个emp1表,修改emp1表的数据),并将更新前后的数据输出出来 -- deptno raise(%) -- 10 5% -- 20 10% -- 30 15% -- 40 20% -- 加薪比例以现有的sal为标准 --case expr when comparison_expr then return_expr --[, when comparison_expr then return_expr]... [else else_expr] end declare cursor crs_casetest is select * from emp1 for update of sal; r_casetest crs_casetest%rowtype; salinfo emp1.sal%type; begin for r_casetest in crs_casetest loop case when r_casetest.depno=10 then salinfo:=r_casetest.sal*1.05; when r_casetest.depno=20 then salinfo:=r_casetest.sal*1.1; when r_casetest.depno=30 then salinfo:=r_casetest.sal*1.15; when r_casetest.depno=40 then salinfo:=r_casetest.sal*1.2; end case; update emp1 set sal=salinfo where current of crs_casetest; end loop; end; --13:对每位员工的薪水进行判断,如果该员工薪水高于其所在部门的平均薪水,则将其薪水减50元,输出更新前后的薪水,员工姓名,所在部门编号。 --avg([distinct|all] expr) over (analytic_clause) ---作用: --按照analytic_clause中的规则求分组平均值。 --分析函数语法: --function_name(<argument>,<argument>...) --over --(<partition-clause><order-by-clause><windowing clause>) --partition子句 --按照表达式分区(就是分组),如果省略了分区子句,则全部的结果集被看作是一个单一的组 select * from emp1 declare cursor crs_testavg is select empno,ename,job,sal,depno,avg(sal) over (partition by depno ) as dep_avg from emp1 for update of sal; r_testavg crs_testavg%rowtype; salinfo emp1.sal%type; begin for r_testavg in crs_testavg loop if r_testavg.sal>r_testavg.dep_avg then salinfo:=r_testavg.sal-50; end if; update emp1 set sal=salinfo where current of crs_testavg; end loop; end;
总结
以上就是本文关于oracle中游标cursor基本用法详解的全部内容,希望对大家有所帮助,欢迎参阅:、oracle sql语句优化技术要点解析、等,有什么问题可以随时留言,感谢大家!