在 PL/SQL 块执行 SELECT/INSERT/UPDATE/DELETE 语句时,Oracle 会在内存中为其分配上下文区,而游标是指向该区域的指针。
游标为应用程序提供了一种对具有多行数据查询结果集中的每一行单独处理的方案,是设计交互式应用程序的编程接口。
在每个用户会话中,可以同时打开多个游标,其数量由数据库初始化参数文件中的OPEN_CURSORS参数定义。
对于不同的 SQL 语句,游标的使用情况不同:
SQL语句 | 游标 |
---|---|
非查询语句 | 隐式的 |
结果是单行的查询语句 | 隐式的或显示的 |
结果是多行的查询语句 | 显示的 |
显示游标的使用步骤:定义游标,打开游标,使用游标,关闭游标
--无参数无返回值游标 例子
DECLARE
CURSOR c_emp IS SELECT empno, ename FROM emp;
v_empno emp.empno%TYPE;
v_ename emp.ename%TYPE;
BEGIN
IF not c_emp %ISOPEN THEN
OPEN c_emp;
END IF;
--抓取游标中的第一条记录
FETCH c_emp into v_empno, v_ename;
--对游标进行循环操作: 判断游标中是否有下一条记录
WHILE c_emp %FOUND LOOP
DBMS_OUTPUT.put_line(v_empno || '-' || v_ename);
FETCH c_emp into v_empno, v_ename;
END LOOP;
--游标使用完毕后关闭游标
CLOSE c_emp;
END;
--有参数无返回值游标 例子
DECLARE
CURSOR c4(dept_id NUMBER, j_id VARCHAR2) --1、声明游标,有参数没有返回值
IS
SELECT first_name f_name, hire_date FROM employees
WHERE department_id = dept_id AND job_id = j_id;
--基于游标定义记录变量,比声明记录类型变量要方便,不容易出错
v_emp_record c4%ROWTYPE;
BEGIN
OPEN c4(90, 'AD_VP'); --2、打开游标,传递参数值
LOOP
FETCH c4 INTO v_emp_record; --3、提取游标fetch into
IF c4%FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name||'的雇佣日期是'
||v_emp_record.hire_date);
ELSE
DBMS_OUTPUT.PUT_LINE('已经处理完结果集了');
EXIT;
END IF;
END LOOP;
CLOSE c4; --4、关闭游标
END;
用户可以通过检查游标属性来确定游标的当前状态:
%FOUND:FETCH语句成功返回时,则%FOUND的值为TRUE。
%NOTFOUND:FETCH语句未能提取行时,则%NOTFOUND的值为TRUE。
%ISOPEN:如果游标已经打开,则返回TRUE,否则返回FALSE。
%ROWCOUNT:返回目前为止游标提取的行数,%ROWCOUNT为数值型属性。在第一次获取之前,%ROWCOUNT为0。当FETCH语句返回一行时,则该数加1。
DECLARE
CURSOR c_emp IS SELECT empno,ename FROM emp;
v_empno emp.empno%TYPE;
v_ename emp.ename%TYPE;
BEGIN
IF not c_emp %isopen THEN
OPEN c_emp;
END IF;
--对游标进行循环操作: 判断游标中是否有下一条记录
LOOP
FETCH c_emp INTO v_empno, v_ename;
EXIT WHEN c_emp %notfound;
DBMS_OUTPUT.put_line(v_empno || '-' || v_ename);
END LOOP;
/* 或者
FETCH c_emp into v_empno, v_ename;
WHILE c_emp %FOUND LOOP
DBMS_OUTPUT.put_line(v_empno || '-' || v_ename);
FETCH c_emp into v_empno, v_ename;
END LOOP; */
--游标使用完毕后关闭游标
CLOSE c_emp;
END;
为了简化游标的应用,Oracle提供了游标的for循环语句,可以隐式地 OPEN,FETCH,CLOSE 游标以及循环处理结果集。
DECLARE
CURSOR c_emp IS SELECT empno, ename FROM emp;
BEGIN
--隐式地 OPEN,FETCH,CLOSE 游标以及循环处理结果集
FOR emp IN c_emp LOOP
DBMS_OUTPUT.put_line(emp.empno || '-' || emp.ename);
END LOOP;
END;
或者可以在游标 FOR 循环语句中使用子查询
BEGIN
FOR c1_rec IN (SELECT department_name, location_id FROM departments) LOOP
DBMS_OUTPUT.PUT_LINE(c1_rec.department_name||'---'||c1_rec.location_id);
END LOOP;
END;
1、使用普通变量接收游标数据
declare
v_cnt number :=20;
cursor emp_cursor is select ename,job,sal from emp where deptno=v_cnt;
vname emp.ename%type;
vsal emp.sal%type;
vjob emp.job%type;
begin
open emp_cursor;
loop
fetch emp_cursor into vname, vjob, vsal; --使用普通变量接收游标数据
exit when emp_cursor%notfound;
dbms_output.put_line('姓名' || vname || '岗顶' || vjob || '工资' || vsal);
end loop;
close emp_cursor;
end;
2、使用PL/SQL记录变量接收游标数据:简化单行数据处理
declare
v_cnt number :=5;
cursor ecur is select ename,sal from emp order by sal desc; --游标
erec ecur%rowtype; --定义声明一个行级的PL/SQL记录变量erec
begin
open ecur;
loop
fetch ecur into erec; --使用PL/SQL记录变量接收游标数据
exit when ecur%notfound or ecur%rowcount > v_cnt;
dbms_output.put_line('姓名' || erec.ename || '工资' || erec.sal);
end loop;
close ecur;
end;
3、使用PL/SQL集合变量接收游标数据,简化多行多列数据处理
declare
v_cnt number :=5;
cursor ec is select ename,sal from emp where lower(job)=lower(v_cnt); --游标
type eetype is table of ec%rowtype index by binary_intefer; --定义一个复合数据类型PL/SQL表变量eetype
et eetype; --声明etype
i int;
begin
open ec;
loop
i:= ec%rowcount + 1;
fetch ec into et(i); --使用PL/SQL集合变量接收游标数据
exit when ec%notfound;
dbms_output.put_line('姓名' || et(i).ename || '工资' || et(i).sal);
end loop;
close ec;
end;
说明:必须要带for update子句。
语法:cursor cursor_name(parameter_name datatype) is select_statement for update [of column_reference] [nowait]
其中,for update用于在游标结果集加共享锁。
当select语句引用多张表示,使用of子句可以确定哪些表要加锁,没有则select语句所引用的全部表加上锁。
nowait用于指定不等待锁,当其他会话已经锁表之后,默认是当前会话一直等待释放,但指定了NOWAIT后,
如果表已经被其它会话加锁,则抛出异常并退出当前块。
注意,为了更新或删除当前游标行数据,必须在update或delete语句中引用where current of子句。
例子:
cursor c_name is select emp.sal,dept.deptno from emp,dept where emp.deptno=dept.deptno for update;–全部加共享锁
cursor c_name is select emp.sal,dept.deptno from emp,dept where emp.deptno=dept.deptno for update of emp.deptno;–只在emp表加共享锁
cursor c_name is select sal from emp for update nowait;
declare
cursor emp_cursor is select ename,sal,deptno from emp for update;–默认所有表均加上共享锁。
dno int:=1001;
begin
for emp_record in emp_cursor loop
if emp_record.deptno=dno then
update emp set sal=sal*1.1 where current of emp_cursor; --更新操作
delete from emp where current of emp_cursor; --删除操作
end if;
end loop;
end;
1、通过游标更新、删除表的数据对于多表的情况(使用for子句在特定表上加共享锁)
declare
cursor emp_cursor is select a.dname,b.ename from dept a join emp b on a.deprno=b.deptno;
name varchar2(20):='sales';
begin
for emp_record in emp_cursor loop
if emp_record.dname=name then
delete from emp where current of emp_cursor;
end if;
end loop;
end;
2、使用fetch…bulk collect批量提取所有数据
declare
v_name varchar2(20):='clerk';
cursor ec is select * from emp where job=v_name;
type etype is table of emp%rowtype; --定义一个复合数据类型嵌套表etype(就看成定义了一个集合)
et etype;
begin
open ec;
fetch ec bulk collect into et; --把游标里的数据都放入到集合中
close ec;
for i in 1..et.count loop
dbms_output.put_line('姓名' || et(i).ename || '工资' || et(i).sal);
end loop;
end;
3、使用fetch…bulk collect批量提取 + limit子句限制提取行数的应用
declare
v_cnt number:=4;
cursor ec is select * from emp;
type emp_array_type is varray(5) of emp%rowtype; --定义一个复合数据类型varray数组(就看成定义了一个集合)
ea emp_array_type;
begin
open ec;
loop
fetch ec bulk collect into ea limit v_cnt;
for i in 1..ea.count loop
dbms_output.put_line('姓名' || et(i).ename || '工资' || et(i).sal); --结果只会输出4行
end loop;
exit when ec%notfound;
end loop;
close ec;
end;
4、游标中保存着游标的应用
declare
v_dno number:=5010;
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;
ec ref_cursor_type;
er emp%rowtype;
vdname dept.dname%type;
begin
open dept_cursor(v_dno);
loop
fetch dept_cursor into vdname,ec;
exit when dept_cursor%notfound;
dbms_output.put_line('部门' || vdname);
loop
fetch ec into er;
exit when ec%notfound;
dbms_output.put_line('雇员' || er.ename || '岗位' || er.job); --一个部门有多名员工
end loop;
end loop;
close dept_cursor;
end;
显式游标主要是用于对查询语句的处理,尤其是在查询结果为多条记录的情况下;
而对于非查询语句,如修改、删除操作,则由ORACLE 系统自动地为这些操作设置游标并创建其工作区,隐式游标的名字为SQL,这是由ORACLE 系统定义的。
对于隐式游标的操作,如定义、打开、取值及关闭操作,都由ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、最新处理的一条SQL 语句所包含的数据。
如前所述,DML操作和单行SELECT语句会使用隐式游标,它们是:
插入操作:INSERT。
更新操作:UPDATE。
删除操作:DELETE。
单行查询操作:SELECT … INTO …。
DECLARE
v_rows NUMBER;
BEGIN
--更新数据
UPDATE employees SET salary = 30000
WHERE department_id = 90 AND job_id = 'AD_VP';
--获取默认游标的属性值
v_rows := SQL%ROWCOUNT;
DBMS_OUTPUT.PUT_LINE('更新了'||v_rows||'个雇员的工资');
--删除指定雇员;如果部门中没有雇员,则删除部门
DELETE FROM employees WHERE department_id = v_deptno;
IF SQL%NOTFOUND THEN
DELETE FROM departments WHERE department_id=v_deptno;
END IF;
END;
当系统使用一个隐式游标时,也可以通过隐式游标的属性来了解操作的状态和结果,进而控制程序的流程。
SQL%ROWCOUNT:代表DML语句成功执行的数据行数
SQL%FOUND:值为TRUE代表插入、删除、更新或单行查询操作成功
SQL%NOTFOUND:与SQL%FOUND属性返回值相反
SQL%ISOPEN:DML执行过程中为真,结束后为假
提示一下:因为隐式游标默认打开游标,但是如果调用%ISOPEN 却返回FALSE
在变量声明部分定义的游标是静态的,不能在程序运行过程中修改。虽然可以通过参数传递来取得不同的数据,但还是有很大的局限性。通过采用动态游标,可以在程序运行阶段随时生成一个查询语句作为游标。要使用动态游标需要先定义一个游标类型,然后声明一个游标变量,游标对应的查询语句可以在程序的执行过程中动态地说明。
动态游标用于处理运行时动态执行的SQL查询的结果集。
定义游标类型的语句如下:TYPE 游标类型名 IS REF CURSOR [return 返回值类型]
声明游标变量的语句如下:游标变量名 游标类型名;
在可执行部分可以如下形式打开一个动态游标:OPEN 游标变量名 FOR 查询语句字符串;
DECLARE
TYPE cur_type IS REF CURSOR;
cur cur_type;
rec scott.emp%ROWTYPE;
str VARCHAR2(50);
letter CAHR := 'A';
BEGIN
LOOP
str := 'SELECT ename FROM emp WHERE ename LIKE ''%'||letter||'%''';
OPEN cur FOR str;
DBMS_OUTPUT.put_line('包含字母'||letter||'的名字:');
LOOP
FETCH cur INTO rec.ename;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.put_line(rec.ename);
END LOOP;
EXIT WHEN letter = 'Z';
Letter := CHR(ASCII(letter) + 1);
END LOOP;
END;
DECLARE
--强类型的游标类型(有返回值)
TYPE strong_cursor_type IS REF CURSOR RETURN emp%ROWTYPE;
--弱类型的游标类型(无返回值)
TYPE weak_cursor_type IS REF CURSOR;
--变量定义
strong_cursor strong_cursor_type;
weak_cursor weak_cursor_type;
v_emp emp%ROWTYPE;
BEGIN
OPEN strong_cursor FOR SELECT * FROM emp;
LOOP
FETCH strong_cursor INTO v_emp;
EXIT WHEN strong_cursor%notfound;
DBMS_OUTPUT.put_line(v_emp.empno || '-' || v_emp.ename);
END LOOP;
CLOSE strong_cursor;
OPEN weak_cursor FOR '&sql';
LOOP
FETCH weak_cursor INTO v_emp;
EXIT WHEN weak_cursor%notfound;
DBMS_OUTPUT.put_line(v_emp.empno || '-' || v_emp.ename);
END LOOP;
CLOSE weak_cursor;
END;
游标查询语句中尽量使用FOR UPDATE选项,以便在打开游标时锁定游标结果集合在表中对应数据行的所有列和部分列。
如果另一个会话已对活动集中的行加了锁,那么SELECT FOR UPDATE操作一直等待到其它的会话释放这些锁后才继续自己的操作;对于这种情况,当加上NOWAIT子句时,如果这些行真的被另一个会话锁定,则OPEN立即返回并给出: ORA-0054 :resource busy and acquire with nowait
DECLARE
V_deptno employees.department_id%TYPE := &p_deptno;
CURSOR emp_cursor
IS
SELECT employees.employee_id, employees.salary
FROM employees WHERE employees.department_id=v_deptno
FOR UPDATE NOWAIT; --1、for update
BEGIN
FOR emp_record IN emp_cursor LOOP
IF emp_record.salary < 1500 THEN
UPDATE employees SET salary=1500
WHERE CURRENT OF emp_cursor; --2、WHERE CURRENT OF cursor_name子句
END IF;
END LOOP;
END