PL/SQL異常處理


As we all known,程序的錯誤一般分為兩類:編譯錯誤和運行時錯誤。其中運行時錯誤被稱為異常。PL/SQL語句塊中處理異常的部分即為異常處理部分。在異常處理部分,可以指定當特定異常發生時所采取的動作。

PL/SQL有兩種類型的異常:內置異常和用戶自定義異常。

其中,內置異常又分為預定義異常和非預定義異常。

一、內置異常

首先試舉一例來拋磚引玉。

DECLARE
   v_ename varchar2(10);
   v_empno number(4) := &v_empno;
BEGIN
   SELECT ename
     INTO v_ename
   FROM EMP
   WHERE empno = v_empno;
   DBMS_OUTPUT.PUT_LINE('Employee name is '||v_ename);
END;

該語句塊通過輸入員工的編號來得出員工的姓名。當輸入的員工編號存在時,輸出員工姓名,當員工編號不存在時,會有運行錯誤。如下所示:

SQL> /
Enter value for v_empno: 7788 
Employee name is SCOTT              -->> 輸入的員工編號存在,輸出員工姓名

PL/SQL procedure successfully completed.

SQL> /
Enter value for v_empno: 1234
DECLARE
*
ERROR at line 1:
ORA-01403: no data found            -->> 輸入的員工編號不存在,報運行時錯誤
ORA-06512: at line 5

 由此可見,編譯器無法檢測運行錯誤。為在程序中處理這種類型的錯誤,必須添加異常處理部分。異常處理部分的語法結構如下:

EXCEPTION
       WHEN EXCEPTION_NAME THEN
             ERROR-PROCESSING STATEMENTS;

在語句塊中,異常處理部分位於可執行部分之后,上例可修改如下:

DECLARE
   v_ename varchar2(10);
   v_empno number(4) := &v_empno;
BEGIN
   SELECT ename
     INTO v_ename
   FROM EMP
   WHERE empno = v_empno;
   DBMS_OUTPUT.PUT_LINE('Employee name is '||v_ename);
EXCEPTION
   WHEN NO_DATA_FOUND THEN
   DBMS_OUTPUT.PUT_LINE('There is no such employee');
END;

使用異常處理部分,可以使得程序能夠正常結束,而不是非正常終止。同時,輸出結果更加面向用戶,而不是編程人員。

上述NO_DATA_FOUND即為Oracle預定義異常。

下面列出一些常見的預定義異常

TOO_MANY_ROWS : SELECT INTO返回多行
INVALID_CURSOR :非法指針操作(關閉已經關閉的游標)
ZERO_DIVIDE :除數等於零
DUP_VAL_ON_INDEX :違反唯一性約束
ACCESS_INTO_NULL: 未定義對象
CASE_NOT_FOUND: CASE 中若未包含相應的 WHEN ,並且沒有設置 ELSE 時
COLLECTION_IS_NULL: 集合元素未初始化
CURSER_ALREADY_OPEN: 游標已經打開
DUP_VAL_ON_INDEX: 唯一索引對應的列上有重復的值
INVALID_NUMBER: 內嵌的 SQL 語句不能將字符轉換為數字
NO_DATA_FOUND: 使用 select into 未返回行,或應用索引表未初始化的元素時
SUBSCRIPT_BEYOND_COUNT:元素下標超過嵌套表或 VARRAY 的最大值
SUBSCRIPT_OUTSIDE_LIMIT: 使用嵌套表或 VARRAY 時,將下標指定為負數
VALUE_ERROR: 賦值時,變量長度不足以容納實際數據
LOGIN_DENIED: PL/SQL 應用程序連接到 oracle 數據庫時,提供了不正確的用戶名或密碼
NOT_LOGGED_ON: PL/SQL 應用程序在沒有連接 oralce 數據庫的情況下訪問數據
PROGRAM_ERROR: PL/SQL 內部問題,可能需要重裝數據字典& pl./SQL 系統包
ROWTYPE_MISMATCH: 宿主游標變量與 PL/SQL 游標變量的返回類型不兼容
SELF_IS_NULL: 使用對象類型時,在 null 對象上調用對象方法
STORAGE_ERROR: 運行 PL/SQL 時,超出內存空間
SYS_INVALID_ID: 無效的 ROWID 字符串
TIMEOUT_ON_RESOURCE: Oracle 在等待資源時超時

others可以代表所有異常,oracle預定義的異常在20000以內。

二、 用戶自定義異常

通常,在自己的程序里,也許需要處理與所寫程序相關的問題。例如,在上個語句塊中,需要輸入員工編號。通常,希望員工編號是正值。但是無意間,用戶輸入一個負數。但是,沒有發生任何錯誤,因為變量v_empno被定義為數值類型。這時,你希望自定義異常來處理這種情況,這種類型的異常被稱為用戶自定義異常。在使用該異常之前,必須首先進行聲明。語法結構如下所示:

DECLARE
     exception_name EXCEPTION;
BEGIN
     ...
     IF  CONDITION THEN
          RAISE exception_name;
     ELSE
          ...
     END IF;
EXCEPTION
     WHEN exception_name THEN
           ERROR-PROCESSING STATEMENTS;
END;

故上例可修改為:

DECLARE
   v_ename varchar2(10);
   v_empno number(4) := &v_empno;
   e_invalid_no exception;
BEGIN
   IF v_empno < 0 THEN
        RAISE e_invalid_no;    -->> 注意:RAISE語句應該與IF語句一起使用,否則,每次執行時,執行權都會轉到該語句塊的異常處理部分
   ELSE
      SELECT ename
         INTO v_ename
      FROM EMP
      WHERE empno = v_empno;
      DBMS_OUTPUT.PUT_LINE('Employee name is '||v_ename);
   END IF;
EXCEPTION
   WHEN e_invalid_no THEN
   DBMS_OUTPUT.PUT_LINE('Employee number can not be negative');
END;

三、RAISE_APPLICATION_ERROR

     RAISE_APPLICATION_ERROR是oracle提供的一種特殊的內置過程,允許編程人員為特定應用程序創建有意義的錯誤信息。RAISE_APPLICATION_ERROR過程適用於未命名的用戶定義異常。它負責將錯誤編號和錯誤文本關聯起來,它的語法為:

     RAISE_APPLICATION_ERROR(error_number,error_message);

     error_number是與特定錯誤信息相關聯的錯誤編號。這個編號的范圍在-20999到-20000之間。error_message是錯誤文本,最多包含2048個字符。

     上例可修改為:

DECLARE
   v_ename varchar2(10);
   v_empno number(4) := &v_empno;
BEGIN
   IF v_empno < 0 THEN
        RAISE_APPLICATION_ERROR(-20000,'Employee number can not be negative');
   ELSE
      SELECT ename
         INTO v_ename
      FROM EMP
      WHERE empno = v_empno;
      DBMS_OUTPUT.PUT_LINE('Employee name is '||v_ename);
   END IF;
END;

當輸入的員工編號為負數時,運行結果如下所示:

SQL> /
Enter value for v_empno: -1234
DECLARE
*
ERROR at line 1:
ORA-20000: Employee number can not be negative
ORA-06512: at line 6

借助於RAISE_APPLICATION_ERROR過程,編程人員能夠遵循與Oracle錯誤一致的方式返回錯誤信息。

四、 EXCEPTION_INIT

在上文內置異常中,預定義異常的個數其實是非常有限的,當程序拋出其它不在上述預定義范圍內的異常時,該如何捕捉呢?

譬如下例:

DECLARE
   v_deptno  number(2) := &v_deptno;
BEGIN
   DELETE FROM dept
    WHERE deptno= v_deptno;
END;

當部門編號輸入10時,我們來看看運行結果:

SQL> /
Enter value for v_deptno: 10
DECLARE
*
ERROR at line 1:
ORA-02292: integrity constraint (SCOTT.FK_DEPTNO) violated - child record found
ORA-06512: at line 4

違反父鍵約束,但是,我們如何捕捉此種錯誤呢?在這里,我們可以用到EXCEPTION_INIT。

使用EXCEPTION_INIT指令,可以將某Oracle錯誤編號和用戶定義異常的名稱建立關聯。EXCEPTION_INIT指令出現在語句塊的聲明部分,如下所示:

DECLARE
      exception_name EXCEPTION;
      PRAGMA EXCEPTION_INIT(exception_name,error_code);

注意,用戶定義異常的聲明出現在所使用的EXCEPTION_INIT指令之前,EXCEPTION_INIT指令有兩個參數:exception_name和error_code。exception_name是異常的名稱,error_code是希望與該異常建立關聯的Oralce錯誤編號。

上例可修改為:

DECLARE
   v_deptno  number(2) := &v_deptno;
   e_child_exists EXCEPTION;
   PRAGMA EXCEPTION_INIT(e_child_exists,-2292);
BEGIN
   DELETE FROM dept
    WHERE deptno= v_deptno;
EXCEPTION
   WHEN e_child_exists THEN
     DBMS_OUTPUT.PUT_LINE('Delete employees for No.'||v_deptno||' dept first');
END;

同樣將部門編號輸入為10,來看看結果:

SQL> /
Enter value for v_deptno: 10
Delete employees for No. 10 dept frist

PL/SQL procedure successfully completed.

可以正常捕捉錯誤!

五、 SQLCODE和SQLERRM

所以Oracle錯誤都可以使用OTHERS異常處理程序進行捕獲和處理,如下例所示:

DECLARE
  v_deptno number(4) := &v_deptno;
  v_dname  varchar2(5);
  v_loc    varchar2(10);
BEGIN
  SELECT dname,loc
    INTO v_dname,v_loc
    FROM dept
  WHERE deptno = v_deptno;
  DBMS_OUTPUT.PUT_LINE(v_dname||','||v_loc);
EXCEPTION
  WHEN OTHERS THEN
     DBMS_OUTPUT.PUT_LINE('An error has occurred');
END;

當輸入10作為部門編號的值時,我們來看看輸出:

SQL> /
Enter value for v_deptno: 10
An error has occurred

PL/SQL procedure successfully completed.

上述輸出說明,在程序運行時發生一個錯誤。如果你對表結構及數據不是很熟悉的話,你很難知道這個錯誤是什么,以及是什么原因導致錯誤發生的。也許在運行時,dept表中不存在對應的部門編號,或者SELECT INTO語句所導致的數據類型匹配問題。盡管這只是一個簡單地例子,但仍舊可能會發生很多意想不到的運行錯誤。

當然,你永遠無法知道程序執行時所有可能發生的運行錯誤,因此,最好在自己的程序中添加OTHERS異常處理程序。為改進自己程序的異常處理接口,Oracle提供了兩個內置函數-SQLCODE和SQLERRM-用於實現OTHERS異常處理程序。SQLCODE函數會返回Oracle錯誤編號,SQLERRM函數返回錯誤信息。

修改上例如下:

DECLARE
  v_deptno number(4) := &v_deptno;
  v_dname  varchar2(5);
  v_loc    varchar2(10);
BEGIN
  SELECT dname,loc
    INTO v_dname,v_loc
    FROM dept
  WHERE deptno = v_deptno;
  DBMS_OUTPUT.PUT_LINE(v_dname||','||v_loc);
EXCEPTION
  WHEN OTHERS THEN
     DBMS_OUTPUT.PUT_LINE(SQLCODE||chr(10)||SQLERRM);  -->> chr(10)代表回車鍵 END;

同樣,當輸入10作為部門編號時,看看輸出:

SQL> / 
Enter value for v_deptno: 10
-6502
ORA-06502: PL/SQL: numeric or value error: character string buffer too
small

PL/SQL procedure successfully completed.

這樣可捕捉任何運行時錯誤的錯誤編號和錯誤信息 

總結:

     1> 預定義異常的錯誤代碼有名稱,譬如上文提到的NO_DATA_FOUNG

     2> 非預定義異常只有錯誤代碼,沒有名稱,如上文提到的ora-02292。這時可以通過EXCEPTION_INIT編譯指令進行錯誤代碼和名稱的關聯。

     3> 當PL/SQL語句塊的可執行部分出現某個運行錯誤時,會拋出不同類型的異常。但是,運行錯誤也可能發生在語句塊的聲明部分或者異常處理部分。控制在這些環境下異常拋出方式的規則稱為異常傳播。

     4> 當PL/SQL語句塊的聲明部分或者異常處理部分出現運行錯誤時,該語句塊的異常處理部分不能捕獲此項錯誤。如果不存在外部語句塊,該程序執行會終止,並將執行權轉到主機環境。如果存在外部語句塊,該異常會立即傳播到外部語句塊。如下例所示:

--outer block
BEGIN
   --inner block
   DECLARE
      v_test CHAR(3) := 'ABC';
   BEGIN
      v_test := '1234';
      DBMS_OUTPUT.PUT_LINE('v_test: '||v_test);
   EXCEPTION
      WHEN INVALID_NUMBER OR VALUE_ERROR THEN
          v_test := 'ABCD';
          DBMS_OUTPUT.PUT_LINE('An error has occurred in the inner block');
   END;
EXCEPTION
   WHEN INVALID_NUMBER OR VALUE_ERROR THEN
       DBMS_OUTPUT.PUT_LINE('An error has occurred in the program');
END;

當執行時,得到如下輸出:

SQL> /
An error has occurred in the program

PL/SQL procedure successfully completed.

最后試舉一例練練思維:

DECLARE
   my_error1 EXCEPTION;
   PRAGMA EXCEPTION_INIT(my_error1, -20001);
   my_error2 EXCEPTION;
   PRAGMA EXCEPTION_INIT(my_error2, -20002);
BEGIN
  IF 1=2  THEN
    raise_application_error(-20001,'err_1');
  ELSE
    raise_application_error(-20002,'err_2');
  END IF;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('Not found');
  WHEN TOO_MANY_ROWS THEN
    DBMS_OUTPUT.PUT_LINE('More data');
  WHEN MY_ERROR1 THEN
    dbms_output.put_line('This is a err_1 test');
  WHEN MY_ERROR2 THEN
    dbms_output.put_line('This is a err_2 test');
END;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM