一、异常
在PL/SQL中出现的警告或错误叫异常,对异常的处理称为异常处理
异常可以分为:预定义异常、用户自定义异常
1、预定义异常
它是由系统定义的异常,由于它们已在standard包中预定义了,因此这些预定义异常可以直接在程序中使用,而不必在定义部分声明。
2、用户自定义异常
它需要在定义部分声明后,才能在可执行部分使用
3、异常声明(用户自定义)
注意:异常声明,它必须先在定义部分声明
方法一:异常声明的语法如下
exception_name EXCEPTION
说明如下:
(1)EXCEPTION用来指定声明的是异常,exception是一个自定义异常名
(2)这种声明的异常,可以通过RAISE语句来产生这个异常,且这种异常是跟预定义异常对应的Oracle的错误相连的
例1:声明一个名为e_TooManyAuthors的异常
DECLARE
e_TooManyAuthors EXCEPTION;
注意如下:
①异常是一个错误状态,而不是一个数据项,所以异常不能出现在赋值语句或者SQL语句中
②异常的作用域,它与定义部分其它变量的作用域相同
③如果让一个用户在异常的作用域外使用该异常,那么可以将该异常放在包中进行声明,而使用时在异常前加包名前缀即可
例2:下面的包中声明了一个异常e_UserDefinedException,这个异常可以在任何块中使用
CREATEORREPLACEPACKAGE globals
/*这个包中声明的对象可在任意块中引用,注意这个包没有包体*/
e_UserDefinedException EXCEPTION;
....
END globals;
方法二:由于预定义异常只是与一部分Oracle错误相连的异常,所以如果要处理没有与预定义异常对应的Oracle的错误时,则需要为这些Oracle错误声明相应的用户自定义异常。声明语的语法如下:
exception_name EXCEPTION;
PRAGMA EXCEPTION_INIT(exception_name,Oracle_error_number);
说明如下:
(1)exception_name是预先被声明的异常名,Oracle_error_number是错误号
(2)这种声明的异常,可以自动产生异常(当然也是可以使用RAISE来产生异常的),且这种异常是与预定义异常对应的Oracle的错误不相连的
例1:在块定义部分声明了一个与错误”ORA-01401:inserted value too large for column”对应的用户自定义异常c_TooLarge。当在块的可执行部分向表中插入的列值超出指定的列长度时,则产生e_TooLarge异常
DECLARE
v_Code auths.name%TYPE;
e_TooLarge EXCEPTION;
PRAGMAEXCEPTION_INIT(e_TooLarge,-1401);
注意如下:
①通过EXCEPTION_INIT,一个用户自定义异常只能和一个Oracle错误相连
②在异常处理语句中,SQLCODE和SQLERRM将返回这个Oracle错误的代码和消息文本,而不是返回用户自定义消息
4、异常产生(用户自定义)
注意:当与预定义异常对应的错误出现时,则该预定义异常就会自动产生
1)第一种:由exception_name EXCEPTION定义的异常,通常由RAISE语句产生(由EXCEPTION_INIT编译指令声明的用户自定义异常也可以通过对应原Oracle错误的出现而产生)。当然如果需要的话,预定义异常也可以使用RAISE语句来产生
3)例子
例1:下面块中使用RAISE语句产生用户自定义异常e_TooSmallSalary
DECLARE
--声明用户自定义异常e_TooSmallSalary
e_TooSmallSalary EXCEPTION;
--声明当前作用的工资变量v_CurrentSalary
v_CurrentSalary NUMBER(8,2);
--声明作家工资的最低限变量v_SmallSalary
v_SmallSalary NUMBER(8,2)DEFAULT100;
BEGIN
--查找"A00002"作家的工资
SELECT salary INTO v_CurrentSalary FROM auths
WHERE author_code ='A00002';
--检查作家的工资
IF v_CurrentSalary < v_SmallSalary THEN
--产生e_TooSmallSalary异常
RAISE e_TooSmallSalary;
ELSE
NULL;
ENDIF;
END;
注意如下:当一个异常产生时,控制权立即转交给块的异常处理部分。如果该块没有异常处理部分,则向该块的外一层块传递。一旦控制权交给了异常处理部分,则再没有办法返回到块的可执行部分
2)第二种:由EXCEPTION_INIT定义的异常,会自动产生该异常例如:声明了一个用户自定义异常e_TooLarge(使用EXCEPTION_INIT),当向表中插入一个列值超出了该列指定的长度时,会自动产生异常
DECLARE
v_Code auths.name%TYPE;
e_TooLarge EXCEPTION;
PRAGMAEXCEPTION_INIT(e_TooLarge,-1401);
BEGIn
INSERTINTO auths(author_code,name, birthdate, entry_date_time)
VALUES('A00001','WANG',TO_DATE('11-3-50'),TO_DATE('12-1-97'));
END;
说明如下:
(1)由于插入的作家代码值”A00001”超出了列author_code的长度,所以产生错误”ORA-01401: inserted value too large for column”,它对应用户自定义异常”e_ToolArge”,同时控制权转到块外的调整用环境。
(2)当用户自定义异常e_TooLarge也可以使用RAISE语句来产生
3)第三种:使用RAISE_APPLICATION_ERROR函数来直接产生异常,并且能为异常定义用户自己指定的错误的消息。执行完RAISE_APPLICATION_ERROR函数后,控制权转到块外的调用环境(1)语法如下
RAISE_APPLICATION_ERROR(error_number,error_message, [keep_errors]);
说明如下
①error_number:是一个错误号,但值必须在-20000到-20999之间
②error_message:是与该错误相连的错误消息文本,它最大不能超过512个字符
③keep_errors是一个boolean值(可选参数),如果该值为TRUE,则这个新的错误将加在已产生的错误列表之后;如果该值为FLASE,则这个新错误将代替当前的错误列表。
(2)例子
CREATEORREPLACEPROCEDURE SalaryAdd(
--p_Author_code用来确定准备长工资的作用
p_Author_code IN auths.author_code%TYPE,
--p_AddSalary用来传入增加的工资
p_AddSalary IN auths.salary%TYPE)AS
--v_CurrentSalary用来存储作家工资增加以后的值
v_CurrentSalary auths.salary%TYPE;
--v_MaxSalary用来存储最高工资1000元
v_MaxSalary auths.salary%TYPEDEFAULT1000;
BEGIN
UPDATE auths SET salary=salary+p_AddSalary
WHERE author_code = p_Author_code
RETURNING salary INTO v_CurrentSalary;
--如果作家p_Author_code不存在,则提示一个错误信息,并退出该存储过程
IFSQL%NOTFOUNDTHEN
--准备长工资的作家不存在时,提示一个错误信息
RAISE_APPLICATION_ERROR(-20001,'没有代码为'|| p_Author_code ||'的作家存在');
ELSE
NULL;
ENDIF;
COMMIT;
ENDSalaryAdd;
5、异常处理
1)异常处理部分包含着对异常的处理语句。当一个异常相应的错误发生导致这个异常产生时,异常处理语句被执行。异常处理部分的语法如下:
EXCEPTION
WHEN exception_name1OR exception_name2 THEN
Sequence_of_statements1;
WHEN exception_name THEN
Sequence_of_statements2; ………………………………………………….
WHEN OTHERS THEN
Sequence_of_statement3;
说明如下:
(1)exception_name:异常的名字
(2)OTHERS语句:它是对应前面WHEN子句中未出现的所有错误
(3)Sequence_of_statements:是对exception_name异常或其它的所有异常(OTHERS)进行处理的语句
(4)一条异常处理语句可以处理多个异常,只要在WHEN子句中加由OR分隔的多个异常名即可
例如:下列这个例子
DECLARE
e_TooSmallSalary EXCEPTION;
v_CurrentSalary NUMBER(8,2);
v_SmallSalary NUMBER(8,2)DEFAULT100;
BEGIN
SELECT salary INTO v_CurrentSalary FROM auths
WHERE author_code ='A00002';
IF v_CurrentSalary < v_SmallSalary THEN
RAISE e_TooSmallSalary;
ELSE
NULL;
ENDIF;
EXCEPTION
WHEN e_TooSmallSalary THEN
UPDATE auths SET salary=500WHERE author_code='A0002';
WHENOTHERSTHEN
ROLLBACK;
END;
注意如下 :
(1)如果块中的异常没有被处理,则该块会带着未处理的异常返回调用它的程序,这会导致调用它的程序出错,例如:在存储过程中出现异常时,则存储过程的OUT参数将得不到返回值
(2)最好在块的最外层使用OTHERS子句来处理块中所有未处理的异常,这样就可以确保所有的错误都能被发现和处理
(3)在块的三个部分(定义部分、可执行部分、异常处理部分)都可能产生异常,但大多数情况下是在可执行部分产生异常
2)处理“可执行部分”产生的异常
(1)第一种:异常e_exc在内部块中产生并被处理,然后控制权转到块外调用环境中
(2)第二种:在内部块中产生的异常e_exc2(注意:该异常是在外部中被声明)在本块中没有对应的处理语句,而在外部块中有e_exc2的处理语句,则e_exc2被传递到处部块中处理
3)处理“定义部分”产生的异常
注意:如果块的"定义部分"的一个赋值语句产生了异常,即使用在当前块的异常处理部分中有处理该异常的处理语句,也不去执行,而是立刻被传递到外部块中。当异常传递到外部块中以后,按照处理可执行部分中产生的异常一样去处理该异常
例如:在定义部分产生了一个名为VALUE_ERROR异常,块中的OTHERS处理语句并没有处理该异常,该异常被传递到块外
4)处理“异常部分”产生的异常
说明:在异常处理语句中也可以产生异常,这个异常可以通过RAISE语句产生,或是由于出现一个运行错误而产生。这两种情况下产生的异常都被立刻传递到块外,这与定义部分产生的异常一样(因为异常部分每一次只能有一个异常被处理,当一个异常处理时,产生了另一个异常,而一次不能同时处理多个异常,所以将异常处理部分产生的异常传递到块外
例如:在内部块的异常处理部分产生了异常e_exc2,同时在内部块中有处理异常e_exc2的语句,但异常e_exc2被传递到外部块中,在外部块中被处理,而且外部块成功结束
说明:在异常处理语句中,RAISE语句还可以不带参数地使用。如果RAISE语句不带参数,则当前的异常被传递到块外
例如:下面块中的异常e_exc执行完处理它的语句后,由不带参数的RAISE语句将其传递到块外
5)SQLCODE和SQLERRM函数说明:由于OTHERS子句处理WHEN子句没有处理的异常,所以在OTHERS子句中处理的异常是未知的。我们可以使用SQLCODE和SQLERRM函数来确定异常对应的错误代码和信息
(1)SQLCODE:返回异常对应原错误代码
(2)SQLERRM:返回的是对应的错误信息
下表是异常和对应的SQLCODE、SQLERRM值,如下
注意:
(1)如果使用EXCEPTION_INIT预编译指令声明与Oracle错误相连的自定义异常,则SQLCODE和SQLERRM返回对应的Oracle错误代码和相应的错误信息,而不是返回“+1”和“User-Defined Exception"
(2)如果要在SQL语句中使用SQLCODE和SQLERRM,则一定要先把它们的值赋给局部变量,然后再将这些局部变量用在SQL语句中(因为这些函数是过程性的,不能直接用在SQL语句中)
例如:一个带有完整的OTHERS异常处理语句的PL/SQL块
DECLARE
e_TooSmallSalary EXCEPTION;
v_CurrentSalary NUMBER(8,2);
v_SmallSalary NUMBER(8,2)DEFAULT100;
--获得错误消息代码的变量
v_ErrorCode NUMBER;
--获得错误消息文本的变量
v_ErrorText VARCHAR2(200);
BEGIN
SELECT salary INTO v_CurrentSalary FROM auths
WHERE author_code ='A00002';
IF v_CurrentSalary < v_SmallSalary THEN
RAISE e_TooSmallSalary;
ELSE
NULL;
ENDIF;
EXCEPTION
WHEN e_TooSmallSalary THEN
DELETEFROM auths WHERE author_code ='A00002';
WHENOTHERSTHEN
v_ErrorCode :=SQLCODE;
v_ErrorText :=SUBSTR(SQLERRM,1,200);
--显示错误代码
DBMS_OUTPUT.PUT_LINE(v_ErrorCode);
--显示错误文本
DBMS_OUTPUT.PUT_LINE(v_ErrorText);
END;