您的位置:首页 > 数据库 > Oracle

Oracle 游标使用全解

2012-01-11 18:16 323 查看

§4.1
游标概念

为了处理 SQL 语句,ORACLE 必须分配一片叫上下文( context area )的区域来处理所必需的信息,其中包括要处理的行的数目,一个指向语句被分析以后的表示形式的指针以及查询的活动集(active set)。

游标是一个指向上下文的句柄( handle)或指针。通过游标,PL/SQL可以控制上下文区和处理语句时上下文区会发生些什么事情。

对于不同的SQL语句,游标的使用情况不同:

SQL语句

游标
非查询语句

隐式的

结果是单行的查询语句

隐式的或显示的

结果是多行的查询语句

显示的

§4.1.1
处理显式游标

1. 显式游标处理

显式游标处理需四个 PL/SQL步骤:

l 定义游标:就是定义一个游标名,以及与其相对应的SELECT 语句。

格式:

CURSOR cursor_name[(parameter[, parameter]…)] IS select_statement;

游标参数只能为输入参数,其格式为:

parameter_name [IN] datatype [{:= | DEFAULT} expression]

在指定数据类型时,不能使用长度约束。如NUMBER(4)、CHAR(10) 等都是错误的。

l 打开游标:就是执行游标所对应的SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首部,标识游标结果集合。如果游标查询语句中带有FOR UPDATE选项,OPEN 语句还将锁定数据库表中游标结果集合对应的数据行。

格式:

OPEN cursor_name[([parameter =>] value[, [parameter =>] value]…)];

在向游标传递参数时,可以使用与函数参数相同的传值方法,即位置表示法和名称表示 法。PL/SQL 程序不能用OPEN 语句重复打开一个游标。

l 提取游标数据:就是检索结果集合中的数据行,放入指定的输出变量中。

格式:

FETCH cursor_name INTO {variable_list | record_variable };

l 对该记录进行处理;

l 继续处理,直到活动集合中没有记录;

l 关闭游标:当提取和处理完游标结果集合数据后,应及时关闭游标,以释放该游标所占用的系统资源,并使该游标的工作区变成无效,不能再使用FETCH 语句取其中数据。关闭后的游标可以使用OPEN 语句重新打开。

格式:

CLOSE cursor_name;

注:定义的游标不能有INTO 子句。

例1. 查询前10名员工的信息。

DECLARE

CURSOR c_cursor IS SELECT ename, sal FROM emp WHERE rownum<11;

v_ename emp.ename%TYPE;

v_sal emp.sal%TYPE;

BEGIN

OPEN c_cursor;

FETCH c_cursor INTO v_ename, v_sal;

WHILE c_cursor %FOUND LOOP

DBMS_OUTPUT.PUT_LINE(v_ename||’---‘||to_char(v_sal) );

FETCH c_cursor INTO v_ename, v_sal;

END LOOP;

CLOSE c_cursor;

END;

例2. 游标参数的传递方法。

DECLARE

DeptRec dept%ROWTYPE;

Dept_name dept.dname%TYPE;

Dept_loc dept.loc%TYPE;

CURSOR c1 IS

SELECT dname, loc FROM dept WHERE deptno <= 30;

CURSOR c2(dept_no NUMBER DEFAULT 10) IS

SELECT dname, loc FROM dept WHERE deptno <= dept_no;

CURSOR c3(dept_no NUMBER DEFAULT 10) IS

SELECT * FROM dept WHERE deptno <=dept_no;

BEGIN

OPEN c1;

LOOP

FETCH c1 INTO dept_name, dept_loc;

EXIT WHEN c1%NOTFOUND;

DBMS_OUTPUT.PUT_LINE(dept_name||’---‘||dept_loc);

END LOOP;

CLOSE c1;

OPEN c2;

LOOP

FETCH c2 INTO dept_name, dept_loc;

EXIT WHEN c2%NOTFOUND;

DBMS_OUTPUT.PUT_LINE(dept_name||’---‘||dept_loc);

END LOOP;

CLOSE c2;

OPEN c3(dept_no =>20);

LOOP

FETCH c3 INTO deptrec;

EXIT WHEN c3%NOTFOUND;

DBMS_OUTPUT.PUT_LINE(deptrec.deptno||’---‘||deptrec.dname

||’---‘||deptrec.loc);

END LOOP;

CLOSE c3;

END;

2.游标属性

%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为TRUE;

%NOTFOUND 布尔型属性,与%FOUND相反;

%ISOPEN 布尔型属性,当游标已打开时返回 TRUE;

%ROWCOUNT 数字型属性,返回已从游标中读取的记录数。

例3:给工资低于1200 的员工增加工资50。

DECLARE

v_empno emp.empno%TYPE;

v_sal emp.sal%TYPE;

CURSOR c_cursor IS SELECT empno, sal FROM emp;

BEGIN

OPEN c_cursor;

LOOP

FETCH c_cursor INTO v_empno, v_sal;

EXIT WHEN c_cursor %NOTFOUND;

IF v_sal<=1200 THEN

UPDATE emp SET sal=sal+50 WHERE empno=v_empno;

DBMS_OUTPUT.PUT_LINE('编码为'||v_empno||'工资已更新!');

END IF;

DBMS_OUTPUT.PUT_LINE('记录数:'|| c_cursor %ROWCOUNT);

END LOOP;

CLOSE c_cursor;

END;

3. 游标的FOR循环

PL/SQL语言提供了游标FOR循环语句,自动执行游标的OPEN、FETCH、CLOSE语句和循环语句的功能;当进入循环时,游标FOR循环语句自动打开游标,并提取第一行游标数据,当程序处理完当前所提取的数据而进入下一次循环时,游标FOR循环语句自动提取下一行数据供程序处理,当提取完结果集合中的所有数据行后结束循环,并自动关闭游标。

格式:

FOR index_variable IN cursor_name[value[, value]…] LOOP

-- 游标数据处理代码

END LOOP;

其中:

index_variable为游标FOR 循环语句隐含声明的索引变量,该变量为记录变量,其结构与游标查询语句返回的结构集合的结构相同。在程序中可以通过引用该索引记录变量元素来读取所提取的游标数据,index_variable中各元素的名称与游标查询语句选择列表中所制定的列名相同。如果在游标查询语句的选择列表中存在计算列,则必须为这些计算列指定别名后才能通过游标FOR 循环语句中的索引变量来访问这些列数据。

注:不要在程序中对游标进行人工操作;不要在程序中定义用于控制FOR 循环的记录。

例4:

DECLARE

CURSOR c_sal IS SELECT empno, ename, sal FROM emp ;

BEGIN

--隐含打开游标

FOR v_sal IN c_sal LOOP

--隐含执行一个FETCH语句

DBMS_OUTPUT.PUT_LINE( to_char(v_sal.empno)||’---‘||

v_sal.ename||’---‘||to_char(v_sal.sal)) ;

--隐含监测c_sal%NOTFOUND

END LOOP;

--隐含关闭游标

END;

例5:PL/SQL还允许在游标FOR循环语句中使用子查询来实现游标的功能。

BEGIN

FOR c1_rec IN (SELECT dname, loc FROM dept) LOOP

DBMS_OUTPUT.PUT_LINE(c1_rec.dname||’---‘||c1_rec.loc);

END LOOP;

END;

§4.1.2
处理隐式游标

显式游标主要是用于对查询语句的处理,尤其是在查询结果为多条记录的情况下;而对于非查询语句,如修改、删除操作,则由ORACLE 系统自动地为这些操作设置游标并创建其工作区,这些由系统隐含创建的游标称为隐式游标,隐式游标的名字为SQL,这是由ORACLE 系统定义的。对于隐式游标的操作,如定义、打开、取值及关闭操作,都由ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、最新处理的一条SQL 语句所包含的数据。

格式调用为: SQL%

注:INSERT, UPDATE, DELETE, SELECT 语句中不必明确定义游标。

隐式游标属性

SQL%FOUND 布尔型属性,当最近一次读记录时成功返回,则值为TRUE;

SQL%NOTFOUND 布尔型属性,与%FOUND相反;

SQL %ROWCOUNT 数字型属性, 返回已从游标中读取得记录数;

SQL %ISOPEN 布尔型属性, 取值总是FALSE。SQL命令执行完毕立即关闭隐式游标。

例7: 删除EMP 表中某部门的所有员工,如果该部门中已没有员工,则在DEPT 表中删除该部门。

DECLARE

V_deptno emp.deptno%TYPE :=&p_deptno;

BEGIN

DELETE FROM emp WHERE deptno=v_deptno;

IF SQL%NOTFOUND THEN

DELETE FROM dept WHERE deptno=v_deptno;

END IF;

END;

§4.1.3
游标修改和删除操作

游标修改和删除操作是指在游标定位下,修改或删除表中指定的数据行。这时,要求游标查询语句中必须使用FOR UPDATE选项,以便在打开游标时锁定游标结果集合在表中对应数据行的所有列和部分列。

为了对正在处理(查询)的行不被另外的用户改动,ORACLE 提供一个 FOR UPDATE 子句来对所选择的行进行锁住。该需求迫使ORACLE锁定游标结果集合的行,可以防止其他事务处理更新或删除相同的行,直到您的事务处理提交或回退为止。

语法:

SELECT . . . FROM … FOR UPDATE [OF column[, column]…] [NOWAIT]

如果另一个会话已对活动集中的行加了锁,那么SELECT FOR UPDATE操作一直等待到其它的会话释放这些锁后才继续自己的操作,对于这种情况,当加上NOWAIT子句时,如果这些行真的被另一个会话锁定,则OPEN立即返回并给出:

ORA-0054 :resource busy and acquire with nowait specified.

如果使用 FOR UPDATE 声明游标,则可在DELETE和UPDATE 语句中使用WHERE CURRENT OF cursor_name子句,修改或删除游标结果集合当前行对应的数据库表中的数据行。

例12:从EMP表中查询某部门的员工情况,将其工资最低定为 1500;

DECLARE

V_deptno emp.deptno%TYPE :=&p_deptno;

CURSOR emp_cursor IS SELECT empno, sal

FROM emp WHERE deptno=v_deptno FOR UPDATE NOWAIT;

BEGIN

FOR emp_record IN emp_cursor LOOP

IF emp_record.sal < 1500 THEN

UPDATE emp SET sal=1500 WHERE CURRENT OF emp_cursor;

END IF;

END LOOP;

-- COMMIT;

END;

转载于:/article/5931572.html

这个文档几乎包含了oracle游标使用的方方面面,全部通过了测试

-- 声明游标;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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: