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;