1、游标的概念
游标是SQL的一个内存工作区,由系统或用户以变量的形式定义。游标的作用就是用于临时存储从数据库中提取的数据块。在某些情况下,需要把数据从存放在磁盘的表中调到计算机内存中进行处理,最后将处理结果显示出来或最终写回数据库。这样数据处理的速度才会提高,否则频繁的磁盘数据交换会降低效率。
游标有两种类型:显式游标和隐式游标。在前述程序中用到的SELECT...INTO...查询语句,一次只能从数据库中提取一行数据,对于这种形式的查询和DML操作,系统都会使用一个隐式游标。但是如果要提取多行数据,就要由程序员定义一个显式游标,并通过与游标有关的语句进行处理。显式游标对应一个返回结果为多行多列的SELECT语句。
2、隐式游标
如前所述,DML操作和单行SELECT语句会使用隐式游标,它们是:
* 插入操作:INSERT。
* 更新操作:UPDATE。
* 删除操作:DELETE。
* 单行查询操作:SELECT ... INTO ...。
隐式游标由 PL/SQL自动定义、打开和关闭
当系统使用一个隐式游标时,可以通过隐式游标的属性来了解操作的状态和结果,进而控制程序的流程。隐式游标可以使用名字SQL来访问,但要注意,通过SQL游标名总是只能访问前一个DML操作或单行SELECT操作的游标属性。所以通常在刚刚执行完操作之后,立即使用SQL游标名来访问属性。游标的属性有四种,如下所示。
- 隐式游标的属性 返回值类型 意 义
- SQL%ROWCOUNT 整型 代表DML语句成功执行的数据行数
- SQL%FOUND 布尔型 值为TRUE代表插入、删除、更新或单行查询操作成功
- SQL%NOTFOUND 布尔型 与SQL%FOUND属性返回值相反
- SQL%ISOPEN 布尔型 DML执行过程中为真,结束后为假
--隐示游标
declare
flag char:='U';
begin
update dept
set dname='研发部'
where deptno=50;
if SQL%NOTFOUND then
flag:='Z';
insert into dept(deptno, dname) values(50,'研发部');
end if;
if flag='U' then
dbms_output.put_line('记录已更新');
else
dbms_output.put_line('记录已插入');
end if;
end;
/
3、显示游标
显示游标需要声明、打开、提取、关闭。
显式游标的属性如下所示。
游标的属性 返回值类型 意 义
%ROWCOUNT 整型 获得FETCH语句返回的数据行数
%FOUND 布尔型 最近的FETCH语句返回一行数据则为真,否则为假
%NOTFOUND 布尔型 与%FOUND属性返回值相反
%ISOPEN 布尔型 游标已经打开时值为真,否则为假
set serveroutput on
--显示游标fetch...into
declare
cursor emp_cursor is select emp.ename,emp.sal from emp;
v_ename emp.ename%type;
v_sal emp.sal%type;
begin
open emp_cursor;
loop fetch emp_cursor into v_ename,v_sal;
exit when emp_cursor%notfound;
dbms_output.put_line(v_ename||':'||v_sal);
end loop;
close emp_cursor;
end;
/
--显示游标fetch...bulk collect into
declare
cursor emp_cursor is select emp.ename from emp;
type ename_table_type is table of varchar2(10);
ename_table ename_table_type;
begin
open emp_cursor;
fetch emp_cursor bulk collect into ename_table;
for i in 1..ename_table.count loop
dbms_output.put_line(ename_table(i));
end loop;
close emp_cursor;
end;
/
--游标使用记录变量,使用参数
declare
cursor emp_cursor(no number) is select * from emp where emp.deptno=no;
emp_record emp_cursor%rowtype;
begin
open emp_cursor(&no);
loop fetch emp_cursor into emp_record;
exit when emp_cursor%notfound;
dbms_output.put_line(emp_record.ename||'='||emp_record.sal);
end loop;
close emp_cursor;
end;
/
--使用游标删除数据
declare
cursor emp_cursor is select * from bonus where rownum<=10 for update nowait;
emp_record emp_cursor%rowtype;
begin
open emp_cursor;
loop fetch emp_cursor into emp_record;
exit when emp_cursor%notfound;
delete from bonus where current of emp_cursor;
end loop;
close emp_cursor;
commit;
end;
/
--游标使用for循环
declare
cursor emp_cursor is select * from emp;
begin
for emp_record in emp_cursor loop
dbms_output.put_line(emp_cursor%rowcount||':'||emp_record.ename);
end loop;
end;
/
4、动态游标
--动态游标(REF游标)
declare
type emp_cursor_type is ref cursor;
emp_cursor emp_cursor_type;
emp_record emp%rowtype;
begin
open emp_cursor for select * from emp;
loop fetch emp_cursor into emp_record;
exit when emp_cursor%notfound;
dbms_output.put_line(emp_record.ename||'='||emp_record.sal);
end loop;
close emp_cursor;
end;
/
--动态游标(REF游标)
declare
type emp_type is ref cursor;
cur emp_type;
name varchar2(20);
salary number(7,2);
begin
open cur for 'select ename,sal from emp where job=:1'
using 'SALESMAN';
loop
fetch cur into name,salary;
exit when cur%notfound;
dbms_output.put_line(name||':'||salary);
end loop;
close cur;
end;
/
5、总结
Cursor与 Ref Cursor区别从技术底层看,两者是相同的。普通plsql cursor在定义时是“静态”的。而Ref cursors可以动态打开。
Ref cursor根据逻辑动态打开;而游标cursor定义好了就无法修改了
ref cursor可以返回给客户端,cursor则不行。
cursor可以是全局的global ,ref cursor则必须定义在过程或函数中。
ref cursor可以在子程序间传递,cursor则不行。
cursor中定义的静态sql比ref cursor效率高,所以ref cursor通常用在:向客户端返回结果集。
6、游标示例
显示游标
15-1:使用标量变量接受游标数据
DECLARE
CURSOR emp_cursor IS
SELECT ename,job,sal FROM emp WHERE deptno=&dno;
v_ename emp.ename%TYPE;
v_sal emp.sal%TYPE;
v_job emp.job%TYPE;
BEGIN
OPEN emp_cursor;
LOOP
FETCH emp_cursor INTO v_ename,v_job,v_sal;
EXIT WHEN emp_cursor%NOTFOUND;
dbms_output.put_line('姓名:'||v_ename||',岗位:'||v_job||',工资:'||v_sal);
END LOOP;
CLOSE emp_cursor;
END;
/
15-2:使用PL/SQL记录接受游标数据
DECLARE
CURSOR emp_cursor IS
SELECT ename,sal FROM emp ORDER BY sal DESC;
emp_record emp_cursor%ROWTYPE;
BEGIN
OPEN emp_cursor;
LOOP
FETCH emp_cursor INTO emp_record;
EXIT WHEN emp_cursor%NOTFOUND OR emp_cursor%ROWCOUNT>&n;
dbms_output.put_line('姓名:'||emp_record.ename||
',工资:'||emp_record.sal);
END LOOP;
CLOSE emp_cursor;
END;
/
15-3:使用PL/SQL集合变量接受游标数据
DECLARE
CURSOR emp_cursor IS SELECT ename,sal FROM emp
WHERE lower(job)=lower('&job');
TYPE emp_table_type IS TABLE OF emp_cursor%ROWTYPE
INDEX BY BINARY_INTEGER;
emp_table emp_table_type;
i INT;
BEGIN
OPEN emp_cursor;
LOOP
i:=emp_cursor%ROWCOUNT+1;
FETCH emp_cursor INTO emp_table(i);
EXIT WHEN emp_cursor%NOTFOUND;
dbms_output.put_line('姓名:'||emp_table(i).ename||
',工资:'||emp_table(i).sal);
END LOOP;
CLOSE emp_cursor;
END;
/
15-4:在FOR循环中引用已定义游标
DECLARE
CURSOR emp_cursor IS SELECT ename,hiredate FROM emp
ORDER BY hiredate DESC;
BEGIN
FOR emp_record IN emp_cursor LOOP
dbms_output.put_line('姓名:'||emp_record.ename
||',工作日期:'||emp_record.hiredate);
EXIT WHEN emp_cursor%ROWCOUNT=&n;
END LOOP;
END;
/
15-5:在FOR循环中直接引用子查询
BEGIN
FOR emp_record IN (SELECT ename,hiredate,rownum FROM emp
ORDER BY hiredate) LOOP
dbms_output.put_line('姓名:'||emp_record.ename
||',工作日期:'||emp_record.hiredate);
EXIT WHEN emp_record.rownum=&n;
END LOOP;
END;
/
15-6:参数游标
DECLARE
CURSOR emp_cursor(dno NUMBER) IS
SELECT ename,job FROM emp WHERE deptno=dno;
BEGIN
FOR emp_record IN emp_cursor(&dno) LOOP
dbms_output.put_line('姓名:'||emp_record.ename
||',岗位:'||emp_record.job);
END LOOP;
END;
/
15-7:更新游标行
DECLARE
CURSOR emp_cursor IS
SELECT ename,sal,deptno FROM emp FOR UPDATE;
dno INT:=&no;
BEGIN
FOR emp_record IN emp_cursor LOOP
IF emp_record.deptno=dno THEN
dbms_output.put_line('姓名:'||emp_record.ename
||',原工资:'||emp_record.sal);
UPDATE emp SET sal=sal*1.1 WHERE CURRENT OF emp_cursor;
END IF;
END LOOP;
END;
/
15-8:删除游标行
DECLARE
CURSOR emp_cursor IS
SELECT ename FROM emp FOR UPDATE;
name VARCHAR2(10):=lower('&name');
BEGIN
FOR emp_record IN emp_cursor LOOP
IF lower(emp_record.ename)=name THEN
DELETE FROM emp WHERE CURRENT OF emp_cursor;
ELSE
dbms_output.put_line('姓名:'||emp_record.ename);
END IF;
END LOOP;
END;
/
15-9:OF子句在特定表上加共享锁(只在EMP表上加锁)
DECLARE
CURSOR emp_cursor IS
SELECT a.dname,b.ename FROM dept a JOIN emp b
ON a.deptno=b.deptno
FOR UPDATE OF b.deptno;
name VARCHAR2(10):=LOWER('&name');
BEGIN
FOR emp_record IN emp_cursor LOOP
IF LOWER(emp_record.dname)=name THEN
dbms_output.put_line('姓名:'||emp_record.ename);
DELETE FROM emp WHERE CURRENT OF emp_cursor;
END IF;
END LOOP;
END;
/
动态游标
15-10:使用无返回类型的游标变量
DECLARE
TYPE ref_cursor_type IS REF CURSOR;
ref_cursor ref_cursor_type;
v1 NUMBER(6);
v2 VARCHAR2(10);
BEGIN
OPEN ref_cursor FOR
SELECT &col1 col1,&col2 col2 FROM &table WHERE &cond;
LOOP
FETCH ref_cursor INTO v1,v2;
EXIT WHEN ref_cursor%NOTFOUND;
dbms_output.put_line('col1='||v1||',col2='||v2);
END LOOP;
CLOSE ref_cursor;
END;
/
15-11:使用有返回类型的游标变量
DECLARE
TYPE emp_cursor_type IS REF CURSOR RETURN emp%ROWTYPE;
emp_cursor emp_cursor_type;
emp_record emp%ROWTYPE;
BEGIN
OPEN emp_cursor FOR SELECT * FROM emp
WHERE deptno=&dno;
LOOP
FETCH emp_cursor INTO emp_record;
EXIT WHEN emp_cursor%NOTFOUND;
dbms_output.put_line('姓名:'||emp_record.ename||
',工资:'||emp_record.sal);
END LOOP;
CLOSE emp_cursor;
END;
/
批量提取
15-12:使用FETCH ... BULK COLLECT 提取所有数据
DECLARE
CURSOR emp_cursor IS
SELECT * FROM emp WHERE LOWER(job)=LOWER('&job');
TYPE emp_table_type IS TABLE OF emp%ROWTYPE;
emp_table emp_table_type;
BEGIN
OPEN emp_cursor;
FETCH emp_cursor BULK COLLECT INTO emp_table;
CLOSE emp_cursor;
FOR i IN 1.. emp_table.COUNT LOOP
dbms_output.put_line('姓名:'||emp_table(i).ename
||',工资:'||emp_table(i).sal);
END LOOP;
END;
/
15-13:使用LIMIT子句限制提取行数
DECLARE
CURSOR emp_cursor IS SELECT * FROM emp;
TYPE emp_array_type IS VARRAY(5) OF emp%ROWTYPE;
emp_array emp_array_type;
BEGIN
OPEN emp_cursor;
LOOP
FETCH emp_cursor BULK COLLECT INTO emp_array LIMIT &rows;
FOR i IN 1..emp_array.COUNT LOOP
dbms_output.put_line('姓名:'||emp_array(i).ename
||',工资:'||emp_array(i).sal);
END LOOP;
EXIT WHEN emp_cursor%NOTFOUND;
END LOOP;
CLOSE emp_cursor;
END;
/
嵌套游标
15-14:
DECLARE
CURSOR dept_cursor(no NUMBER) IS
SELECT a.dname,CURSOR(SELECT * FROM emp
WHERE deptno=a.deptno)
FROM dept a WHERE a.deptno=no;
TYPE ref_cursor_type IS REF CURSOR;
emp_cursor ref_cursor_type;
emp_record emp%ROWTYPE;
v_dname dept.dname%TYPE;
BEGIN
OPEN dept_cursor(&dno);
LOOP
FETCH dept_cursor INTO v_dname,emp_cursor;
EXIT WHEN dept_cursor%NOTFOUND;
dbms_output.put_line('部门名:'||v_dname);
LOOP
FETCH emp_cursor INTO emp_record;
EXIT WHEN emp_cursor%NOTFOUND;
dbms_output.put_line('----雇员名:'||emp_record.ename
||',岗位:'||emp_record.job);
END LOOP;
END LOOP;
CLOSE dept_cursor;
END;
/