天天看點

PL/SQL6——異常處理(Exception)

 Exception是一種PL/SQL辨別符,當運作的PL/SQL塊出現錯誤或警告,則會觸發異常處理。為了提高程式的健壯性,可以在PL/SQL塊中引

入異常處理部分,進行捕捉異常,并根據異常出現的情況進行相應的處理。

一、異常的類型 

    ORACLE異常分為兩種類型:系統異常、自定義異常。

    其中系統異常又分為:預定義異常和非預定義異常。

    1.預定義異常

        ORACLE定義了他們的錯誤編号和異常名字,常見的預定義異常處理Oracle常見的錯誤

        NO_DATA_FOUND          SELECT ... INTO ... 時,沒有找到資料

        DUL_VAL_ON_INDEX       試圖在一個有惟一性限制的列上存儲重複值

        CURSOR_ALREADY_OPEN    試圖打開一個已經打開的遊标

        TOO_MANY_ROWS          SELECT ... INTO ... 時,查詢的結果是多值

        ZERO_DIVIDE            零被整除

    2.非預定義異常

        ORACLE為它定義了錯誤編号,但沒有定義異常名字。我們使用的時候,先聲名一個異常名,

        通過僞過程PRAGMA EXCEPTION_INIT,将異常名與錯誤号關聯起來。

    3.自定義異常

        程式員從我們業務角度出發,制定的一些規則和限制。

二、異常處理

    PL/SQL中,異常處理按個步驟進行:

        定義異常

        抛出異常

        捕獲及處理異常

    a.定義異常

        exception_name EXCEPTION;

    b.抛出異常

        RAISE exception_name

    c.捕獲及處理異常

        EXCEPTION

            WHEN e_name1 [OR e_name2 ... ] THEN

                statements;

            WHEN e_name3 [OR e_name4 ... ] THEN

                ......

            WHEN OTHERS THEN

        END;

        /

三、異常處理示例   

    1.預定義異常的例子

        --示範一個整除的異常

            DECLARE

                v_n1 NUMBER:=50;

                v_n2 NUMBER:=0;

                v_n3 NUMBER;

            BEGIN

                v_n3:=v_n1/v_n2;

                DBMS_OUTPUT.PUT_LINE('v_n3='||v_n3);

            EXCEPTION

                WHEN ZERO_DIVIDE  THEN

                    DBMS_OUTPUT.PUT_LINE('v_n2 don't is 0');       

            END;

            /

        --示範VALUE_ERROR(錯誤号ORA-06502)

            scott@ORCL> declare

              2  v_ename varchar2(3);

              3  begin

              4      select ename into v_ename from emp where empno=&eno;

              5      dbms_output.put_line(v_ename);

              6  exception

              7      when value_error then

              8      dbms_output.put_line('variable datatype length is small');

              9  end;

             10  /

            Enter value for eno: 7788

            old   4:     select ename into v_ename from emp where empno=&eno;

            new   4:     select ename into v_ename from emp where empno=7788;

            variable datatype length is small

            PL/SQL procedure successfully completed.       

        --示範TOO_MANY_ROWS(對應Oracle錯誤号ORA-01422)

              2  v_ename emp.ename%type;

              4      select ename into v_ename from emp where deptno=&dno;

              5  exception

              6      when too_many_rows then

              7      dbms_output.put_line('Too many rows are returned');

              8  end;

              9  /

            Enter value for dno: 10

            old   4:     select ename into v_ename from emp where deptno=&dno;

            new   4:     select ename into v_ename from emp where deptno=10;

            Too many rows are returned

            PL/SQL procedure successfully completed.           

        非預定義異常使用的基本過程

            a.定義一個異常名

            b.将異常名與異常編号相關聯

            c.在異常處理部分捕捉并處理異常

        DECLARE

            e_deptid EXCEPTION;

            PRAGMA EXCEPTION_INIT(e_deptid,-2292);

        BEGIN

            DELETE FROM DEPT WHERE DEPTNO=10;

            WHEN e_deptid THEN

                DBMS_OUTPUT.PUT_LINE('at sub table have record!');

            e_emp EXCEPTION;

            PRAGMA EXCEPTION_INIT(e_emp,-2291);

            INSERT INTO EMP(empno,deptno) VALUES (7935,60);

            WHEN e_emp THEN

                DBMS_OUTPUT.PUT_LINE('60 at table dept not exist');

        /  

        自定義異常與Oracle錯誤沒有任何關系,有開發人員為特定情況所定義的例外

        下面的例子中當輸入empno為時,盡管該雇員編号不存在,但PL/SQL代碼并未給出适當的提示

              2  e_integrity exception;

              3  pragma exception_init(e_integrity,-2291);

              4  begin

              5      update emp set deptno=&dno where empno=&eno;

              7      when e_integrity then

              8      dbms_output.put_line('The dept is not exists');

            Enter value for dno: 20

            Enter value for eno: 1111

            old   5:     update emp set deptno=&dno where empno=&eno;

            new   5:     update emp set deptno=20 where empno=1111;

            PL/SQL procedure successfully completed.

        針對上述情況,可以使用自定義異常處理。

        對于自定義的異常處理需要顯示的觸發,其步驟如下

            a.定義異常(在declare部分進行定義)

            b.顯示觸發異常(在執行BEGIN部分觸發異常,使用RAISE語句)

            c.引用異常(在EXCEPTION部分捕捉并處理異常)

        --對上面的例子,使用自定義異常來處理,代碼如下:

              4  e_no_employee exception;

              5  begin

              6      update emp set deptno=&dno where empno=&eno;

              7      if sql%notfound then

              8          raise e_no_employee;

              9      end if;

             10  exception

             11      when e_integrity then

             12          dbms_output.put_line('The dept is not exists');

             13      when e_no_employee then

             14          dbms_output.put_line('The employee is not exists');

             15  end;

             16  /

            Enter value for eno: 1234

            old   6:     update emp set deptno=&dno where empno=&eno;

            new   6:     update emp set deptno=20 where empno=1234;

            The employee is not exists

        --下面的例子中,如果插入的工資少于,就抛出異常

                v_sal emp.sal%TYPE;

                v_id emp.empno%TYPE;

                e_sal EXCEPTION;      --定義異常

                v_id:=&inputid;

                v_sal:=&inputsal;

                INSERT INTO emp (empno,sal) VALUES (v_id,v_sal);

                IF v_sal<700 THEN

                    RAISE e_sal;      --捕捉異常

                END IF;

            EXCEPTION                 --處理異常

                WHEN e_sal THEN

                    ROLLBACK;

                    DBMS_OUTPUT.PUT_LINE('Sal must be more than 700'); 

四、使用異常函數處理異常           

    1.SQLCODE與SQLERRM函數

        函數SQLCODE用于取得Oracle錯誤号

        函數SQLERRM用于取得與錯誤号對應的相關錯誤消息

            scott@ORCL> get /u01/bk/scripts/sqlcode_errm.sql

              1  undef v_sal

              2  declare

              3      v_ename emp.ename%type;

              4      begin

              5          select ename into v_ename from emp

              6          where sal=&&v_sal;

              7          dbms_output.put_line('Employee Name:'||v_ename);

              8  exception

              9      when no_data_found then

             10          dbms_output.put_line('The employee is not exists for salary '||&v_sal);

             11      when others then

             12          dbms_output.put_line('Error No:'||SQLCODE);

             13          dbms_output.put_line(SQLERRM);

             14* end;

            scott@ORCL> start /u01/bk/scripts/sqlcode_errm.sql

            Enter value for v_sal: 1600

            old   5:         where sal=&&v_sal;

            new   5:         where sal=1600;

            old   9:         dbms_output.put_line('The employee is not exists for salary '||&v_sal);

            new   9:         dbms_output.put_line('The employee is not exists for salary '||1600);

            Error No:-1422

            ORA-01422: exact fetch returns more than requested number of rows

            PL/SQL procedure successfully completed.   

    2.RAISE_APPLICATION_ERROR

        是将應用程式專有的錯誤從伺服器端轉達到用戶端應用程式(其他機器上的SQLPLUS或者其他前台開發語言)。

        該函數用于在PL/SQL中定義錯誤消息,且隻能在資料庫端的子程式中使用(存儲過程、函數、包、觸發器),不能在匿名塊和用戶端的

        子程式中使用

        使用方法

            RAISE_APPLICATION_ERROR(error_number,message[,{true | false}]);

            該函數内的錯誤代碼和内容,都是用使用者自定義

            error_number:用于定義錯誤号,且錯誤号從-20000 到-20999 之間,以避免與ORACLE 的任何錯誤代碼發生沖突。

            message:用于指定錯誤消息,且消息長度不能超過k,超出部分将被截取

            可選參數true,false:預設值為false,會替換先前的所有錯誤。當設定為true,則該錯誤會被放在先前錯誤堆棧中。

            scott@ORCL> get /u01/bk/scripts/sp_raise_comm.sql

              1  create or replace procedure raise_comm

              2  (v_no emp.empno%type,v_comm out emp.comm%type)

              3  as

              5       select comm into v_comm from emp where empno=v_no;

              6       if v_comm is null then

              7           raise_application_error(-20001,'It is no comm for this employee');

              8       end if;

              9  exception

             10       when no_data_found then

             11       dbms_output.put_line('The employee is not exist');

             12* end;

            scott@ORCL> start /u01/bk/scripts/sp_raise_comm.sql

            Procedure created.

            scott@ORCL> variable g_sal number;

            scott@ORCL> call raise_comm(7788,:g_sal);

            call raise_comm(7788,:g_sal)

                 *

            ERROR at line 1:

            ORA-20001: It is no comm for this employee

            ORA-06512: at "SCOTT.RAISE_COMM", line 7

            scott@ORCL> call raise_comm(7499,:g_sal);

            Call completed.

            scott@ORCL> print g_sal

                 G_SAL

            ----------

                   300     

五、PL/SQL編譯警告

    1.PL/SQL警告的分裂

        SEVERE: 用于檢查可能出現的不可預料結果或錯誤結果,例如參數的别名問題.

        PERFORMANCE: 用于檢查可能引起性能問題,如在INSERT操作是為NUMBER列提供了VARCHAR2類型資料.

        INFORMATIONAL: 用于檢查程式中的死代碼.

        ALL: 用于檢查所有警告.

    2.控制PL/SQL警告消息

        通過設定初始化參數PLSQL_WARNINGS來啟用在編譯PL/SQL子程式時發出警告消息,預設為DISABLE:ALL

        警告消息設定的級别

            系統級别

            會話級别

            ALTER PROCEDURE

            既可以激活或禁止所有警告類型,也可以激活或禁止特定消息号

            scott@ORCL> show parameter plsql%ings;

            NAME                                 TYPE        VALUE

            ------------------------------------ ----------- ------------------------------

            plsql_warnings                       string      DISABLE:ALL       

            scott@ORCL> ALTER SYSTEM SET PLSQL_WARNINGS='ENABLE:ALL' |'DISABLE:ALL'

            scott@ORCL> ALTER SESSION SET PLSQL_WARNINGS='ENABLE:PERFORMANCE' |'DISABLE:ALL'

            scott@ORCL> ALTER PROCEDURE usp COMPILE PLSQL_WARNINGS='ENABLE:PERFORMANCE';

            scott@ORCL> ALTER SESSION SET PLSQL_WARNINGS='ENABLE:SEVERE','DISABLE:PERFORMANCE','ERROR:06002';

    3.示範PL/SQL編譯告警

        a.檢測死代碼

            在下面的代碼中,ELSE子句永遠不會執行,應該避免出現類似的死代碼.

            從Oracle 10g開始,在編寫PL/SQL子程式之前開發人員可以激活警告檢查.

                scott@ORCL> get /u01/bk/scripts/dead_code.sql

                  1  CREATE OR REPLACE PROCEDURE dead_code AS

                  2     x number := 10;

                  3  BEGIN

                  4     IF x>0 THEN

                  5             x:=1;

                  6     ELSE             

                  7             x:=2;    --死代碼

                  8     END IF;

                  9* END dead_code;

                scott@ORCL> start /u01/bk/scripts/dead_code.sql

                scott@ORCL> alter session set plsql_warnings='enable:informational';

                scott@ORCL> show parameter plsql%ings

                NAME                                 TYPE        VALUE

                ------------------------------------ ----------- ------------------------------

                plsql_warnings                       string      ENABLE:INFORMATIONAL, DISABLE:

                                                                 PERFORMANCE, DISABLE:SEVERE

                scott@ORCL> alter procedure dead_code compile;

                SP2-0805: Procedure altered with compilation warnings          

                scott@ORCL> show errors;

                Errors for PROCEDURE DEAD_CODE:

                LINE/COL ERROR

                -------- -----------------------------------------------------------------

                4/6      PLW-06002: Unreachable code

                7/3      PLW-06002: Unreachable code

        b.檢測引起性能問題的代碼

            scott@ORCL> create or replace procedure update_sal

              2  (no number,salary varchar2)

              5      update emp set sal=salary where empno=no;

              6  end;

              7  /

            scott@ORCL> alter session set plsql_warnings='enable:performance';

            scott@ORCL> alter procedure update_sal compile;

            SP2-0805: Procedure altered with compilation warnings

            scott@ORCL> show errors

            Errors for PROCEDURE UPDATE_SAL:

            LINE/COL ERROR

            -------- -----------------------------------------------------------------

            5/24     PLW-07202: bind type would result in conversion

away from column