⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 第七章 控制pl-sql错误 - pl-sql用户指南与参考 - whatiswhat.htm

📁 sql初学者不错的教程
💻 HTM
📖 第 1 页 / 共 5 页
字号:
                        src="第七章 控制PL-SQL错误 - PL-SQL用户指南与参考 - whatiswhat.files/o_7-1.gif"> 

                        <P>异常可以跨作用域传递,也就是说,它能够超越声明它的块的范围而存在。如下例所示: </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>BEGIN</STRONG><BR>&nbsp;&nbsp;...<BR>&nbsp;&nbsp;<STRONG>DECLARE</STRONG>&nbsp;&nbsp;&nbsp;<EM>--&nbsp;sub-block&nbsp;begins</EM><BR>&nbsp;&nbsp;&nbsp;&nbsp;past_due&nbsp;&nbsp;&nbsp;<STRONG>EXCEPTION</STRONG>;<BR>&nbsp;&nbsp;<STRONG>BEGIN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;...<BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>IF</STRONG>&nbsp;...&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>RAISE</STRONG>&nbsp;past_due;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>END</STRONG>&nbsp;<STRONG>IF</STRONG>;<BR>&nbsp;&nbsp;<STRONG>END</STRONG>;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;sub-block&nbsp;ends</EM><BR><STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;...<BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;<STRONG>OTHERS</STRONG>&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>ROLLBACK</STRONG>;<BR><STRONG>END</STRONG>; 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P>因为异常past_due所在的块并没有专门针对它的处理程序,所以异常就被传递到封闭块。但是,按照作用域规则,封闭块是不能引用子块声明的异常。所以,只有OTHERS处理器才能捕获到这个异常。如果没有用户定义异常的处理程序,调用这个程序就会得到下面的错误: 
                        </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap>ORA-06510:&nbsp;PL/SQL:&nbsp;unhandled&nbsp;user-defined&nbsp;exception 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P class=title1>七、重新抛出PL/SQL异常</P>
                        <P>有时我们需要重新抛出捕获到异常,也就是说,我们想在本地处理之后再把它传递到封闭块。比如,在异常发生的时候,我们可能需要回滚事务,然后在封闭块中写下错误日志。 
                        </P>
                        <P>要重新抛出异常,只要在本地处理程序中放置一个RAISE语句即可,示例如下: </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>DECLARE</STRONG><BR>&nbsp;&nbsp;out_of_balance&nbsp;&nbsp;&nbsp;<STRONG>EXCEPTION</STRONG>;<BR><STRONG>BEGIN</STRONG><BR>&nbsp;&nbsp;...<BR>&nbsp;&nbsp;<STRONG>BEGIN</STRONG>&nbsp;&nbsp;&nbsp;<EM>--&nbsp;sub-block&nbsp;begins</EM><BR>&nbsp;&nbsp;&nbsp;&nbsp;...<BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>IF</STRONG>&nbsp;...&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>RAISE</STRONG>&nbsp;out_of_balance;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;raise&nbsp;the&nbsp;exception</EM><BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>END</STRONG>&nbsp;<STRONG>IF</STRONG>;<BR>&nbsp;&nbsp;<STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;out_of_balance&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;handle&nbsp;the&nbsp;error</EM><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>RAISE</STRONG>;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;reraise&nbsp;the&nbsp;current&nbsp;exception</EM><BR>&nbsp;&nbsp;<STRONG>END</STRONG>;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;sub-block&nbsp;ends</EM><BR><STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;out_of_balance&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;handle&nbsp;the&nbsp;error&nbsp;differently</EM><BR>&nbsp;&nbsp;&nbsp;&nbsp;...<BR><STRONG>END</STRONG>; 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P>如果在RAISE语句中省略了异常名称——只允许在异常处理程序中这样做——程序就会把当前的异常重新抛出。</P>
                        <P class=title1>八、处理PL/SQL异常</P>
                        <P>异常抛出时,PL/SQL块或子程序的正常执行就会停止,控制权转到块或子程序的异常处理部分,语法如下: 
                        </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;exception_name1&nbsp;<STRONG>THEN</STRONG>&nbsp;&nbsp;&nbsp;<EM>--&nbsp;handler</EM><BR>&nbsp;&nbsp;&nbsp;&nbsp;sequence_of_statements1<BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;exception_name2&nbsp;<STRONG>THEN</STRONG>&nbsp;&nbsp;&nbsp;<EM>--&nbsp;another&nbsp;handler</EM><BR>&nbsp;&nbsp;&nbsp;&nbsp;sequence_of_statements2<BR>&nbsp;&nbsp;&nbsp;&nbsp;...<BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;<STRONG>OTHERS</STRONG>&nbsp;<STRONG>THEN</STRONG>&nbsp;&nbsp;&nbsp;<EM>--&nbsp;optional&nbsp;handler</EM><BR>&nbsp;&nbsp;&nbsp;&nbsp;sequence_of_statements3<BR><STRONG>END</STRONG>; 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P>为捕获抛出的异常,我们需要编写异常处理程序。每个处理程序都由一个WHEN子句和语句序列组成。这些语句执行完毕后,块或子程序就会结束,控制权不再返回异常被抛起的地方。换句话说,也就是我们不能再次返回异常发生的地方继续执行我们的程序。 
                        </P>
                        <P>可选的OTHERS处理器总是块或子程序的最后一个处理程序,它可以用于捕获所有的未命名异常。因此,块或子程序只能有一个OTHERS处理器。如下例所示,OTHERS处理器能够保证所有的异常都会被控制: 
                        </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;...&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;handle&nbsp;the&nbsp;error</EM><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;...&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;handle&nbsp;the&nbsp;error</EM><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;<STRONG>OTHERS</STRONG>&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;handle&nbsp;all&nbsp;other&nbsp;errors</EM><BR><STRONG>END</STRONG>; 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P>如果我们想让两个或更多的异常执行同样的语句序列,只需把异常名称用关键字OR隔开,放在同一个WHEN子句中即可,如下例所示: 
                        </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;over_limit&nbsp;<STRONG>OR</STRONG>&nbsp;under_limit&nbsp;<STRONG>OR</STRONG>&nbsp;VALUE_ERROR&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;<EM>--&nbsp;handle&nbsp;the&nbsp;error</EM> 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P>只要在WHEN子句的异常列表中有一项与被抛出异常相匹配,相关的语句序列就会被执行。关键字OTHERS不能出现在异常名称列表中;它只能单独使用。我们可以有任意数量的异常处理程序,而且每个处理程序都与一个异常列表及其对应的语句序列相关联。但是,异常名称只能在块或子程序的异常处理部分出现一次。 
                        </P>
                        <P>变量作用范围的规则在这里也同样适用,所以我们可以在异常处理程序中引用本地或全局变量。但是,当游标FOR循环中有异常抛出时,游标就会在异常处理程序调用之前被隐式地关闭。因此,显式游标的属性值在异常处理程序中就不再可用了。 
                        </P>
                        <P class=title2>1、声明中控制异常</P>
                        <P>如果在声明时使用了错误的初始化表达式也有可能引发异常。例如,下面的声明就是因常量credit_limit不能存储超过999的数字而抛出了异常: 
                        </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>DECLARE</STRONG><BR>&nbsp;&nbsp;credit_limit&nbsp;<STRONG>CONSTANT</STRONG>&nbsp;<STRONG>NUMBER</STRONG>(3)&nbsp;:=&nbsp;5000;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;raises&nbsp;an&nbsp;exception</EM><BR>&nbsp;&nbsp;<STRONG>BEGIN</STRONG><BR>&nbsp;&nbsp;...<BR><STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;<STRONG>OTHERS</STRONG>&nbsp;<STRONG>THEN</STRONG>&nbsp;&nbsp;&nbsp;<EM>--&nbsp;cannot&nbsp;catch&nbsp;the&nbsp;exception</EM><BR>&nbsp;&nbsp;...<BR><STRONG>END</STRONG>; 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P>当前块中的处理程序并不能捕获到抛出的异常,这是因为声明时抛出的异常会被立即传递到最近的封闭块中去。 
</P>
                        <P class=title2>2、异常句柄中控制异常</P>
                        <P>在一个块或子程序中,一次只能有一个异常被激活。所以,一个被异常处理程序抛出的异常会被立即传递到封闭块,在那儿,封闭块会为它查找新的处理程序。从那一刻起,异常传递才开始正常化。参考下面的例子:</P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;INVALID_NUMBER&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>INSERT</STRONG>&nbsp;<STRONG>INTO</STRONG>&nbsp;...&nbsp;&nbsp;&nbsp;<EM>--&nbsp;might&nbsp;raise&nbsp;DUP_VAL_ON_INDEX</EM><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;DUP_VAL_ON_INDEX&nbsp;<STRONG>THEN</STRONG>&nbsp;...&nbsp;&nbsp;&nbsp;<EM>--&nbsp;cannot&nbsp;catch&nbsp;the&nbsp;exception</EM><BR><STRONG>END</STRONG>; 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P class=title2>3、异常分支</P>
                        <P>GOTO语句不能跳转到异常控制程序。同样,GOTO语句也不能从异常控制程序跳转到当前块。例如,下面的GOTO语句就是非法的: 
                        </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>DECLARE</STRONG><BR>&nbsp;&nbsp;pe_ratio&nbsp;&nbsp;&nbsp;<STRONG>NUMBER</STRONG>&nbsp;(3,&nbsp;1);<BR><STRONG>BEGIN</STRONG><BR>&nbsp;&nbsp;<STRONG>DELETE</STRONG>&nbsp;<STRONG>FROM</STRONG>&nbsp;stats<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>WHERE</STRONG>&nbsp;symbol&nbsp;=&nbsp;<EM>'xyz'</EM>;<BR>&nbsp;&nbsp;<STRONG>SELECT</STRONG>&nbsp;price&nbsp;/&nbsp;NVL&nbsp;(earnings,&nbsp;0)<BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>INTO</STRONG>&nbsp;pe_ratio<BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>FROM</STRONG>&nbsp;stocks<BR>&nbsp;&nbsp;&nbsp;<STRONG>WHERE</STRONG>&nbsp;symbol&nbsp;=&nbsp;<EM>'xyz'</EM>;<BR><BR>&nbsp;&nbsp;&lt;&lt;my_label&gt;&gt;<BR>&nbsp;&nbsp;<STRONG>INSERT</STRONG>&nbsp;<STRONG>INTO</STRONG>&nbsp;stats&nbsp;(symbol,&nbsp;ratio)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>VALUES</STRONG>&nbsp;(<EM>'xyz'</EM>,&nbsp;pe_ratio);<BR><STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;ZERO_DIVIDE&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;pe_ratio&nbsp;&nbsp;:=&nbsp;0;<BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>GOTO</STRONG>&nbsp;my_label;&nbsp;&nbsp;&nbsp;<EM>--&nbsp;illegal&nbsp;branch&nbsp;into&nbsp;current&nbsp;block</EM><BR><STRONG>END</STRONG>; 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P>但是,GOTO语句可以从一个异常控制程序中跳转到一个封闭块。</P>
                        <P class=title2>4、获取错误代号与消息:SQLCODE和SQLERRM</P>
                        <P>在异常处理程序中,我们可以使用内置函数SQLCODE和SQLERRM来查出到底发生了什么错误,并能够获取相关的错误信息。对于内部异常来说, 
                        SQLCODE会返回Oracle错误编号。SQLCODE返回的总是一个负数,除非发生的Oracle错误是没有找到数据,这时返回的是+100。 
                        SQLERRM会返回对应的错误消息。消息是以Oracle错误编号开头的。</P>
                        <P>如果我们没有使用编译指令EXCEPTION_INIT把异常与编号关联的话,SQLCODE和SQLERRM就会分别返回+1和消息"User- 
                        Defined 
                        Exception"。Oracle错误消息最大长度是512个字符,其中包括错误编号、嵌套消息和具体表和字段的名称。 
                        </P>
                        <P>如果没有异常抛出,SQLCODE返回0,SQLERRM返回消息"ORA-0000: normal, 
                        successful completion"。 </P>
                        <P>我们可以把错误编号传递给SQLERRM,让它返回对应的错误消息。但是,一定要保证我们传递给SQLERRM的错误编号是负数。下例中,我们把一个正数传递给SQLERRM,结果就不是我们想要的那样的了: 
                        </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>DECLARE</STRONG><BR>&nbsp;&nbsp;err_msg&nbsp;&nbsp;&nbsp;<STRONG>VARCHAR2</STRONG>(100);<BR><STRONG>BEGIN</STRONG><BR>&nbsp;&nbsp;<EM>/*&nbsp;Get&nbsp;all&nbsp;Oracle&nbsp;error&nbsp;messages.&nbsp;*/</EM><BR>&nbsp;&nbsp;<STRONG>FOR</STRONG>&nbsp;err_num&nbsp;<STRONG>IN</STRONG>&nbsp;1&nbsp;..&nbsp;9999&nbsp;<STRONG>LOOP</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;err_msg&nbsp;&nbsp;&nbsp;&nbsp;:=&nbsp;<STRONG>SQLERRM</STRONG>(err_num);&nbsp;&nbsp;&nbsp;<EM>--&nbsp;wrong;&nbsp;should&nbsp;be&nbsp;-err_num</EM><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>INSERT</STRONG>&nbsp;<STRONG>INTO</STRONG>&nbsp;ERRORS<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>VALUES</STRONG>&nbsp;(err_msg);<BR>&nbsp;&nbsp;<STRONG>END</STRONG>&nbsp;<STRONG>LOOP</STRONG>;<BR><STRONG>END</STRONG>; 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P>把正数传给SQLERRM时,如果传递的是+100,返回的结果是"no data 
                        found",其他情况总是会返回消息"user-defined 
                        exception"。把0传递给SQLERRM,就会返回消息"normal, successful 
                        completion"。 </P>
                        <P>我们不能直接在SQL语句中使用SQLCODE或SQLERRM。我们必须先把它们的值赋给本地变量,然后再在SQL中使用变量,如下例所示: 
                        </P>
                        <BLOCKQUOTE>
                          <TABLE>
                            <TBODY>
                            <TR>
                              <TD 
                                noWrap><STRONG>DECLARE</STRONG><BR>&nbsp;&nbsp;err_num&nbsp;&nbsp;&nbsp;<STRONG>NUMBER</STRONG>;<BR>&nbsp;&nbsp;err_msg&nbsp;&nbsp;&nbsp;<STRONG>VARCHAR2</STRONG>(100);<BR><STRONG>BEGIN</STRONG><BR>&nbsp;&nbsp;...<BR><STRONG>EXCEPTION</STRONG><BR>&nbsp;&nbsp;<STRONG>WHEN</STRONG>&nbsp;<STRONG>OTHERS</STRONG>&nbsp;<STRONG>THEN</STRONG><BR>&nbsp;&nbsp;&nbsp;&nbsp;err_num&nbsp;&nbsp;&nbsp;&nbsp;:=&nbsp;<STRONG>SQLCODE</STRONG>;<BR>&nbsp;&nbsp;&nbsp;&nbsp;err_msg&nbsp;&nbsp;&nbsp;&nbsp;:=&nbsp;SUBSTR(<STRONG>SQLERRM</STRONG>,&nbsp;1,&nbsp;100);<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>INSERT</STRONG>&nbsp;<STRONG>INTO</STRONG>&nbsp;ERRORS<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<STRONG>VALUES</STRONG>&nbsp;(err_num,&nbsp;err_msg);<BR><STRONG>END</STRONG>; 
                              </TD></TR></TBODY></TABLE></BLOCKQUOTE>
                        <P>字符串函数SUBSTR可以保证用SQLERRM为err_msg赋值时不会引起VALUE_ERROR异常。函数SQLCODE和SQLERRM在OTHERS异常处理程序中特别有用,因为它们能让我们知道哪个内部异常被抛出。 
                        </P>
                        <P>注意:在使用编译指示RESTRICT_REFERENCES判断存储函数的纯度时,如果函数调用了SQLCODE和SQLERRM,我们就不能指定约束为WNPS和RNPS了。 
                        </P>
                        <P class=title2>5、捕获未控制异常</P>
                        <P>记住,如果被抛出的异常找不到合适的异常控制程序,PL/SQL会向主环境抛出一个未捕获的异常错误,然后由主环境决定如何处理。例如,在Oracle预编译程序环境中,任何一个执行失败的SQL语句或PL/SQL块所涉及到的改动都会被回滚。 
                        </P>
                        <P>未捕获也能影响到子程序。如果我们成功地从子程序中退出,PL/SQL就会把值赋给OUT参数。但是,如果我们因未捕获异常而退出程序,PL/SQL就不会为OUT参数进行赋值。同样,如果一个存储子程序因异常而执行失败,PL/SQL也不会回滚子程序所做的数据变化。 
                        </P>
                        <P>我们可以在每个PL/SQL程序的顶级使用OTHERS句柄来捕获那些没有被子程序捕捉到的异常。 </P>
                        <P class=title1>九、PL/SQL错误控制技巧</

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -