PL/SQL异常处理

简介:

异常处理语句块结构

定义区:定义异常,如果使用预定义异常,则不用在定义区定义异常

执行区:可以显式地触发异常,也可以由PL/SQL引擎触发异常

异常处理区:只要在执行过程中出现了异常,那么执行区中后续的语句将立即停止执行,语句执行流程 跳转到异常处理区

异常处理结构示例

declare

e_duplicate_name exception;

v_ename emp.ename%TYPE;

v_newname emp.ename%TYPE:='史密斯';

begin

select ename into v_ename from emp where empno=7369;

if v_ename=v_newname

then

RAISE e_duplicate_name;

end if;

insert into emp values(7881,v_newname,'职员',NULL,TRUNC(SYSDATE),2000,200,20);

exception

when e_duplicate_name

then

dbms_output.put_line('不能插入重复的员工名称');

end;


对于未处理的异常,可以通过when others then来提供一个统一的异常处理

declare

e_duplicate_name exception;

v_ename emp.ename%TYPE;

v_newname emp.ename%TYPE:='史密斯';

begin

select ename into v_ename from emp where empno=7369;

if v_ename=v_newname

then

RAISE e_duplicate_name;

end if;

insert into emp values(7881,v_newname,'职员',NULL,TRUNC(SYSDATE),2000,200,20);

exception

when e_duplicate_name then

dbms_output.put_line('不能插入重复的员工名称');

when others then

dbms_output.put_line('异常编码:'||SQLCODE||'异常信息:'||SQLERRM);

end;


预定义异常使用示例

declare

v_tmpstr varchar2(10);

begin

v_tmpstr:='这是临时句子';

exception

when VALUE_ERROR

then

dbms_output.put_line('出现了VALUE_ERROR错误'

||'错误编号:'

||SQLCODE

||'错误名称:'

||SQLERRM

);

end;



自定义异常


declare

e_nodeptno exception;

begin

NULL;

end;


异常是一种错误的表现形式,而不是一个真正的变量,因此不能再赋值语句或SQL语句中使用异常,但是异常和变量的作用范围和规则是相同的。


作用域范围

自定义异常和变量的作用域和规则相同。

1.在同一个块中不能声明一个异常超过两次,但是可以再不同的块中声明相同的异常。但是可以再不同的块中声明两个相同的异常。

2.内层块定义的异常不可以被外层使用,内层可以调用外层定义的异常

3.如果在子块重新声明外部块中同名的异常,将覆盖外部块中的全局异常,使得子块不能引用外部块中的全局异常,但是可以再在标签块中声明相同的异常



使用EXCEPTION_INIT

如果一些异常并没有异常名称,此时在when子句中无法使用具体的异常名称,必须要使用others异常处理器进行捕捉,通过EXCEPTION_INIT编译指示,可以为这些不在预定义异常范围之类的异常添加名称。

编译指示是指能在编译期而非运行时进行处理的编译指令


编译指令EXCEPTION_INIT将告诉编译器,将异常名称和错误编号关联起来,使得在PLSQL语句块中可以使用名称来引用所有的内部异常。

declare

e_missingnull exception;

pragma exception_init(e_missingnull,-1400);

begin

insert into emp(empno)values(null);

commit;

exception

when e_missingnull then

dbms_output.put_line('触发了ORA-1400错误!'||SQLERRM);

rollback;

end;



使用RAISE_APPLICATION_ERROR

RAISE_APPLICATION_ERROR在子程序内部使用时,能够帮助用户从存储子程序中抛出用户自定义的错误信息。


declare or replace procedure registeremployee (

p_empno in emp.empno%TYPE,

p_ename in emp.ename%TYPE,

p_sal in emp.sal%TYPE,

p_deptno in emp.deptno%TYPE

as

v_empcount number;

begin

if p_empno is null

then

raise_application_error(-20000,'员工编号不能为空');

else

select count(*) into v_empcount from emp where empno=p_empno;

if v_empcount>0

then

raise_application_error(-20001,

'员工编号为:'||p_empno

||'的员工已存在!'

);

end if;

end if;

if p_deptno is null

then

raise_application_error(-20002,'部门编号不能为空');

end if;

insert into emp(empno,ename,sal,deptno)values(p_empno,p_ename,p_sal,p_deptno);

exception

when others then

raise_application_error(-20003,'插入数据时出现错误!异常编码:'

||SQLCODE

||'异常描述'

||SQLERRM

);

end;



输出  begin

RegisterEmployee(7779,'李明',2000,NULL);

      end;

ORA-20003:插入数据时出现错误!异常编码:-20002异常描述ORA-20002;



抛出异常

使用RAISE语句抛出异常示例

declare

e_nocomm exception;

v_comm number(10,2);

v_empno number(4):=&empno;

begin

select comm into v_comm from emp where empno=v_empno;

if v_comm is null

then

RAISE e_nocomm;

end if;

exception

when e_nocomm then

dbms_output.put_line('选择的员工没有提成!');

end;



处理异常

可以使用OR关键字进行分隔即可。

declare

e_nocomm exception;

v_comm  number(10,2);

v_empno number(4):=&empno;

begin

select comm into v_comm from emp where empno=v_empno;

if v_comm is null

then

RAISE e_nocomm;

end if;

exception

when e_nocomm or no_data_found then

dbms_output.put_line('出现了异常!');

when others then

dbms_output.put_line('任何其他未处理的异常');

end;


使用SQLCODE和SQLERRM

尽管一个异常在同一时刻只能抛出一次,但是实际上错误的消息文本可能包含来自多个异常的消息这是因为异常具有传递特性。

SQLCODE函数返回当前的错误编码。

SQLERRM函数返回消息文本


declare

e_nocomm exception;

v_comm number(10,2);

v_empno number(4):=&empno;

begin

select comm into v_comm from emp where empno=v_empno;

if v_comm is null

then

RAISE e_nocomm;

end if;

exception

when others then

dbms_output.put_line('错误编码:'||SQLCODE||'错误消息:'||SQLERRM);

end;



SQLERRM函数还可以接受一个负数的单精度参数,它将返回与该数字相关的文本,否则将返回当前异常的错误消息。

when others then               必须单独出现

dbms_output.put_line('错误编码:'||SQLCODE||'错误消息:'||SQLERRM(0));

想返回NO_DATA_FOUND的错误消息,可以传递错误消息号100



异常的传递


异常的传递是指当异常被抛出时,执行立即跳转到exception语句块中的异常处理器,异常处理器中查找是否匹配的异常,如果在当前的PL/SQL块或子程序中没有找到对应的异常处理器,那么这个异常会向其PLSQL块的外层或子程序的调用方传递,直到没有可以搜索到的块为止,这里PLSQL会向PLSQL引擎抛出一个未处理的异常。


执行时异常传递

执行时异常传递是指在PLSQL块的执行部分抛出的异常的传递机制,当在执行部分抛出异常后,PLSQL使用下面的机制来确定使用哪一个异常处理器。

1.如果当前PL/SQL的异常处理部分具有一个匹配的异常处理器,则执行当前块的异常处理器,成功完成语句块,然后将控制权传递到外层语句块

2.如果当前PLSQL块中没有匹配的异常处理器,则在当前块中抛出的异常会被传递到外层的异常处理器,然后执行外层语句块中的步骤1中的匹配操作

3.如果已经到了顶层,没有外层语句块了,则异常将被传递到调用环境。

例如

declare

e_outerexception exception;

e_innerexception exception;

e_threeexception exception;

begin

begin

RAISE e_innerexception;

RAISE e_outerexception;

RAISE e_threeexception;

exception

when e_innerexception;

end;

exception

when e_outerexception then

end;


1.首先搜寻内层块的异常处理区中的异常,在本地块中的e_innerexception异常具有一个异常处理器,因此该异常将在本地块中被处理

2.其他的两个异常将向外传递,传送给外层PL/SQL块的异常处理区,在外层的异常处理区中具有一个e_outerexception的异常处理器,因此该异常将在外层块中被捕捉并处理

3.异常e_threeexception并没有任何处理器,该异常将被传递到调用环境,由调用环境进行处理。比如SQL*PLUS会弹出一个异常。




声明时异常传递

示例

begin

declare

v_ename varchar2(2):='ABC';

begin

dbms_output.put_line(v_ename);

exception

when others then

dbms_output.put_line('产生了异常');

end;

exception

when others then

dbms_output.put_line('错误编号:'

||SQLCODE||'错误消息:'

||SQLERRM);

end;


在声明一个变量v_ename,并赋予了初始值ABC,这个赋值产生了ORA-06502异常,因为varchar2(2)不能包含ABC这3个字符。尽管在子块的exception区具有when others then 异常处理语句,但是声明区中抛出的异常并不会被本地块所捕获,而是向外层传递,被外层的异常处理区中的others异常处理器捕获,因此最终可以看到如下所示:

错误编号:-6502 错误消息:ORA-06502:PL/SQL:数字或值错误:字符串缓冲区太小。


异常处理器中的异常

declare

e_outerexception exception;

e_innerexception exception;

e_threeexception exception;

begin

begin

RAISE e_innerexception;

RAISE e_outerexception;

RAISE e_threeexception;

exception

when e_innerexception then

RAISE e_outerexception;

when e_outerexception then

when others then

end;

exception

when e_outerexception then

end;

在嵌套的内层PL/SQL块中,在执行区触发了3个异常,在异常处理区中捕获取e_innerexception异常,在处理该异常的处理器中,使用RAISE语句又触发了e_outerexception.可以看到即便在内层块中包含了e_outerexception的异常处理器,但是异常并不会在内存块中的处理器中被捕获,而是跳转到了外层的异常处理器进行处理。


重新抛出异常

declare

e_nocomm exception;

v_comm number(10,2);

v_empno number(4):=&empno;

begin

select comm into v_comm from emp where empno=v_empno;

if v_comm is null

then

RAISE e_nocomm;

end if;

exception 

when others then

dbms_output.put_line('错误编码:'||SQLCODE||'错误消息:'||SQLERRM(100));

RAISE;

end;

当e_nocomm触发后,会被others异常处理器处理,在该异常处理器中,首先输出了异常信息,然后调用RAISE语句将异常重新抛出。此时由于已经是最外层的语句块,异常将被传递到调用环境,因此SQLPLUS将触发异常。

可以看到当不为RASIE指定任何异常名称时,程序就会把当前的异常重新抛出。

这种重新抛出异常的方式只允许在异常程序中这样做。



异常处理准则

要实现从异常恢复,可以将异常包装在其自己的子块中,在子块中编写对应的异常控制程序,这样一旦在子块中有错误发生,子块内部的异常处理器就能捕获并处理异常。当子块结束时,就可以继续执行外层块中的下一条语句,因此如下

declare

e_duplicate_name exception;

v_ename emp.ename%TYPE;

v_newname emp.ename%TYPE:='史密斯';

begin

begin

select ename into v_ename from where empno=7369;

if v_ename=v_newname

then

RAISE e_duplicate_name;

end if;

exception

when e_duplicate_name then

v_newname:='刘大夏';

end;

insert into emp values(7881,v_newname,'职员',NULL,TRUNC(SYSDATE),2000,200,20);

exception

when others then

dbms_output.put_line('异常编码:'||SQLCODE||'异常信息:'||SQLERRM);

end;

可以看到,现在查询并检测同名的逻辑被封装到一个PL/SQL子块中,在子块中柏涵了异常处理语句,当异常触发时,将v_newname更改为另一个名字,从内层块中退出后,会继续执行insert语句,直到PL/SQL语句块终止。


获取异常抛出的位置

示例

declare

v_empno1 number(4):=&empno1;

v_empno2 number(4):=&empno2;

v_empno3 number(4):=&empno3;

v_sal1 number(10,2);

v_sal2 number(10,2);

v_sal3 number(10,2);

begin

select sal into v_sal1 from emp where empno=v_empno1;

select sal into v_sal2 from emp where empno=v_empno2;

select sal into v_sal3 from emp where empno=v_empno3;

exception

when NO_DATA_FOUND then

dbms_output.put_line('错误编号:'||SQLCODE||'错误消息:'SQLERRM);

end;

当触发了NO_DATA_FOUND异常时,如何知道是哪个select语句触发了异常?


方法一:使用递增的计数器标识SQL语句

declare

v_empno1 number(4):=&empno1;

v_empno2 number(4):=&empno2;

v_empno3 number(4):=&empno3;

v_sal1 number(10,2);

v_sal2 number(10,2);

v_sal3 number(10,2);

v_selectcounter number:=1;

begin

select sal into v_sal1 from emp where empno=v_empno1;

v_selectcounter:=2;

select sal into v_sal2 from emp where empno=v_empno2;

v_selectcounter:=3;

select sal into v_sal3 from emp where empno=v_empno3;

exception

when NO_DATA_FOUND then

dbms_output.put_line('错误编号:'||SQLCODE||'错误消息:'||SQLERRM||'触发异常的位置是:'|| v_selectcounter);

end;


方法二:将每一个select语句定义到一个子块中去,这样就可以知道哪个select语句触发了异常,当然这种方式将导致出现多个异常消息,因为没一条select语句都会被执行。

declare

v_empno1 number(4):=&empno1;

v_empno2 number(4):=&empno2;

v_empno3 number(4):=&empno3;

v_sal1 number(10,2);

v_sal2 number(10,2);

v_sal3 number(10,2);

begin

begin

select sal into v_sall from emp where empno=v_empno1;

exception

when NO_DATA_FOUND then

dbms_output.put_line('错误编号:'||SQLCODE||'错误消息:'||SQLERRM||'触发异常的位置是 1');

end;

begin

select sal into v_sal2 from emp where empno=v_empno2;

exception

when NO_DATA_FOUND then

dbms_output.put_line('错误编号:'||SQLCODE||'错误消息:'||SQLERRM||'触发异常的位置是 2');

end;

begin

select sal into v_sal3 from emp where empno=v_empno3;

exception

when NO_DATA_FOUND then

dbms_output.put_line('错误编号:'||SQLCODE||'错误消息:'||SQLERRM||'触发异常的位置 是3');

end;

exception

when NO_DATA_FOUND then

dbms_output.put_line('错误编号:'||SQLCODE||'错误消息:'||SQLERRM);

end;

方法三:可以使用DBMS_UTILITY.FORMAT_ERROR_BACKTRACE函数来获取错误位置,这个函数是oracle10g以后的版本提供的

declare

v_empno1 number(4):=&empno1;

v_empno2 number(4):=&empno2;

v_empno3 number(4):=&empno3;

v_sal1 number(10,2);

v_sal2 number(10,2);

v_sal3 number(10,2);

begin

select sal into v_sal1 from emp where empno=v_empno1;

select sal into v_sal2 from emp where empno=v_empno2;

select sal into v_sal3 from emp where empno=v_empno3;

exception

when NO_DATA_FOUND then

dbms_output.put_line('错误编号:'||SQLCODE||'错误消息:'||SQLERRM|| dbms_utility.format_backtrace);

end;

在发生错误时,oracle会为最近一次生成的异常设置一个栈,并跟踪它的传递过程。



异常与事务处理

抛出一个异常并不会终止一个事务,除非在异常处理器中显式地使用了ROLLBACK或者COMMIT语句,但是这里有一个问题,如果顶层的语句块存在一个未处理的异常,该异常将被传递到调用环境,那么事务将被服务端自动回滚。

如果想在异常发生后,不放弃事务,不进行回滚,重新再处理一次,可以按如下的3步来实现。

1.将事务放在一个子块中

2.把子块放入一个循环,重复执行事务

3.在开始事务之前标记一个保存点,如果事务执行成功,能提交事务并退出循环,如果执行失败,就将控制权交给异常处理程序,事务回滚到保存点,然后重新尝试执行事务。


在异常中重复事务执行代码

declare

e_duplicate_name EXCEPTION;

v_ename    emp.ename%TYPE;

v_newname        emp.ename%TYPE := '史密斯';

begin

loop

begin

savepoint 开始事务;

select ename into v_ename from emp where empno=7369;

if v_ename=v_newname

then

raise e_duplicate_name;

end if;

insert into emp values(7881,v_newname,'职员',NULL,TRUNC(SYSDATE),2000,200,20);

commit;

exit;

exception

when e_duplicate_name then

rollback to 开始事务;

v_newname:='刘大夏';

end;

end loop;

end;





      本文转自潘阔 51CTO博客,原文链接:http://blog.51cto.com/pankuo/1630253,如需转载请自行联系原作者




相关文章
|
7月前
|
SQL Oracle 关系型数据库
Oracle的PL/SQL隐式游标:数据的“自动导游”与“轻松之旅”
【4月更文挑战第19天】Oracle PL/SQL中的隐式游标是自动管理的数据导航工具,简化编程工作,尤其适用于简单查询和DML操作。它自动处理数据访问,提供高效、简洁的代码,但不适用于复杂场景。显式游标在需要精细控制时更有优势。了解并适时使用隐式游标,能提升数据处理效率,让开发更加轻松。
|
1月前
|
SQL 数据采集 监控
局域网监控电脑屏幕软件:PL/SQL 实现的数据库关联监控
在当今网络环境中,基于PL/SQL的局域网监控系统对于企业和机构的信息安全至关重要。该系统包括屏幕数据采集、数据处理与分析、数据库关联与存储三个核心模块,能够提供全面而准确的监控信息,帮助管理者有效监督局域网内的电脑使用情况。
22 2
|
4月前
|
SQL Oracle 关系型数据库
SQL与PL/SQL:数据库编程语言的比较
【8月更文挑战第31天】
81 0
|
6月前
|
SQL 数据库 Perl
PL/SQL中执行按钮变为灰色后如何恢复【已解决】
PL/SQL中执行按钮变为灰色后如何恢复【已解决】
529 0
|
7月前
|
SQL Oracle 关系型数据库
Oracle的PL/SQL游标属性:数据的“导航仪”与“仪表盘”
【4月更文挑战第19天】Oracle PL/SQL游标属性如同车辆的导航仪和仪表盘,提供丰富信息和控制。 `%FOUND`和`%NOTFOUND`指示数据读取状态,`%ROWCOUNT`记录处理行数,`%ISOPEN`显示游标状态。还有`%BULK_ROWCOUNT`和`%BULK_EXCEPTIONS`增强处理灵活性。通过实例展示了如何在数据处理中利用这些属性监控和控制流程,提高效率和准确性。掌握游标属性是提升数据处理能力的关键。
|
7月前
|
SQL Oracle 关系型数据库
Oracle的PL/SQL游标自定义异常:数据探险家的“专属警示灯”
【4月更文挑战第19天】Oracle PL/SQL中的游标自定义异常是处理数据异常的有效工具,犹如数据探险家的警示灯。通过声明异常名(如`LOW_SALARY_EXCEPTION`)并在满足特定条件(如薪资低于阈值)时使用`RAISE`抛出异常,能灵活应对复杂业务规则。示例代码展示了如何在游标操作中定义和捕获自定义异常,提升代码可读性和维护性,确保在面对数据挑战时能及时响应。掌握自定义异常,让数据管理更从容。
|
7月前
|
SQL Oracle 安全
Oracle的PL/SQL游标异常处理:从“惊涛骇浪”到“风平浪静”
【4月更文挑战第19天】Oracle PL/SQL游标异常处理确保了在数据操作中遇到的问题得以优雅解决,如`NO_DATA_FOUND`或`TOO_MANY_ROWS`等异常。通过使用`EXCEPTION`块捕获并处理这些异常,开发者可以防止程序因游标问题而崩溃。例如,当查询无结果时,可以显示定制的错误信息而不是让程序终止。掌握游标异常处理是成为娴熟的Oracle数据管理员的关键,能保证在复杂的数据环境中稳健运行。
|
7月前
|
SQL Oracle 关系型数据库
Oracle的PL/SQL中FOR语句循环游标的奇幻之旅
【4月更文挑战第19天】在Oracle PL/SQL中,FOR语句与游标结合,提供了一种简化数据遍历的高效方法。传统游标处理涉及多个步骤,而FOR循环游标自动处理细节,使代码更简洁、易读。通过示例展示了如何使用FOR循环游标遍历员工表并打印姓名和薪资,对比传统方式,FOR语句不仅简化代码,还因内部优化提升了执行效率。推荐开发者利用这一功能提高工作效率。
|
7月前
|
SQL Oracle 关系型数据库
Oracle的PL/SQL显式游标:数据的“私人导游”与“定制之旅”
【4月更文挑战第19天】Oracle PL/SQL中的显式游标提供灵活精确的数据访问,与隐式游标不同,需手动定义、打开、获取和关闭。通过DECLARE定义游标及SQL查询,OPEN启动查询,FETCH逐行获取数据,CLOSE释放资源。显式游标适用于复杂数据处理,但应注意SQL效率、游标管理及异常处理。它是数据海洋的私人导游,助力实现业务逻辑和数据探险。
|
7月前
|
SQL Oracle 安全
Oracle的PL/SQL异常处理方法:守护数据之旅的“魔法盾”
【4月更文挑战第19天】Oracle PL/SQL的异常处理机制是保障数据安全的关键。通过预定义异常(如`NO_DATA_FOUND`)和自定义异常,开发者能优雅地管理错误。异常在子程序中抛出后会向上传播,直到被捕获,提供了一种集中处理错误的方式。理解和善用异常处理,如同手持“魔法盾”,确保程序在面对如除数为零、违反约束等挑战时,能有效保护数据的完整性和程序的稳定性。