此书及其相关资源下载方法:关注微信公众号,点击功能介绍-书籍链接下载,即可获取
说明
ORACLE PL/SQL从入门到精通的事务处理和锁定,异常处理机制将会在<ORACLE官方文档>的笔记中具体详细介绍,至此ORACLE PL/SQL的入门和基础已经进入完结
本文主要介绍如下:
- 12.1游标基本结构
12.1.1 游标简介
12.1.2 游标分类
12.1.3 定义游标类型
12.1.4 打开游标
12.1.5 使用游标属性
12.1.6 提取游标数据
12.1.7 批量提取游标数据
12.1.8 关闭游标- 12.2操纵游标数据
12.2.1 LOOP循环
12.2.2 WHILE循环
12.2.3游标FOR循环
12.2.4 修改游标数据- 12.3游标变量
12.3.1 游标变量简介
12.3.2 声明游标变量类型
12.3.3 定义游标变量
12.3.4 打开游标变量
12.3.5 控制游标变量
12.3.6 处理游标变量异常
12.3.7 在包中使用游标变量
12.3.8 游标变量的限制
-----------------------------------12.1 游标基本结构-----------------------------------
12.1.1 游标简介
定义
游标实际上指向的是一块内存区域,这块内存区域位于进程全局区内部,称为上下文
区域 (ContextAIea) ,在上下文区域中保存了如下3类信息。
- 查询返回的数据行。
- 查询所处理的数据的行号。
- 指向共享池中的己分析的 SQL语句。
代码部分
DECLARE
emprow emp%ROWTYPE; --定义保存游标检索结果行的记录变量
CURSOR emp_cur --定义游标
IS
SELECT *
FROM emp
WHERE deptno IS NOT NULL;
BEGIN
OPEN emp_cur; --打开游标
LOOP --循环检索游标
FETCH emp_cur --提取游标内容
INTO emprow;
--输出检索到的游标行的信息
DBMS_OUTPUT.put_line ( '员工编号:'
|| emprow.empno
|| ' '
|| '员工名称:'
|| emprow.ename
);
EXIT WHEN emp_cur%NOTFOUND; --当游标数据检索完成退出循环
END LOOP;
CLOSE emp_cur; --关闭游标
END;
执行过程如图所示
执行过程文字说明
- 上述语句的执行过程如下所示:
(l) 在语句的声明块中, 声明了一个用来保存游标检索结果的记录类型,指向 emp表
的行记录.
(2) 在声明块中, 使用 CURSOR 关键字定义了一个游标, 在IS 子句后面指定了查询
语句查询 emp表中的数据.
(3) 在执行块中, 首先使用 OPEN 语旬打开emp_cur游标, 此时查询数据将保存到内
存中' 游标就指向该内存区域.
(4)通过 FETCH语句提取游标中的数据到游标行中.
(5) 输出游标数据到屏幕上.
(6) 使用游标变量%NOTFOUND判断记录是否己检索完.
(7) 循环结束, 必须关闭游标.- 通过上面的示例y 可以发现一个显式的游标由如下的几个过程实现:
(1) 声明游标并为游标关联SELECT语句.
(2)执行 SELECT语句打开游标。
(3)将游标的结果放到PL/SQL变量中.
(4) 关闭游标。
12.1.2 游标分类
- 显式游标:使用 CURSOR语句显式定义的游标, 游标被定义之后, 需要打开并提
取游标。- 隐式游标: 由0racle 为每一个不属于显式游标的 SQL DML 语句都创建一个隐式
的游标, 由于隐式游标没有名称, 困此也可以叫做SQL游标。
12.1.3 定义游标类型
指定的SELECT语句。 显式游标的定义语法如下所示。
CURSOR cursor_name [parameter_list]
[RETURN return_type]
IS query
[FOR UPDATE [OF (column_11st)] [NOWAIT] ]:
语法说明
声明语句中关键部分的含义如下。
- cursor_naIne:用于指定一个有效的游标名称, 这个名称遵循 PL/SQL 的标识符命
名规范。- parameter_list: 用于指定一个或多个可选的游标参数, 这些参数将用于查询执行。
- RETURN return_type:可选的 RETURN 子句指定游标将要返回的由 return_type 指
定的数据类型, return_type必须是记录或数据表的行类型。- query: 可以是任何 SELECT语句。
- FOR UPDATE: 指定该子句将在游标打开期间锁定游标记录, 这些记录对其他用
户来说为只读模式。
12.1.4 打开游标
打开游标的语法如下所示。
OPEN cursor_name [ (parameter_values
语法说明
curs0r_name 用于指定游标名称,parameter_values 用于指定游标的参数列表。 在游标
被打开时, 将会发生如下所示的几个动作。
- 检验绑定变量的值。
- 基于查询的语句确定游标的活动集
- 游标指针指向游标活动集的第l行。
打开游标执行流程
12.1.5 使用游标属性
不论显示还是隐式游标都含有四个属性:%ISOPEN,%FOUND,%NOTFOUND和%ROWCOUNT
%ISOPEN属性
判断对应的游标是否被打开,如果打开:TRUE,否则FALSE
%FOUND属性
在调用FETCH语句获取数据之前,为NULL值,而此后会每取得一行数据返回为TRUE,直到最后一个值被取完后找不到数据就会为FALSE
%NOTFOUND属性
和%FOUND数据完全相反,在调用FETCH语句获取数据之前,为NULL值,而此后会每取得一行数据返回为FALSE,直到最后一个值被取完后找不到数据就会为TRUE
ROWCOUNT属性
游标被打开时,值为0,每获取一条数据,则加1
注意:一定要在被使用的情况下,才会加1,而不是计算查询结果总数据量
12.1.6 提取游标数据
在使用游标时′,只有将游标数据提取出来, 才 能进行进一步的使用
提取游标数据使用 FETCH 语句,该语句可以一次一行地提取游标数据, 也可以使用 BULK COLLECT子句一次性接收所有的游标数据到一个数组或一个PL/SQL表中.
FETCH培句的使用语法如下所示。
FETCH cursor_name INTO variable_name (s) | PL/SQL_record
语法说明
- cursor_name 用于指定要打开的游标名称,
- variable_name(s)用来指定一个或多个以逗号分隔的变量,变量的类型匹配在游标定义中使用 SELECT语句查询的字段的顺序。
- PL/SQL_record 用来指定]个记录类型来一次性接收所有的列数据。
12.1.7 批量提取游标数据
使用BULK COLLECT子句一次性接收所有的游标数据到一个数组或一个PL/SQL表中,一次性获取数据
12.1.8 关闭游标
语法
CLOSE cursor_name;
注意:关闭一个未打开的游标会报异常
游标的限制
使用system登入oracle,dba权限下使用如下语句
show parameter open_cursors;
执行结果
如果需要修改这个限制
alter system set open_cursors=max_open_cursors;--max_open_cursors为number类型,定义最大的游标打开次数
12.1 游标基本结构涉及到的知识点代码如下:
DECLARE
--声明并定义一个标识符为cur_emp的游标
--注意:cur_emp不是PL/SQL的变量名,不能赋值或者表达式中使用,但是作用域规则和变量一样
--如果定义含参数的游标,那么查询的类型也必须一致
CURSOR cur_emp_all IS
SELECT s.empno,
s.ename,
s.job,
s.mgr,
s.hiredate,
s.sal,
s.comm,
s.deptno
FROM hyz_emp s
WHERE s.deptno = 20;
CURSOR cur_emp(v_deptno IN NUMBER) IS
SELECT s.empno, s.ename FROM hyz_emp s WHERE s.deptno = v_deptno;
v_empno hyz_emp.empno%TYPE;
v_ename hyz_emp.ename%TYPE;
TYPE emp_type IS TABLE OF hyz_emp%ROWTYPE; --定义一个emp表中的行类型的嵌套表类型
emp_tab emp_type; --定义一个嵌套遍历
BEGIN
OPEN cur_emp(20); --打开含有参数的参数游标,需要带参数
--显示游标ISOPEN属性
IF cur_emp%ISOPEN THEN
dbms_output.put_line('游标已open');
--使用LOOP循环,游标打开的情况下才能遍历
dbms_output.put_line('使用FETCH获取数据后LOOP遍历');
LOOP
FETCH cur_emp
INTO v_empno, v_ename;
--显示游标NOTFUDN属性
EXIT WHEN cur_emp%NOTFOUND;
--显示游标的FOUND属性
IF cur_emp%FOUND AND cur_emp%ROWCOUNT < 5 THEN
--显示游标的ROWCOUNT属性,记录能查到的数据条数,而非总条数
dbms_output.put_line(v_empno || '号员工姓名是' || v_ename || ',已查询到的数据:' ||
cur_emp%ROWCOUNT);
ELSE
--当已经退出%ROWCOUNT不会加1,而当cur_emp%ROWCOUNT>=5时则仍会加1
dbms_output.put_line('程序已经退出' || cur_emp%ROWCOUNT);
END IF;
END LOOP;
ELSE
dbms_output.put_line('游标已close');
END IF;
CLOSE cur_emp; --关闭时无需参数
OPEN cur_emp_all; --打开游标
--使用FETCH...BULK COLLECT子句一次性遍历游标
dbms_output.put_line('使用FETCH...BULK COLLECT获取数据后FOR...LOOP遍历');
FETCH cur_emp_all BULK COLLECT
INTO emp_tab;
CLOSE cur_emp_all; --关闭游标
--即使游标关闭,也可正常使用depttab嵌套变量
FOR i IN 1 .. emp_tab.count --遍历嵌套变量的数据
LOOP
dbms_output.put_line(emp_tab(i).empno || '号员工姓名是' || emp_tab(i).ename);
END LOOP;
--比如要把濮军 的姓名改成黄濮军
UPDATE hyz_emp s SET s.ename = '黄濮军' WHERE s.ename = '濮军';
IF SQL%FOUND THEN
--隐式游标的也有%FOUND,%NOTFUND,%ISOPEN和%RWOCOUNT属性
COMMIT;
dbms_output.put_line('成功修改员工姓名原为:濮军,现为:黄濮军');
ELSE
ROLLBACK;
dbms_output.put_line('不存在此员工或您已修改过');
END IF;
--显示游标ISOEN属性
IF cur_emp%ISOPEN THEN
dbms_output.put_line('游标已open');
ELSE
dbms_output.put_line('游标已close');
END IF;
END;
--------------------------------12.2 操纵游标数据--------------------------------
12.2.1 LOOP循环
见12.2代码部分
12.2.2 WHILE循环
见12.2代码部分
12.2.3游标FOR循环
见12.2代码部分
12.2.4 修改游标数据
FOR UPDATE子句
FOR UPDATE 子句会对用 SELECT 语句提取出来的结果进行锁定, 相当下给结果集
的行加了一把互斥锁, 实行行级锁定。 这样其他的用户或会话就不能对当前游标行进行修
改或删除, 直到整个事务被提交为止.
声明
[FOR UPDATE [OF (column_list) ][NOWAIT] ] ;
声明说明
在声明中, 可选的 OF(colunm_list)指定要锁定的表的列, 可以同时指定多个表列进行
锁定: NOWAIT指定当有别的会话锁定相同的行时, SELECT FOR UPDATE语句将不进
行等待而直接返回.
WHERE CURRENT OF子句
使用FOR UPDATE锁定表中的行后,可以在UPDATE或DELETE语句中使用WHERE CURRENT OF子句来得到当前游标所检索出来的行
语法
WHERE CURRENT OF cursorname;
语法说明
cursorname为当前使用FOR UPDATE子句的游标的名称,用来更新游标数据.
注意:使用WHERE CURRENT OF子句检索的游标一定要有FOR UPDATE子句,并且游标要被打开且至少返回一行,不然ORACLE会触发异常
12.2 操纵游标数据涉及到的知识点代码如下:
DECLARE
CURSOR cur_emp_all IS
SELECT s.* FROM hyz_emp s WHERE s.deptno = 20;
v_empno hyz_emp%ROWTYPE;
v_empno2 hyz_emp%ROWTYPE;
BEGIN
dbms_output.put_line('LOOP循环:');
--LOOP循环
OPEN cur_emp_all; --打开游标
LOOP
FETCH cur_emp_all
INTO v_empno;
EXIT WHEN cur_emp_all%NOTFOUND;
IF cur_emp_all%FOUND THEN
dbms_output.put_line('员工号:' || v_empno.empno || ',' || '姓名:' ||
v_empno.ename || ',' || '职业:' || v_empno.job || ',' ||
'上级编号:' || v_empno.mgr || ',' || '入职日期:' ||
v_empno.hiredate || ',' || '工资:' || v_empno.sal || ',' ||
'奖金:' || v_empno.comm || ',' || '部门号' ||
v_empno.deptno);
ELSE
dbms_output.put_line('遍历完毕');
END IF;
END LOOP;
CLOSE cur_emp_all; --关闭游标
dbms_output.put_line('WHILE循环:');
--WHILE循环
OPEN cur_emp_all; --打开游标
FETCH cur_emp_all
INTO v_empno2;
WHILE cur_emp_all%FOUND LOOP
dbms_output.put_line('员工号:' || v_empno2.empno || ',' || '姓名:' ||
v_empno2.ename || ',' || '职业:' || v_empno2.job || ',' ||
'上级编号:' || v_empno2.mgr || ',' || '入职日期:' ||
v_empno2.hiredate || ',' || '工资:' || v_empno2.sal || ',' ||
'奖金:' || v_empno2.comm || ',' || '部门号' ||
v_empno2.deptno);
FETCH cur_emp_all
INTO v_empno2;
END LOOP;
CLOSE cur_emp_all; --关闭游标
dbms_output.put_line('FOR循环:');
--FOR循环,隐式的自动开关游标,不要在使用前后打开和关闭游标,否则会报错
FOR cur_emp3 IN cur_emp_all LOOP
dbms_output.put_line('员工号:' || cur_emp3.empno || ',' || '姓名:' ||
cur_emp3.ename || ',' || '职业:' || cur_emp3.job || ',' ||
'上级编号:' || cur_emp3.mgr || ',' || '入职日期:' ||
cur_emp3.hiredate || ',' || '工资:' || cur_emp3.sal || ',' ||
'奖金:' || cur_emp3.comm || ',' || '部门号' ||
cur_emp3.deptno);
END LOOP;
END;
--使用WHERE CURRENT OF
DECLARE
CURSOR emp_cursor (p_deptno IN NUMBER)
IS
SELECT deptno
FROM hyz_emp
WHERE deptno = p_deptno
FOR UPDATE; --使用FOR UPDATE子句添加互斥锁
BEGIN
FOR emp_row IN emp_cursor (20) --使用游标FOR循环检索游标
LOOP
UPDATE hyz_emp
SET comm = comm * 1.12
WHERE CURRENT OF emp_cursor; --使用WHERE CURRENT OF更新游标数据
END LOOP;
COMMIT; --提交更改
END;
--------------------------------12.3 游标变量--------------------------------
12.3.1 游标变量简介
可以把静态游标与游标变量想象为常量与变量的区别, 常量的值一经定义就不可改
变, 而变量通过为其赋予不同的值而发生变化。 游标变量有点类似于高级语言中的指针,
它指向的是一块内存地址而不是具体的内容' 因此声明一个游标变量其实就是创建了一个
指针, 而不是创建了具体的内容。 在PL/SQL中, 指针是使用 REF 作为前缀进行定义的,
因此游标变量类型就是REF CURSOR类型.
12.3.2 声明游标变量类型
它的定义与集合或记录一样, 必须通过两个声明步骤来实现一个游标变量的
定义。
(l) 创建一个游标变量类型。
(2) 基于该类型创建实际的游标变量。
游标类型的声明语法如下所示。
TYPE cursor_type_name IS REF CURSOR [RETURN return_type ] ;
语法说明
- TYPE语句指定将要定义]一个类型,关键字REFCURSOR表示将定义一个游标变量类
型, REF 表示为一个指针类型, 因此理解起来就是表示指向游标的指针。 cursor_type_name
是游标类型的名称, RETURN指定游标将要返回的类型,要求是一个由%ROWTYPE属性
定义的记录或者是在定义游标类型之前预先定义的记录类型。- RETURN 子句是可选的y 当指定了 RETURN 子句后, 游标变量是受约束的, 要求游
标必须具有特定的返回类型, 因此查询的选择列表必须匹配游标的返回类型′ 否则将出现
预定义的 ROWTYPE_MISMATCH 异常。 如果不指定 RETURN 子句, 则游标变量是无约
束的, 当后来打开一个无约束的游标变量时, 可以为任何查询而打开。
注意:9i以上SYS_REFCURSOR 代替了gen_type
定义一个无约束的游标变量代码举例
TYPE emp__type IS REF CURSOR RETURN emp%ROWTYPE;
TYPE SYS_REFCURSOR IS REF CURSOR;
12.3.3 定义游标变量
语法
cursor_name cursor_type_name;
语法说明
cursor_name 是符合 PL/SQL 标识符命名规范的游标变量的名称, cursor_type_name是
预先使用 TYPE语句定义的游标类型
游标和游标变量的区别
12.3.4 打开游标变量
语法
OPEN cursor_name FOR select_statement;
语法说明
- cursor_name是所要打开的游标的名称,select_statement是要进行查询的SELECT语句。
由于仅在游标打开时,才 会为游标变量分配一个具体的游标对象, 因此 SELECT 语句的字
段列表的数据类型必须要与游标类型定义时的 RETURN子句相匹配或者相兼容- 当用不同的查询语句打开同一个游标变量的时候, 上一个查询将被丢弃掉。
12.3.5 控制游标变量
语法
FETCH cursor_variab1e_name INTO record_name;
EETCHcursor_variab1e_name INTO variab1e_name,variab1e_name . . . ;
语法说明
cursor_variab1e_name 是要提取的已打开的游标变量, record_name 是要提取的目标记
录类型. 也可以将游标变量中的列值直接赋给由 variable_name 指定的变量,使用 FETCH
语句提取游标变量数据.
12.3.6 处理游标变量异常
捕获INVALID_CURSOR,游标未打开异常即可
捕获ROWTYPE_MISMATCH,声明类型不一致异常
12.3.7 在包中使用游标变量
可以移至ORACLE PL/SQL从入门到精通笔记-包
12.3.8 游标变量的限制
- 不能在包中声明游标变量。
- 不能在创建表或创建视图的语句中把字段类型指定为 REFCURSOR类型, 数据库
字段是不能存放游标变量值的。- 游标类型的参数不支持使用远程过程调用 (RPC) 将游标变量从一个服务器传递
到另一个服务器。- 不能用比较操作符来判断两个游标变量是否相等、 不相等或者为 NULL。
- 不能为游标变量赋空值.
- 不能将REFCURSOR类型作为集合的元素类型,也就是说在索引表、 嵌套表和变
长数组中不能存放游标变量的值.- 不能将在游标中使用的游标FOR循环用在游标变量上, 也就是说游标和游标变量
不要试图互相替换.
12.3 游标变量涉及到的知识点代码如下:
DECLARE
TYPE emp_type IS REF CURSOR RETURN hyz_emp%ROWTYPE; --定义游标的类型
TYPE SYS_REFCURSOR IS REF CURSOR; --声明游标变量
emp_cur emp_type; --emp_cur定义为emp_type类型
gen_cur emp_type; --gen_cur定义为SYS_REFCURSOR类型
v_emp hyz_emp%ROWTYPE; --定义员工号
BEGIN
--打开游标变量的表emp_type的字段类型必须和hyz_emp表字段类型完全一致
OPEN emp_cur FOR
SELECT * FROM hyz_emp s WHERE s.deptno = 30;
--多次次后打开同一个emp_cur时,前面定义的emp_cur会被丢弃
OPEN emp_cur FOR
SELECT * FROM hyz_emp s WHERE s.deptno = 20;
LOOP
FETCH emp_cur
INTO v_emp;
EXIT WHEN emp_cur%NOTFOUND;
dbms_output.put_line('用户id为:' || v_emp.empno || '姓名为:' || v_emp.ename);
END LOOP;
CLOSE emp_cur;
END;