第十章 PL/SQL ---------游标 |
1、分类:2、步骤;3、应用中 ---for游标 |
显示游标 |
隐式游标 |
游标的使用 |
在 PL/SQL 程序中,对于处理多行记录的事务经常使用游标来实现。 |
4.1 游标概念 |
对于不同的SQL语句,游标的使用情况不同: |
SQL语句 |
游标 |
非查询语句 |
隐式的 |
结果是单行的查询语句 |
隐式的或显示的 |
结果是多行的查询语句 |
显示的 |
游标名%属性名 |
显式游标的名字由用户定义,隐式游标名为SQL |
隐式游标只是一个状态 |
4.1.1 处理显式游标 |
|
1. 显式游标处理 |
语法格式: |
CURSOR cursor_name is select * from emp; |
OPEN cursor_name; |
FETCH cursor_name INTO variables_list; |
CLOSE cursor_name; |
例1: |
DECLARE |
v_deptno NUMBER:=&inputno; |
v_row emp1%ROWTYPE; |
CURSOR v_cursor IS SELECT * FROM emp1 WHERE deptno=v_deptno; |
BEGIN |
--打开 |
OPEN v_cursor; |
--提取 |
LOOP |
FETCH v_cursor INTO v_row; --先提取 |
EXIT WHEN v_cursor%NOTFOUND; |
dbms_output.put_line( 'Employee Name: ' || v_row.ename || ' ,Salary: ' || v_row.sal); |
END LOOP; |
--关闭 |
CLOSE v_cursor; |
END ; |
显式游标处理需四个 PL/SQL步骤: |
l 定义/声明游标:就是定义一个游标名,以及与其相对应的 SELECT 语句。 |
格式: |
|
CURSOR cursor_name[(parameter[, parameter]…)] |
|
[ RETURN datatype] |
IS |
select_statement; |
select_statement; |
|
游标参数只能为输入参数,其格式为: |
|
parameter_name [ IN ] datatype [{:= | DEFAULT } expression] |
在指定数据类型时,不能使用长度约束。如NUMBER(4), CHAR (10) 等都是错误的。 |
[ RETURN datatype]是可选的,表示游标返回数据的数据。如果选择,则应该严格与select_statement中的选择列表在次序和数据类型上匹配。一般是记录数据类型或带“%ROWTYPE”的数据。 |
|
l 打开游标:就是执行游标所对应的 SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首部,标识游标结果集合。如果游标查询语句中带有 FOR UPDATE 选项, OPEN 语句还将锁定数据库表中游标结果集合对应的数据行。 |
格式: |
|
OPEN cursor_name[([parameter =>] value[, [parameter =>] value]…)]; |
|
在向游标传递参数时,可以使用与函数参数相同的传值方法,即位置表示法和名称表示法。PL/SQL 程序不能用 OPEN 语句重复打开一个游标。 |
|
l 提取游标数据:就是检索结果集合中的数据行,放入指定的输出变量中。 |
格式: |
|
FETCH cursor_name INTO {variable_list | record_variable }; |
|
执行 FETCH 语句时,每次返回一个数据行,然后自动将游标移动指向下一个数据行。当检索到最后一行数据时,如果再次执行 FETCH 语句,将操作失败,并将游标属性%NOTFOUND置为 TRUE 。所以每次执行完 FETCH 语句后,检查游标属性%NOTFOUND就可以判断 FETCH 语句是否执行成功并返回一个数据行,以便确定是否给对应的变量赋了值。 |
l 对该记录进行处理; |
l 继续处理,直到活动集合中没有记录; |
l 关闭游标:当提取和处理完游标结果集合数据后,应及时关闭游标,以释放该游标所占用的系统资源,并使该游标的工作区变成无效,不能再使用 FETCH 语句取其中数据。关闭后的游标可以使用 OPEN 语句重新打开。 |
格式: |
|
CLOSE cursor_name; |
|
注:定义的游标不能有 INTO 子句。 |
|
例1. 查询前10名员工的信息。 |
DECLARE |
CURSOR c_cursor |
IS SELECT first_name || last_name, Salary FROM EMPLOYEES WHERE rownum<11; |
v_ename EMPLOYEES.first_name%TYPE; |
v_sal EMPLOYEES.Salary%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.游标属性 |
Cursor_name%FOUND 布尔型属性,当最近一次提取游标操作 FETCH 成功则为 TRUE ,否则为 FALSE ; |
Cursor_name%NOTFOUND 布尔型属性,与%FOUND相反; |
Cursor_name%ISOPEN 布尔型属性,当游标已打开时返回 TRUE ; |
Cursor_name%ROWCOUNT 数字型属性,返回已从游标中读取的记录数。 |
|
例2:没有参数且没有返回值的游标。 |
DECLARE |
v_f_name employees.first_name%TYPE; |
v_j_id employees.job_id%TYPE; |
|
CURSOR c1 --声明游标,没有参数没有返回值 |
IS |
SELECT first_name, job_id FROM employees WHERE department_id = 20; |
BEGIN |
OPEN c1; --打开游标 |
LOOP |
FETCH c1 INTO v_f_name, v_j_id; --提取游标 |
IF c1%FOUND THEN |
DBMS_OUTPUT.PUT_LINE(v_f_name|| '的岗位是' ||v_j_id); |
ELSE |
DBMS_OUTPUT.PUT_LINE( '已经处理完结果集了' ); |
EXIT; |
END IF; |
END LOOP; |
CLOSE c1; --关闭游标 |
END ; |
|
例3:有参数且没有返回值的游标。 |
DECLARE |
v_f_name employees.first_name%TYPE; |
v_h_date employees.hire_date%TYPE; |
|
CURSOR c2(dept_id NUMBER, j_id VARCHAR2) --声明游标,有参数没有返回值 |
IS |
SELECT first_name, hire_date FROM employees WHERE department_id = dept_id AND job_id = j_id; |
BEGIN |
OPEN c2(90, 'AD_VP' ); --打开游标,传递参数值 |
LOOP |
FETCH c2 INTO v_f_name, v_h_date; --提取游标 |
IF c2%FOUND THEN |
DBMS_OUTPUT.PUT_LINE(v_f_name|| '的雇佣日期是' ||v_h_date); |
ELSE |
DBMS_OUTPUT.PUT_LINE( '已经处理完结果集了' ); |
EXIT; |
END IF; |
END LOOP; |
CLOSE c2; --关闭游标 |
END ; |
例4:有参数且有返回值的游标。 |
DECLARE |
TYPE emp_record_type IS RECORD( |
f_name employees.first_name%TYPE, |
h_date employees.hire_date%TYPE); |
v_emp_record EMP_RECORD_TYPE; |
v_emp_record EMP_RECORD_TYPE; |
CURSOR c3(dept_id NUMBER, j_id VARCHAR2) --声明游标,有参数有返回值 |
RETURN EMP_RECORD_TYPE |
IS |
SELECT first_name, hire_date FROM employees WHERE department_id = dept_id AND job_id = j_id; |
BEGIN |
OPEN c3(j_id => 'AD_VP' , dept_id => 90); --打开游标,传递参数值 |
LOOP |
FETCH c3 INTO v_emp_record; --提取游标 |
IF c3%FOUND THEN |
DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name|| '的雇佣日期是' ||v_emp_record.h_date); |
ELSE |
DBMS_OUTPUT.PUT_LINE( '已经处理完结果集了' ); |
EXIT; |
END IF; |
END LOOP; |
CLOSE c3; --关闭游标 |
END ; |
例5:基于游标定义记录变量。 |
DECLARE |
CURSOR c4(dept_id NUMBER, j_id VARCHAR2) --声明游标,有参数没有返回值 |
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' ); --打开游标,传递参数值 |
LOOP |
FETCH c4 INTO v_emp_record; --提取游标 |
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; --关闭游标 |
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 循环的记录。 |
|
例6:当所声明的游标带有参数时,通过游标 FOR 循环语句为游标传递参数。 |
DECLARE |
CURSOR c_cursor(dept_no NUMBER DEFAULT 10) |
IS |
SELECT department_name, location_id FROM departments WHERE department_id <= dept_no; |
BEGIN |
DBMS_OUTPUT.PUT_LINE( '当dept_no参数值为30:' ); |
FOR c1_rec IN c_cursor(30) LOOP |
DBMS_OUTPUT.PUT_LINE(c1_rec.department_name|| '---' ||c1_rec.location_id); |
END LOOP; |
DBMS_OUTPUT.PUT_LINE(CHR(10)|| '使用默认的dept_no参数值10:' ); |
FOR c1_rec IN c_cursor LOOP |
DBMS_OUTPUT.PUT_LINE(c1_rec.department_name|| '---' ||c1_rec.location_id); |
END LOOP; |
END ; |
|
例7:PL/SQL还允许在游标 FOR 循环语句中使用子查询来实现游标的功能。 |
BEGIN |
--隐含打开游标 |
FOR r IN ( SELECT * FROM emp1 WHERE deptno=v_deptno) LOOP |
--隐含执行一个FETCH语句 |
dbms_output.put_line( 'Employee Name: ' || r.ename || ' ,Salary: ' || r.sal); |
--隐含监测c_sal%NOTFOUND |
END LOOP; |
--隐含关闭游标 |
END ; |
|
4.1.2 处理隐式游标 |
对于非查询语句,如修改、删除操作,则由ORACLE 系统自动地为这些操作设置游标并创建其工作区,这些由系统隐含创建的游标称为隐式游标,隐式游标的名字为SQL,这是由ORACLE 系统定义的。对于隐式游标的操作,如定义、打开、取值及关闭操作,都由ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、最新处理的一条SQL 语句所包含的数据。 |
格式调用为: SQL% |
|
注: INSERT , UPDATE , DELETE , SELECT (单查询) 语句中不必明确定义游标。 |
|
隐式游标属性 |
属性 |
值 |
SELECT |
INSERT |
UPDATE |
DELETE |
SQL%ISOPEN |
|
FALSE |
FALSE |
FALSE |
FALSE |
SQL%FOUND |
TRUE |
有结果 |
|
成功 |
成功 |
SQL%FOUND |
FALSE |
没结果 |
|
失败 |
失败 |
SQL%NOTFUOND |
TRUE |
没结果 |
|
失败 |
失败 |
SQL%NOTFOUND |
FALSE |
有结果 |
|
成功 |
失败 |
SQL%ROWCOUNT |
|
返回行数,只为1 |
插入的行数 |
修改的行数 |
删除的行数 |
|
例8: 通过隐式游标SQL的%ROWCOUNT属性来了解修改了多少行。 |
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|| '个雇员的工资' ); |
--回退更新,以便使数据库的数据保持原样 |
ROLLBACK ; |
END ; |
|
4.1.3 使用游标更新和删除数据 |
游标修改和删除操作是指在游标定位下,修改或删除表中指定的数据行。这时,要求游标查询语句中必须使用 FOR UPDATE 选项,以便在打开游标时锁定游标结果集合在表中对应数据行的所有列和部分列。 |
为了对正在处理(查询)的行不被另外的用户改动,ORACLE 提供一个 FOR UPDATE 子句来对所选择的行进行锁住。该需求迫使ORACLE锁定游标结果集合的行,可以防止其他事务处理更新或删除相同的行,直到您的事务处理提交或回退为止。 |
语法: |
|
SELECT column_list FROM table_list FOR UPDATE [ OF column [, column ]…] [NOWAIT] |
|
如果使用 FOR UPDATE 声明游标,则可在 DELETE 和 UPDATE 语句中使用 |
WHERE CURRENT OF cursor_name子句,修改或删除游标结果集合当前行对应的数据库表中的数据行。 |
|
例9:从EMPLOYEES表中查询某部门的员工情况,将其工资最低定为 1500; |
DECLARE |
V_deptno employees.department_id |
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; |
BEGIN |
FOR emp_record IN emp_cursor LOOP |
IF emp_record.salary < 1500 THEN |
UPDATE employees SET salary=1500 WHERE CURRENT OF emp_cursor; |
END IF; |
END LOOP; |
COMMIT ; |
END ; |
4.2 游标变量 |
与游标一样,游标变量也是一个指向多行查询结果集合中当前数据行的指针。但与游标不同的是,游标变量是动态的,而游标是静态的。游标只能与指定的查询相连,即固定指向一个查询的内存处理区域,而游标变量则可与不同的查询语句相连,它可以指向不同查询语句的内存处理区域(但不能同时指向多个内存处理区域,在某一时刻只能与一个查询语句相连),只要这些查询语句的返回类型兼容即可。 |
4.2.1 声明游标变量 |
游标变量为一个指针,它属于参照类型,所以在声明游标变量类型之前必须先定义游标变量类型。在PL/SQL中,可以在块、子程序和包的声明区域内定义游标变量类型。 |
语法格式为: |
1.定义游标变量 |
TYPE cursortype IS REF CURSOR ; |
cursor_variable cursortype; |
2.打开游标变量 |
OPEN cursor_variable FOR dynamic_string |
[USING bind_argument[,bind_argument]...] |
3.循环提取数据 |
FETCH cursor_variable INTO {var1[,var2]...| record_variable}; |
EXIT WHEN cursor_variable%NOTFOUND |
4.关闭游标变量 |
CLOSE cursor_variable; |
|
例10:使用游标变量(没有 RETURN 子句) |
DECLARE |
--定义一个游标数据类型 |
TYPE emp_cursor_type IS REF CURSOR ; |
--声明一个游标变量 |
c1 EMP_CURSOR_TYPE; |
--声明两个记录变量 |
v_emp_record employees%ROWTYPE; |
v_reg_record regions%ROWTYPE; |
BEGIN |
OPEN c1 FOR SELECT * FROM employees WHERE department_id = 20; |
LOOP |
FETCH c1 INTO v_emp_record; |
EXIT WHEN c1%NOTFOUND; |
DBMS_OUTPUT.PUT_LINE(v_emp_record.first_name|| '的雇佣日期是' ||v_emp_record.hire_date); |
END LOOP; |
--将同一个游标变量对应到另一个SELECT语句 |
OPEN c1 FOR SELECT * FROM regions WHERE region_id IN (1,2); |
LOOP |
FETCH c1 INTO v_reg_record; |
|
EXIT WHEN c1%NOTFOUND; |
DBMS_OUTPUT.PUT_LINE(v_reg_record.region_id|| '表示' ||v_reg_record.region_name); |
END LOOP; |
CLOSE c1; |
END ; |
|
例11:使用游标变量(有 RETURN 子句) |
DECLARE |
--定义一个与employees表中的这几个列相同的记录数据类型 |
TYPE emp_record_type IS RECORD( |
f_name employees.first_name |
f_name employees.first_name%TYPE, |
h_date employees.hire_date |
h_date employees.hire_date%TYPE, |
j_id employees.job_id |
j_id employees.job_id%TYPE); |
--声明一个该记录数据类型的记录变量 |
v_emp_record EMP_RECORD_TYPE; |
--定义一个游标数据类型 |
TYPE emp_cursor_type IS REF CURSOR |
RETURN EMP_RECORD_TYPE; |
--声明一个游标变量 |
c1 EMP_CURSOR_TYPE; |
BEGIN |
OPEN c1 FOR SELECT first_name, hire_date, job_id FROM employees WHERE department_id = 20; |
LOOP |
FETCH c1 INTO v_emp_record; |
EXIT WHEN c1%NOTFOUND; |
DBMS_OUTPUT.PUT_LINE( '雇员名称:' ||v_emp_record.f_name|| ' 雇佣日期:' ||v_emp_record.h_date|| ' 岗位:' ||v_emp_record.j_id); |
END LOOP; |
CLOSE c1; |
END ; |