错误的分类
在PL/SQL中可能出现两种错误
- 编译错误 即语法错误 程序编写没有满足语法要求
- 运行时错误 语法没有错误,但在运行时报错.这种错误语法检查器无法监测. 对于运行时错误我们又称之为异常
在PL/SQL的执行部分有一个可选的异常处理部分.
异常处理的优点
使程序正常结束而不是中途报错 将错误处理过程独立,增加程序的逻辑性 错误输出更加丰富,友好.
预定义异常
Oracle将常见的21个异常定义为预定义异常,并为每个异常指定了一个名字.
PL/SQL块中的异常处理部分通过异常的名字对异常进行捕获处理.
异常名称 | 异常原因 | 错误编号 |
---|---|---|
ACCESS_INTO_NULL | 未定义对象 | ORA-06530 -6530 |
CASE_NOT_FOUND | CASE 中若未包含相应的WHEN ,并且没有设置ELSE 时 | ORA-06592 -6592 |
COLLECTION_IS_NULL | 集合元素未初始化 | ORA-06531 -6531 |
CURSOR_ALREADY_OPEN | 游标已经打开 | ORA-06511 -6511 |
DUP_VAL_ON_INDEX | 唯一索引对应的列上有重复的值 | ORA-00001 -1 |
INVALID_NUMBER | 无效数字 | ORA-01722 -1722 |
INVALID_CURSOR | 在不合法的游标上进行操作 | ORA-01001 -1001 |
NO_DATA_FOUND | 使用 select into 未返回行,或应用索引表未初始化的元素时 | ORA-01403 +100 |
TOO_MANY_ROWS | 执行 select into 时,结果集超过一行 | ORA-01422 -1422 |
ZERO_DIVIDE | 除数为 0 | ORA-01476 -1476 |
SUBSCRIPT_BEYOND_COUNT | 元素下标超过嵌套表或VARRAY 的最大值 | ORA-06533 -6533 |
SUBSCRIPT_OUTSIDE_LIMIT | 使用嵌套表或 VARRAY 时,将下标指定为负数 | ORA-06532 -6532 |
VALUE_ERROR | 赋值时,变量长度不足以容纳实际数据 | ORA-06502 -6502 |
LOGIN_DENIED | PL/SQL 应用程序连接到oracle 数据库时,提供了不正确的用户名或密码 | ORA-01017 -1017 |
NOT_LOGGED_ON | PL/SQL 应用程序在没有连接oralce数据库的情况下访问数据 | ORA-01012 -1012 |
PROGRAM_ERROR | PL/SQL 内部问题,可能需要重装数据字典& PL/SQL 系统包 | ORA-06501 -6501 |
ROWTYPE_MISMATCH | 宿主游标变量与 PL/SQL 游标变量的返回类型不兼容 | ORA-06504 -6504 |
SELF_IS_NULL | 使用对象类型时,在 null 对象上调用对象方法 | ORA-30625 -30625 |
STORAGE_ERROR | 运行 PL/SQL 时,超出内存空间 | ORA-06500 -6500 |
SYS_INVALID_ROWID | 无效的 ROWID 字符串 | ORA-01410 -1410 |
TIMEOUT_ON_RESOURCE | Oracle 在等待资源时超时 | ORA-00051 -51 |
预定义异常实例
ZERO_DIVIDE
例程:
DECLARE
V1 NUMBER := 1;
V2 NUMBER := 0;
V3 NUMBER;
BEGIN
V3 := V1 / V2; --此处0做除数引发异常,直接跳转到异常处理部分
DBMS_OUTPUT.PUT_LINE('OK1'); --此处不打印OK
EXCEPTION --异常处理部分
WHEN ZERO_DIVIDE THEN --当发现异常是ZERO_DIVIDE时,打印ZERO DIVIDE
DBMS_OUTPUT.put_line('ZERO DIVIDE');
end;
执行结果:
ZERO DIVIDE
PL/SQL procedure successfully completed
NO_DATA_FOUND
例程:
DECLARE
V_1 NUMBER;
BEGIN
SELECT 1 INTO V_1 FROM DUAL WHERE 1 = 2;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-01403: no data found
PL/SQL procedure successfully completed
TOO_MANY_ROWS
例程:
DECLARE
V_NUM NUMBER ;
BEGIN
SELECT 1
INTO V_NUM
FROM DUAL
CONNECT BY LEVEL<=2 ;
EXCEPTION
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-01422: exact fetch returns more than requested number of rows
PL/SQL procedure successfully completed
CASE_NOT_FOUND
例程:
DECLARE
V_1 NUMBER;
BEGIN
CASE 1
WHEN 2 THEN
NULL;
WHEN 3 THEN
NULL;
END CASE;
EXCEPTION
WHEN CASE_NOT_FOUND THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-06592: CASE not found while executing CASE statement
PL/SQL procedure successfully completed
CURSOR_ALREADY_OPEN
当重复打开游标时会引发此异常
例程:
DECLARE
CURSOR C1 IS SELECT * FROM DUAL ;
BEGIN
OPEN C1 ;
OPEN C1;
EXCEPTION
WHEN CURSOR_ALREADY_OPEN THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-06511: PL/SQL: cursor already open
PL/SQL procedure successfully completed
INVALID_CURSOR
例程:
DECLARE
CURSOR C1 IS SELECT * FROM DUAL ;
BEGIN
OPEN C1 ;
CLOSE C1 ;
DBMS_OUTPUT.put_line(C1%ROWCOUNT);
EXCEPTION
WHEN INVALID_CURSOR THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-01001: invalid cursor
PL/SQL procedure successfully completed
DUP_VAL_ON_INDEX
例程:
DECLARE
CURSOR C1 IS
SELECT * FROM DUAL;
BEGIN
INSERT INTO TEST_PK VALUES(1,1);
INSERT INTO TEST_PK VALUES(1,1);
EXCEPTION
WHEN DUP_VAL_ON_INDEX THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-00001: unique constraint (DAO.SYS_C0013692) violated
PL/SQL procedure successfully completed
INVALID_NUMBER
例程:
DECLARE
V_NUM NUMBER ;
BEGIN
SELECT TO_NUMBER('A')
INTO V_NUM
FROM DUAL ;
EXCEPTION
WHEN INVALID_NUMBER THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-01722: invalid number
PL/SQL procedure successfully completed
VALUE_ERROR
例程:
DECLARE
V_NUM NUMBER ;
BEGIN
V_NUM :='A';
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
PL/SQL procedure successfully completed
SUBSCRIPT_BEYOND_COUNT
例程:
DECLARE
TYPE TYPE_NUM_NT IS TABLE OF NUMBER;
T1 TYPE_NUM_NT := TYPE_NUM_NT(11);
BEGIN
T1(2) := 100;
EXCEPTION
WHEN SUBSCRIPT_BEYOND_COUNT THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-06533: Subscript beyond count
PL/SQL procedure successfully completed
SUBSCRIPT_OUTSIDE_LIMIT
例程:
DECLARE
TYPE TYPE_NUM_NT IS VARRAY(5) OF NUMBER;
T1 TYPE_NUM_NT := TYPE_NUM_NT(11);
BEGIN
T1(6) := 100;
EXCEPTION
WHEN SUBSCRIPT_OUTSIDE_LIMIT THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-06532: Subscript outside of limit
PL/SQL procedure successfully completed
ROWTYPE_MISMATCH
例程:
DECLARE
TYPE REF_CURSOR IS REF CURSOR;
C1 REF_CURSOR;
V_ENAME EMP.ENAME%TYPE;
BEGIN
OPEN C1 FOR
SELECT 1, 2 FROM DUAL;
FETCH C1
INTO V_ENAME;
CLOSE C1;
EXCEPTION
WHEN ROWTYPE_MISMATCH THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
执行结果:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
PL/SQL procedure successfully completed
ACCESS_INTO_NULL
例程:
CREATE TYPE address_typ AS OBJECT (
street VARCHAR2(30),
city VARCHAR2(20),
state CHAR(2),
postal_code VARCHAR2(6) );
/
CREATE TYPE employee_typ AS OBJECT (
employee_id NUMBER(6),
first_name VARCHAR2(20),
last_name VARCHAR2(25),
email VARCHAR2(25),
phone_number VARCHAR2(20),
hire_date DATE,
job_id VARCHAR2(10),
salary NUMBER(8,2),
commission_pct NUMBER(2,2),
manager_id NUMBER(6),
department_id NUMBER(4),
address address_typ,
MAP MEMBER FUNCTION get_idno RETURN NUMBER,
MEMBER PROCEDURE display_address ( SELF IN OUT NOCOPY employee_typ ) );
/
CREATE TYPE BODY employee_typ AS
MAP MEMBER FUNCTION get_idno RETURN NUMBER IS
BEGIN
RETURN employee_id;
END;
MEMBER PROCEDURE display_address ( SELF IN OUT NOCOPY employee_typ ) IS
BEGIN
DBMS_OUTPUT.PUT_LINE(first_name || ' ' || last_name);
DBMS_OUTPUT.PUT_LINE(address.street);
DBMS_OUTPUT.PUT_LINE(address.city || ', ' || address.state || ' ' ||
address.postal_code);
END;
END;
/
DECLARE
emp employee_typ; -- emp is atomically null
BEGIN
emp.employee_id := 330;
END;
/
执行结果:
ORA-06530: Reference to uninitialized composite
ORA-06512: at line 4
例程:
DECLARE
emp employee_typ; -- emp is atomically null
BEGIN
emp.employee_id := 330;
EXCEPTION
WHEN ACCESS_INTO_NULL THEN
DBMS_OUTPUT.PUT_LINE('Cannot assign value to NULL object');
END;
/
执行结果:
Cannot assign value to NULL object
SELF_IS_NULL
例程:
DECLARE
EMP EMPLOYEE_TYP; -- EMP IS ATOMICALLY NULL ;
V_IDNO NUMBER;
BEGIN
V_IDNO := EMP.GET_IDNO;
EXCEPTION
WHEN SELF_IS_NULL THEN
DBMS_OUTPUT.PUT_LINE('SELF_IS_NULL');
END;
/
执行结果:
SELF_IS_NULL
COLLECTION_IS_NULL
例程:
declare
type ct is table of number ;
collection1 ct ;
begin
collection1(0):=0 ;
exception
when COLLECTION_IS_NULL
then dbms_output.put_line('COLLECTION_IS_NULL');
end ;
执行结果:
COLLECTION_IS_NULL
ROWTYPE_MISMATCH
例程:
DECLARE
TYPE REF_CURSOR IS REF CURSOR ;
C1 REF_CURSOR ;
EMP_REC EMP%ROWTYPE ;
BEGIN
OPEN C1 FOR SELECT * FROM DEPT ;
FETCH C1 INTO EMP_REC ;
CLOSE C1 ;
EXCEPTION
WHEN ROWTYPE_MISMATCH THEN
DBMS_OUTPUT.PUT_LINE('ROWTYPE_MISMATCH');
END ;
执行结果:
ROWTYPE_MISMATCH
SYS_INVALID_ROWID
例程:
DECLARE
C1 ROWID :='AAAAAAAAAAAAAAAAAA' ;
BEGIN
DELETE FROM EMP WHERE ROWID=C1 ;
EXCEPTION
WHEN SYS_INVALID_ROWID THEN
DBMS_OUTPUT.PUT_LINE('SYS_INVALID_ROWID');
END ;
执行结果:
SYS_INVALID_ROWID
STORAGE_ERROR
例程:
DECLARE
TYPE TC1 IS TABLE OF CHAR(2000) ;
DAO_COL TC1 :=TC1();
BEGIN
WHILE TRUE
LOOP
DAO_COL.EXTEND();
END LOOP ;
EXCEPTION
WHEN STORAGE_ERROR
THEN DBMS_OUTPUT.PUT_LINE('STORAGE_ERROR');
END ;
执行结果:
STORAGE_ERROR
异常的抛出与捕获
- 对于内置异常Oracle采取隐式抛出
- 对于用户自定义异常使用RAISE语句抛出
- 异常在异常处理部分依靠名字来捕获
- OTHERS 关键字写在异常处理的部分的最后,可以捕获用户自定异常或未在前面声明的异常
例程:隐式抛出
BEGIN
DELETE FROM SCOTT.DEPT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR');
END;
执行结果
ERROR
PL/SQL procedure successfully completed
例程:显式抛出
BEGIN
RAISE NO_DATA_FOUND ;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR');
END;
ERROR
PL/SQL procedure successfully completed
例程:OTHERS
BEGIN
DELETE FROM SCOTT.DEPT;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.put_line('RETURN NO VALUE');
WHEN OTHERS THEN
DBMS_OUTPUT.put_line(SQLERRM);
END;
执行结果
ORA-02292: integrity constraint (SCOTT.FK_DEPTNO) violated - child record found
PL/SQL procedure successfully completed
自定义异常
- 当我们违反了Oracle的内部规则时Oracle会抛出异常,这些Oracle自带的错误信息我们称之为内置异常
- 而有时程序数据会违反一些业务逻辑,而非Oracle内部规则.此时我们需用自定义异常进行处理
例程:Oracle自定义异常
DECLARE
V_SAL NUMBER;
EXCP_SAL_ZERO EXCEPTION; -- 定义自定义异常
BEGIN
V_SAL := &1;
IF V_SAL < 0 THEN -- 发现工资小于0抛出异常
RAISE EXCP_SAL_ZERO;
END IF;
UPDATE EMP SET SAL = SAL WHERE ROWNUM = 1;
EXCEPTION
WHEN EXCP_SAL_ZERO THEN -- 对异常进行捕获
DBMS_OUTPUT.PUT_LINE('ERROR SAL LESS THAN ZERO ');
END;
执行结果
ERROR SAL LESS THAN ZERO
PL/SQL procedure successfully completed
异常信息的获取
- SQLCODE 返回Oracle的报错编号
- SQLERRM 返回Oracle的报错信息
例程:SQLCODE && SQLERRM
BEGIN
DELETE FROM SCOTT.DEPT ;
EXCEPTION
WHEN OTHERS
THEN DBMS_OUTPUT.put_line(SQLCODE);
DBMS_OUTPUT.put_line(SQLERRM);
END ;
执行结果
-2292
ORA-02292: integrity constraint (SCOTT.FK_DEPTNO) violated - child record found
PL/SQL procedure successfully completed
异常的作用域
异常作用域主要是指自定义异常的作用域. 自定义异常的作用域与变量相同
- 父块声明的异常可以被子块引用
- 子块声明的异常不可以被父块引用
- 兄弟块之间的异常不可引用
- 如果子块与父块之间存在同名异常,子块在引用变量时需要借助标签,但是这并没有什么意义
DECLARE
E1 EXCEPTION;
BEGIN
BEGIN
RAISE E1;
END;
END;
执行结果
ORA-06510: PL/SQL: unhandled user-defined exception
ORA-06512: at line 5
BEGIN
DECLARE
E1 EXCEPTION ;
BEGIN
NULL ;
END ;
RAISE E1 ; --父块抛出子块异常引发报错
END ;
执行结果
ORA-06550: line 7, column 9:
PLS-00201: identifier 'E1' must be declared
ORA-06550: line 7, column 3:
PL/SQL: Statement ignored
BEGIN
DECLARE
E1 EXCEPTION ;
BEGIN
NULL ;
END ;
BEGIN
RAISE E1 ; --子块2调用子块1定义的异常引发报错
END ;
END ;
执行结果
ORA-06550: line 8, column 10:
PLS-00201: identifier 'E1' must be declared
ORA-06550: line 8, column 4:
PL/SQL: Statement ignored
异常的抛出与捕获
- BEGIN
END ;
异常的传播规则
执行部分引发的异常
- 子块中执行部分引发的异常先由异常处理部分进行捕获
- 如果子块中异常处理部分无法处理,抛由父块进行处理,以此类推,一直到程序的调用者
BEGIN
BEGIN
RAISE NO_DATA_FOUND;
EXCEPTION
WHEN NO_DATA_FOUND THEN --执行部分引发的异常先由子块处理
DBMS_OUTPUT.PUT_LINE('INNER BLOCK NO_DATA_FOUND!');
END;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('OUTER BLOCK NO_DATA_FOUND!');
END;
执行结果
INNER BLOCK NO_DATA_FOUND!
PL/SQL procedure successfully completed
BEGIN
BEGIN
RAISE TOO_MANY_ROWS;
EXCEPTION
WHEN NO_DATA_FOUND THEN --子块中的异常处理部分无法处理TOO_MANY_ROWS
DBMS_OUTPUT.PUT_LINE('INNER BLOCK NO_DATA_FOUND!');
END;
EXCEPTION
WHEN TOO_MANY_ROWS THEN --父块异常处理部分可以对TOO_MANY_ROWS进行处理
DBMS_OUTPUT.PUT_LINE('OUTER BLOCK TOO_MANY_ROWS!');
END;
执行结果
OUTER BLOCK TOO_MANY_ROWS!
PL/SQL procedure successfully completed
定义部分与异常处理部分引发的异常
- 由父块中的异常处理部分进行捕获
- 如果父块中异常处理部分无法处理,交由父块的父块进行处理
例程:定义部分的异常
BEGIN
DECLARE
V1 NUMBER(1) := 10;
BEGIN
NULL;
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('INNER BLOCK VALUE_ERROR!');
END;
EXCEPTION
WHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('OUTER BLOCK VALUE_ERROR!');
END;
执行结果
OUTER BLOCK VALUE_ERROR!
PL/SQL procedure successfully completed
例程:
BEGIN
DECLARE
V1 NUMBER(1);
BEGIN
RAISE NO_DATA_FOUND; --1 抛出异常
EXCEPTION
WHEN NO_DATA_FOUND THEN --2 捕获后引发VALUES_ERROR异常
V1 := 10;
WHEN VALUE_ERROR THEN -- 子块异常处理部分无法处理
DBMS_OUTPUT.PUT_LINE('INNER BLOCK VALUE_ERROR!');
END;
EXCEPTION
WHEN VALUE_ERROR THEN --3 交由父块异常处理部分处理
DBMS_OUTPUT.PUT_LINE('OUTER BLOCK VALUE_ERROR!');
END;
执行结果
OUTER BLOCK VALUE_ERROR!
PL/SQL procedure successfully completed
RAISE_ APPLICATION_ERROR
RAISE_ APPLICATION_ERROR是Oracle提供的一个过程,允许创建有意义的错误消息.
本质上来说是Oracle部分开放了内部报错机制供开发人员使用
RAISE_ APPLICATION_ERROR有三个参数
- error_number 报错编号从[-20999 , -20000]
- error_message 报错信息
- keep_errors 是否保留错误栈
BEGIN
RAISE NO_DATA_FOUND ;
EXCEPTION
WHEN OTHERS THEN --保留错误栈
RAISE_APPLICATION_ERROR(-20000,'ERROR MESSAGES',TRUE);
END;
ORA-20000: ERROR MESSAGES
ORA-06512: at line 5
ORA-01403: no data found
执行结果
ORA-20000: ERROR MESSAGES
ORA-06512: at line 5
ORA-01403: no data found
BEGIN
RAISE NO_DATA_FOUND ;
EXCEPTION
WHEN OTHERS THEN --不保留错误栈,之前的报错信息会被覆盖
RAISE_APPLICATION_ERROR(-20000,'ERROR MESSAGES',FALSE);
END;
执行结果
ORA-20000: ERROR MESSAGES
ORA-06512: at line 5 --没有打印 ORA-01403: no data found
EXCEPTION_INIT
让我们先来总结下各种类型的异常
异常类别 | 是否有名字 | 是否存在报错编号 | 是否存在报错信息 |
---|---|---|---|
预定义异常 | YES | YES | YES |
其他内置异常 | NO | YES | YES |
自定义异常 | YES | NO | NO |
而我们又知道在异常处理部分中,异常是通过名字来被捕获的,所以其他内置异常无法通过名字捕获.
此时我们可以使用编译指令EXCEPTION_INIT将自定义异常与其他内置异常进行组合
组合后的结果非常类似于预定义异常,即存在名字,报错编号,报错名称
DECLARE
EXCP_CHILD_RECORD_FOUND EXCEPTION;
PRAGMA EXCEPTION_INIT(EXCP_CHILD_RECORD_FOUND, -2292); --将自定义异常EXCP_CHILD_RECORD_FOUND与ora-02292绑定
BEGIN
DELETE FROM SCOTT.DEPT;
EXCEPTION
WHEN EXCP_CHILD_RECORD_FOUND THEN --通过捕获绑定的自定义异常捕获报错编码为ora-02292的内置异常
DBMS_OUTPUT.put_line(SQLERRM);
END;
执行结果
ORA-02292: integrity constraint (SCOTT.FK_DEPTNO) violated - child record found
PL/SQL procedure successfully completed