1. 两种提取数据方式:
FETCH 游标名 INTO 变量名1[,变量名2...];
FETCH 游标名 INTO 记录变量;
第一种格式中的变量名是用来从游标中接收数据的变量,需要事先定义。变量的个数和类型应与SELECT语句中的字段变量的个数和类型一致。
第二种格式一次将一行数据取到记录变量中,需要使用%ROWTYPE事先定义记录变量,这种形式使用起来比较方便,不必分别定义和使用多个变量。
定义记录变量的方法如下:
变量名 表名|游标名%ROWTYPE;
其中的表必须存在,游标名也必须先定义。
第一种:变量名
SET SERVEROUTPUT ON
DECLARE
v_ename VARCHAR2(10);
v_job VARCHAR2(10);
CURSOR emp_cursor IS
SELECT ename,job FROM emp WHERE empno=7788;
BEGIN
OPEN emp_cursor;
FETCH emp_cursor INTO v_ename,v_job;
DBMS_OUTPUT.PUT_LINE(v_ename||','||v_job);
CLOSE emp_cursor;
END;
1.1例子:
SET SERVEROUTPUT ON
DECLARE
CURSOR emp_cursor IS SELECT ename,job,sal FROM emp WHERE empno=7788;
emp_record emp_cursor%ROWTYPE;
BEGIN
OPEN emp_cursor;
FETCH emp_cursor INTO emp_record;
DBMS_OUTPUT.PUT_LINE(emp_record.ename||','|| emp_record.job||','|| emp_record.sal);
CLOSE emp_cursor;
END;
1.2例子
--显示工资最高的前3名雇员的名称和工资。
SET SERVEROUTPUT ON
DECLARE
V_ename VARCHAR2(10);
V_sal NUMBER(5);
CURSOR emp_cursor IS SELECT ename,sal FROM emp ORDER BY sal DESC;
BEGIN
OPEN emp_cursor;
FOR I IN 1..3 LOOP
FETCH emp_cursor INTO v_ename,v_sal;
DBMS_OUTPUT.PUT_LINE(v_ename||','||v_sal);
END LOOP;
CLOSE emp_cursor;
END;
--2.1 使用特殊的FOR循环形式显示全部雇员的编号和名称。
SET SERVEROUTPUT ON
DECLARE
CURSOR emp_cursor IS
SELECT empno, ename FROM emp;
BEGIN
FOR Emp_record IN emp_cursor LOOP
DBMS_OUTPUT.PUT_LINE(Emp_record.empno|| Emp_record.ename);
END LOOP;
END;
--2.2 另一种形式的游标循环
SET SERVEROUTPUT ON
BEGIN
FOR re IN (SELECT ename FROM EMP) LOOP
DBMS_OUTPUT.PUT_LINE(re.ename)
END LOOP;
END;
游标定义以后使用它的一些属性来进行结构控制是一种更为灵活的方法。显式游标的属性如下所示。
游标的属性 返回值类型 意 义
%ROWCOUNT 整型 获得FETCH语句返回的数据行数
%FOUND 布尔型 最近的FETCH语句返回一行数据则为真,否则为假
%NOTFOUND 布尔型 与%FOUND属性返回值相反
%ISOPEN 布尔型 游标已经打开时值为真,否则为假
SET SERVEROUTPUT ON
DECLARE
V_ename VARCHAR2(10);
CURSOR emp_cursor IS
SELECT ename FROM emp;
BEGIN
OPEN emp_cursor;
IF emp_cursor%ISOPEN THEN
LOOP
FETCH emp_cursor INTO v_ename;
EXIT WHEN emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(to_char(emp_cursor%ROWCOUNT)||'-'||v_ename);
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('用户信息:游标没有打开!');
END IF;
CLOSE emp_cursor;
END;
说明:本例使用emp_cursor%ISOPEN判断游标是否打开;使用emp_cursor%ROWCOUNT获得到目前为止FETCH语句返回的数据行数并输出;使用循环来获取数据,在循环体中使用FETCH语句;使用emp_cursor%NOTFOUND判断FETCH语句是否成功执行,当FETCH语句失败时说明数据已经取完,退出循环。
4.游标参数的传递
--4.1带参数的游标。
SET SERVEROUTPUT ON
DECLARE
V_empno NUMBER(5);
V_ename VARCHAR2(10);
CURSOR emp_cursor(p_deptno NUMBER, p_job VARCHAR2) IS
SELECT empno, ename FROM emp
WHERE deptno = p_deptno AND job = p_job;
BEGIN
OPEN emp_cursor(10, 'CLERK');
LOOP
FETCH emp_cursor INTO v_empno,v_ename;
EXIT WHEN emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_empno||','||v_ename);
END LOOP;
END;
说明:游标emp_cursor定义了两个参数:p_deptno代表部门编号,p_job代表职务。语句OPEN emp_cursor(10, 'CLERK')传递了两个参数值给游标,即部门为10、职务为CLERK,所以游标查询的内容是部门10的职务为CLERK的雇员。循环部分用于显示查询的内容。
--4.2通过变量向游标传递参数,但变量需要先于游标定义,并在游标打开之前赋值
SET SERVEROUTPUT ON
DECLARE
v_empno NUMBER(5);
v_ename VARCHAR2(10);
v_deptno NUMBER(5);
v_job VARCHAR2(10);
CURSOR emp_cursor IS
SELECT empno, ename FROM emp
WHERE deptno = v_deptno AND job = v_job;
BEGIN
v_deptno:=10;
v_job:='CLERK';
OPEN emp_cursor;
LOOP
FETCH emp_cursor INTO v_empno,v_ename;
EXIT WHEN emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_empno||','||v_ename);
END LOOP;
END;
语法:
execute immediate 查询语句字符串 into 变量1[,变量2...];
--5.1 动态SELECT查询
SET SERVEROUTPUT ON
DECLARE
str varchar2(100);
v_ename varchar2(10);
begin
str:='select ename from scott.emp where empno=7788';
execute immediate str into v_ename;
dbms_output.put_line(v_ename);
END;
通过采用动态游标,可以在程序运行阶段随时生成一个查询语句作为游标。要使用动态游标需要先定义一个游标类型,然后声明一个游标变量,游标对应的查询语句可以在程序的执行过程中动态地说明。
定义游标类型的语句如下:
TYPE 游标类型名 REF CURSOR;
声明游标变量的语句如下:
游标变量名 游标类型名;
在可执行部分可以如下形式打开一个动态游标:
OPEN 游标变量名 FOR 查询语句字符串;
--按名字中包含的字母顺序分组显示雇员信息。
declare
type cur_type is ref cursor;
cur cur_type;
rec scott.emp%rowtype;
str varchar2(50);
letter char:= '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;
说明:使用了二重循环,在外循环体中,动态生成游标的SELECT语句,然后打开。通过语句letter:=chr(ascii(letter)+1)可获得字母表中的下一个字母。
6. 异常处理
凡是出现在WHEN后面的错误都是可以捕捉到的错误,其他未被捕捉到的错误,将在WHEN OTHERS部分进行统一处理,OTHENS必须是EXCEPTION部分的最后一个错误处理分支。如要在该分支中进一步判断错误种类,可以通过使用预定义函数SQLCODE( )和SQLERRM( )来获得系统错误号和错误信息。
如果在程序的子块中发生了错误,但子块没有错误处理部分,则错误会传递到主程序中。
--6.1查询编号为1234的雇员名字。
SET SERVEROUTPUT ON
DECLARE
v_name VARCHAR2(10);
BEGIN
SELECT ename
INTO v_name
FROM emp
WHERE empno = 1234;
DBMS_OUTPUT.PUT_LINE('该雇员名字为:'|| v_name);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('编号错误,没有找到相应雇员!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生其他错误!');
END;
编号错误,没有找到相应雇员!
PL/SQL 过程已成功完成。
6.2由程序代码显示系统错误。
SET SERVEROUTPUT ON
DECLARE
v_temp NUMBER(5):=1;
BEGIN
v_temp:=v_temp/0;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生系统错误!');
DBMS_OUTPUT.PUT_LINE('错误代码:'|| SQLCODE( ));
DBMS_OUTPUT.PUT_LINE('错误信息:' ||SQLERRM( ));
END;
错 误 名 称 错误代码 错 误 含 义
CURSOR_ALREADY_OPEN ORA_06511 试图打开已经打开的游标
INVALID_CURSOR ORA_01001 试图使用没有打开的游标
DUP_VAL_ON_INDEX ORA_00001 保存重复值到惟一索引约束的列中
ZERO_DIVIDE ORA_01476 发生除数为零的除法错误
INVALID_NUMBER ORA_01722 试图对无效字符进行数值转换
ROWTYPE_MISMATCH ORA_06504 主变量和游标的类型不兼容
VALUE_ERROR ORA_06502 转换、截断或算术运算发生错误
TOO_MANY_ROWS ORA_01422 SELECT…INTO…语句返回多于一行的数据
NO_DATA_FOUND ORA_01403 SELECT…INTO…语句没有数据返回
TIMEOUT_ON_RESOURCE ORA_00051 等待资源时发生超时错误
TRANSACTION_BACKED_OUT ORA_00060 由于死锁,提交失败
STORAGE_ERROR ORA_06500 发生内存错误
PROGRAM_ERROR ORA_06501 发生PL/SQL内部错误
NOT_LOGGED_ON ORA_01012 试图操作未连接的数据库
LOGIN_DENIED ORA_01017 在连接时提供了无效用户名或口令
--定义新的系统错误类型。
SET SERVEROUTPUT ON
DECLARE
V_ENAME VARCHAR2(10);
NULL_INSERT_ERROR EXCEPTION;
PRAGMA EXCEPTION_INIT(NULL_INSERT_ERROR,-1400);
BEGIN
INSERT INTO EMP(EMPNO) VALUES(NULL);
EXCEPTION
WHEN NULL_INSERT_ERROR THEN
DBMS_OUTPUT.PUT_LINE('无法插入NULL值!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('发生其他系统错误!');
END;
--执行结果为:
无法插入NULL值!
PL/SQL 过程已成功完成。
程序设计者可以利用引发异常的机制来进行程序设计,自己定义异常类型。可以在声明部分定义新的异常类型,定义的语法是:
错误名 EXCEPTION;
用户定义的错误不能由系统来触发,必须由程序显式地触发,触发的语法是:
RAISE 错误名;
RAISE也可以用来引发模拟系统错误,比如,RAISE ZERO_DIVIDE将引发模拟的除零错误。
使用RAISE_APPLICATION_ERROR函数也可以引发异常。该函数要传递两个参数,第一个是用户自定义的错误编号,第二个参数是用户自定义的错误信息。使用该函数引发的异常的编号应该在20 000和20 999之间选择。
自定义异常处理错误的方式同前。
--8.1插入新雇员,限定插入雇员的编号在7000~8000之间。
SET SERVEROUTPUT ON
DECLARE
new_no NUMBER(10);
new_excp1 EXCEPTION;
new_excp2 EXCEPTION;
BEGIN
new_no:=6789;
INSERT INTO emp(empno,ename)
VALUES(new_no, '小郑');
IF new_no<7000 THEN
RAISE new_excp1;
END IF;
IF new_no>8000 THEN
RAISE new_excp2;
END IF;
COMMIT;
EXCEPTION
WHEN new_excp1 THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('雇员编号小于7000的下限!');
WHEN new_excp2 THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('雇员编号超过8000的上限!');
END;
--执行结果为:
雇员编号小于7000的下限!
PL/SQL 过程已成功完成。
说明:在此例中,自定义了两个异常:new_excp1和new_excp2,分别代表编号小于7000和编号大于8000的错误。在程序中通过判断编号大小,产生对应的异常,并在异常处理部分回退插入操作,然后显示相应的错误信息。
--使用RAISE_APPLICATION_ERROR函数引发系统异常。
SET SERVEROUTPUT ON
DECLARE
New_no NUMBER(10);
BEGIN
New_no:=6789;
INSERT INTO emp(empno,ename)
VALUES(new_no, 'JAMES');
IF new_no<7000 THEN
ROLLBACK;
RAISE_APPLICATION_ERROR(-20001, '编号小于7000的下限!');
END IF;
IF new_no>8000 THEN
ROLLBACK;
RAISE_APPLICATION_ERROR (-20002, '编号大于8000的下限!');
END IF;
END;
--执行结果为:
DECLARE
*
ERROR 位于第 1 行:
ORA-20001: 编号小于7000的下限!
ORA-06512: 在line 9
DECLARE
v_error_code NUMBER;
v_error_message VARCHAR2(255);
BEGIN
...
EXCEPTION
...
WHEN OTHERS THEN
v_error_code := SQLCODE ;
v_error_message := SQLERRM ;
INSERT INTO errors
VALUES(v_error_code, v_error_message);
END;