PL/SQL 错误的定义、抛出和处理

PL/SQL 异常的定义、抛出和处理
       何错误都叫异常,不期而遇时,执行单元必须交割控制权,这是一个肉包子打狗--有去无回的过程
       PL/SQL引擎对系统异常、用户异常或者应用异常皆是一视同仁
       异常仅是异常吗?有些异常如NO_DATA_FOUND,我们更愿意待他是逻辑的一个分支
       下面详细介绍异常的定义、抛出和处理
       ㈠ 定义异常
       
       隐姓埋名的异常仍是合法公民,但他们是可读性差、维护成本高的主要贡献者
       异常,必也正名乎!
       语法:
       必须在声明部分
       exception_name EXCEPTION;
       用法:
       ① 执行单元的RAISE语句
          RAISE exception_name;
       ② 异常处理单元的WHEN语句
          WHEN exception_name THEN;
       然而,当我们不得不和匿名异常打交道时,比如:
       ① 神秘的世外高人--匿名的系统异常
       ② RAISE_APPLICATION_ERROR抛出的应用异常
       这时,神器"EXCEPTION_INIT",江湖人称--编译命令,可助你为匿名异常正名。
       语法:
       必须在声明部分
       exception_name EXCEPTION;--该异常已经在同一个块或外层块、或者包的规范部分被命名
       PRAGMA EXCEPTION_INIT(exception_name,error_number);
       注释:
           --error_number的约束
           ● 不可使用-1403(是NO_DATA_FOUND的错误号之一)
           ● 不可使用0或100之外的任何正数
           ● 不可使用<-1000000的负数
           --最佳实践
           建议把EXCEPTION_INIT集中到一个包,比如:
           CREATE OR REPLACE PACKAGE pkg_think
           IS
             invalid_date EXCEPTION;
             PRAGMA EXCEPTION_INIT(invalid_date,-110);
           END pkg_think;
           我们就可以在任何程序中窃听这些异常,比如:
           WHEN pkg_think.invalid_date THEN ...
       对于异常的作用范围,Think有必要唠叨两下
       我们知道程序于外部代码都是透明的,是个"黑盒子"
       比如,我们可以在A过程抛出B异常,但是不能从调用A过程的程序C中抛出B异常
       
       ㈡ 抛出异常
       
       手工抛出异常的法子有二:
       ▼ RAISE语句
       ▼ RAISE_APPLICATION_ERROR过程
       先看RAISE
       语法:
       RAISE exception_name; OR
       RAISE package_name.exception_name; OR
       RAISE;
       注释:
       方式一,可用于抛出一个在当前块或包含当前块的外层块的自定义的异常
               也可抛出STANDARD包预定义的系统异常
       方式三,只可用在异常处理单元的WHEN语句,常用于异常的传播
       再瞧RAISE_APPLICATION_ERROR
       相比RAISE,此君的优点在于,可以给异常加注解
       语法:
       RAISE_APPLICATION_ERROR(error_number,error_comments);
       这里的error_number只能介于-20999~-20000这1000个错误号
       
       ㈢ 处理异常
       
       有两个术语扯下先:
       ▲ 异常处理单元:EXCEPTION关键字指示异常处理单元的开始
       ▲ 异常处理句柄:每个独立的WHEN...THEN...都是一个句柄
       一个异常处理单元可以有多个处理句柄,比如:
       EXCEPTION
         /*句柄一*/
         WHEN NO_DATA_FOUND
         THEN ...
         /*句柄二*/
         WHEN payment_overdue
         THEN ...
         /*句柄三*/
         WHEN OTHERS
         THEN ...
       END;
       WHEN语句只能根据异常的名称而无法依照错误号来捕获异常
       WHEN OTHERS语句是可选,而且必须是异常处理单元的最后一个处理句柄,如果没有这个语句,任何未处理异常立即传播到外层块
       在异常处理单元里包含WHEN OTHERS语句,可以捕获所有其他的未处理的异常
       WHEN OTHERS 和SQLCODE的结合使用可以替代EXCEPTION_INIT的功能
       不过要仔细WHEN OTHERS的使用,因为它很容易"吞掉"异常,向外层和用户隐瞒这些异常
       当然啦,我们通过OR操作符在一个单独的句柄中包含多个异常,无论这个异常来自系统还是应用
       然而,不可用AND操作符,因为任意时刻只能有一个异常抛出    
       在进一步讨论异常处理细节前,先看两个内置的异常处理函数
       ▲ SQLCODE:返回代码中最后一次抛出的错误号
       ▲ SQLERRM:返回某个错误号所对应的错误消息
       还有就是作用在异常身上的两个动作:
       --抛出:由执行单元→→异常处理单元(一般在同层块)
       --传播:由内层块→→外层块
       所有程序员自定义的异常的错误号都是1,错误消息都是"User Defined Exception"
       除非使用EXCEPTION_INIT为这个声明的异常指定不同错误号,以及RAISE_APPLICATION_ERROR给它指定不同的错误消息
       如果你使用了局部定义异常,就应该提供一个针对该错误名的异常句柄
       否则,未处理的异常传播到外层块,外层块皆视若无睹,很不上心呀
       开篇Think提到了,一旦交出控制权,就意味着彻底没有"皇帝轮流坐,明年到我家"的机会

       那么如何"屌丝逆袭"呢?

       PL/SQL  错误的定义、抛出和处理

       在执行单元抛出的异常总是在当前块中被处理--如果有匹配的句柄存在
       你可以通过给任何语句加上一个BEGIN,之后加上一个EXCEPTION单元和END语句构建一个"虚拟块"
       用这种方法,你可以通过在代码中构建匿名块的方式来控制异常引起的失败范围
       并且,Think还建议把所有要隔离的代码都放到单独的过程或者函数中去
       这样,有一个关键的好处在于,你可以从程序主线上隐藏掉BEGIN-EXCEPTION-END语句
       使程序更加容易阅读,理解,维护和在多种环境重复使用