1、PL/SQL錯誤類型
錯誤類型
報告者
處理方法
編譯時錯誤
PL/SQL編譯器
交互式地處理:編譯器報告錯誤,你必須更正這些錯誤
運行時錯誤
PL/SQL運行時引擎
程序化地處理:異常由異常處理子程序引發並進行捕獲
2、異常的聲明
有兩種異常:用戶自定義異常和預定義異常
用戶自定義異常就是由程序員自己定義的一個錯誤。該錯誤還不是非常重要,所以並沒有將整個錯誤包含在Oracle的錯誤中。例如,它可能是一個與數據有關的錯誤。而預定義異常則對應於一般的SQL和PL/SQL錯誤。
用戶自定義異常是在PL/SQL塊的聲明部分聲明的。像變量一樣,異常也有一個類型(EXCEPTION)和有效範圍。例如:
view plaincopy to clipboardprint?
DECLARE
Exception_name EXCEPTION;
…
DECLARE
Exception_name EXCEPTION;
…
3、異常的引發
與異常相關聯的錯誤發生的時候,就會引發相應的異常。用戶自定義異常是通過RAISE語句顯式引發的,而預定義異常則是在它們關聯的ORACLE錯誤發生的時候隱式引發的。如果發生了一個還沒有和異常進行關聯的ORACLE錯誤的時候,也會引發一個異常。該異常可以使用OTHERS子程序進行捕獲。預定義的異常也可以使用RAISE進行顯式地引發,如果需要這樣做的話。
view plaincopy to clipboardprint?
…
RAISE exception_name;
…
…
RAISE exception_name;
…
4、異常的處理
發生異常的時候,程序的控制就會轉移到代碼塊的異常處理部分。異常處理部分是由異常處理子程序組成的,這些異常處理子程序可以是針對某些異常的,也可以是針對所有異常的。與該異常相關聯的錯誤發生,並引發了該異常的時候,就會執行異常處理部分的代碼。
異常處理部分的語法如下:
view plaincopy to clipboardprint?
EXCEPTION
WHEN exception_name THEN
Sequence_of_statements1;
WHEN exception_name THEN
Sequence_of_statements2;
[WHEN OTHERS THEN
Sequence_of_statements3;]
END;
EXCEPTION
WHEN exception_name THEN
Sequence_of_statements1;
WHEN exception_name THEN
Sequence_of_statements2;
[WHEN OTHERS THEN
Sequence_of_statements3;]
END;
每一個異常處理部分都是由WHEN子句和引發異常以後要執行的語句組成的。WHEN標識這個處理子程序是針對哪個異常的。
OTHERS異常處理子程序
PL/SQL定義了一個異常處理子程序,即OTHERS。當前異常處理部分定義的所有WHEN語句都沒有處理的任意一個已引發的異常,都會導致執行這個OTHERS異常處理子程序。該異常處理子程序應該總是作爲代碼塊的最後一個異常處理子程序,這樣就會首先掃描前面的異常處理子程序。WHEN OTHERS會捕獲所有異常,不管這些異常是預定義的,還是用戶自定義的。
檢查錯誤堆棧—SQLCODE和SQLERRM
PL/SQL使用兩個內置函數SQLCODE和SQLERRM提供錯誤信息。SQLCODE返回的是當前的錯誤代號,而SQLERRM返回的是當前的錯誤信息文本。如果是用戶自定義的異常,SQLCODE就會返回值1,SQLERRM就會返回“ User-defined Exception”。
下面是一個使用SQLCODE和SQLERRM的例子
view plaincopy to clipboardprint?
DECLARE
-- Exception to indicate an error condition
e_DuplicateAuthors EXCEPTION;
-- IDs for three authors
v_Author1 books.author1%TYPE;
v_Author2 books.author2%TYPE;
v_Author3 books.author3%TYPE;
-- Code and text of other runtime errors
v_ErrorCode log_table.code%TYPE;
v_ErrorText log_table.message%TYPE;
BEGIN
/* Find the IDs for the 3 authors of 'Oracle9i DBA 101' */
SELECT author1, author2, author3
INTO v_Author1, v_Author2, v_Author3
FROM books
WHERE title = 'Oracle9i DBA 101';
/* Ensure that there are no duplicates */
IF (v_Author1 = v_Author2) OR (v_Author1 = v_Author3) OR
(v_Author2 = v_Author3) THEN
RAISE e_DuplicateAuthors;
END IF;
EXCEPTION
WHEN e_DuplicateAuthors THEN
/* Handler which executes when there are duplicate authors for
Oracle9i DBA 101. We will insert a log message recording
what has happened. */
INSERT INTO log_table (info)
VALUES ('Oracle9i DBA 101 has duplicate authors');
WHEN OTHERS THEN
/* Handler which executes for all other errors. */
v_ErrorCode := SQLCODE;
-- Note the use of SUBSTR here.
v_ErrorText := SUBSTR(SQLERRM, 1, 200);
INSERT INTO log_table (code, message, info) VALUES
(v_ErrorCode, v_ErrorText, 'Oracle error occurred');
END;
/
DECLARE
-- Exception to indicate an error condition
e_DuplicateAuthors EXCEPTION;
-- IDs for three authors
v_Author1 books.author1%TYPE;
v_Author2 books.author2%TYPE;
v_Author3 books.author3%TYPE;
-- Code and text of other runtime errors
v_ErrorCode log_table.code%TYPE;
v_ErrorText log_table.message%TYPE;
BEGIN
/* Find the IDs for the 3 authors of 'Oracle9i DBA 101' */
SELECT author1, author2, author3
INTO v_Author1, v_Author2, v_Author3
FROM books
WHERE title = 'Oracle9i DBA 101';
/* Ensure that there are no duplicates */
IF (v_Author1 = v_Author2) OR (v_Author1 = v_Author3) OR
(v_Author2 = v_Author3) THEN
RAISE e_DuplicateAuthors;
END IF;
EXCEPTION
WHEN e_DuplicateAuthors THEN
/* Handler which executes when there are duplicate authors for
Oracle9i DBA 101. We will insert a log message recording
what has happened. */
INSERT INTO log_table (info)
VALUES ('Oracle9i DBA 101 has duplicate authors');
WHEN OTHERS THEN
/* Handler which executes for all other errors. */
v_ErrorCode := SQLCODE;
-- Note the use of SUBSTR here.
v_ErrorText := SUBSTR(SQLERRM, 1, 200);
INSERT INTO log_table (code, message, info) VALUES
(v_ErrorCode, v_ErrorText, 'Oracle error occurred');
END;
/
由於該堆棧上每一條錯誤消息文本的最大長度均爲512個字節,但是堆棧中可能會有多條消息文本。在上面的例子中,v_ErrorText只有200個字符。如果該錯誤消息文本長度大於200個字符,那麼賦值語句
v_ErrorText := SQLERRM;
就會引發預定義的異常VALUE_ERROR。爲了防止發生這種異常,我們使用了內置函數SUBSTR。
注意,SQLCODE和SQLERRM的返回值首先會被分配給局部變量,然後再在SQL語句中使用這些局部變量。因爲這些函數都是過程化的函數,所以不能直接在SQL語句中使用它們。
通過下面這個例子我們看看錯誤號和相應的錯誤消息文本之間的關係
view plaincopy to clipboardprint?
set serveroutput on
BEGIN
DBMS_OUTPUT.PUT_LINE('SQLERRM(0): ' || SQLERRM(0));
DBMS_OUTPUT.PUT_LINE('SQLERRM(100): ' || SQLERRM(100));
DBMS_OUTPUT.PUT_LINE('SQLERRM(10): ' || SQLERRM(10));
DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('SQLERRM(-1): ' || SQLERRM(-1));
DBMS_OUTPUT.PUT_LINE('SQLERRM(-54): ' || SQLERRM(-54));
END;
/
--運行結果如下
SQL> @SQLERRM.sql
SQLERRM(0): ORA-0000: normal, successful completion
SQLERRM(100): ORA-01403: no data found
SQLERRM(10): -10: non-ORACLE exception
SQLERRM: ORA-0000: normal, successful completion
SQLERRM(-1): ORA-00001: unique constraint (.) violated
SQLERRM(-54): ORA-00054: resource busy and acquire with NOWAIT specified
PL/SQL procedure successfully completed.
set serveroutput on
BEGIN
DBMS_OUTPUT.PUT_LINE('SQLERRM(0): ' || SQLERRM(0));
DBMS_OUTPUT.PUT_LINE('SQLERRM(100): ' || SQLERRM(100));
DBMS_OUTPUT.PUT_LINE('SQLERRM(10): ' || SQLERRM(10));
DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('SQLERRM(-1): ' || SQLERRM(-1));
DBMS_OUTPUT.PUT_LINE('SQLERRM(-54): ' || SQLERRM(-54));
END;
/
--運行結果如下
SQL> @SQLERRM.sql
SQLERRM(0): ORA-0000: normal, successful completion
SQLERRM(100): ORA-01403: no data found
SQLERRM(10): -10: non-ORACLE exception
SQLERRM: ORA-0000: normal, successful completion
SQLERRM(-1): ORA-00001: unique constraint (.) violated
SQLERRM(-54): ORA-00054: resource busy and acquire with NOWAIT specified
PL/SQL procedure successfully completed.
EXCEPTION_INIT pragma
你可以將一個經過命名的異常和一個特別的ORACLE錯誤相關聯。這會使你專門能夠捕獲此錯誤,而不是通過WHEN OTHERS處理器來進行捕獲。EXCEPTION_INIT pragma的語法如下:
PRAGMA EXCEPTION_INIT(exception_name,Oracle_error_number);
這裏,exception_name是在PRAGMA前面聲明的異常的名字,而Oracle_error_number是與此命名異常相關的所需錯誤代碼。這個PRAGMA必須在聲明部分。
下面這個例子在運行時刻如果遇到“ORA-1400:mandatory NOT NULL column missing or NULL during insert”錯誤時將引發e_MissingNull--用戶定義的異常。
view plaincopy to clipboardprint?
DECLARE
e_MissingNull EXCEPTION;
PRAGMA EXCEPTION_INIT(e_MissingNull, -1400);
BEGIN
INSERT INTO students (id) VALUES (NULL);
EXCEPTION
WHEN e_MissingNull then
INSERT INTO log_table (info) VALUES ('ORA-1400 occurred');
END;
/
DECLARE
e_MissingNull EXCEPTION;
PRAGMA EXCEPTION_INIT(e_MissingNull, -1400);
BEGIN
INSERT INTO students (id) VALUES (NULL);
EXCEPTION
WHEN e_MissingNull then
INSERT INTO log_table (info) VALUES ('ORA-1400 occurred');
END;
/
每次發生PRAGMA EXCEPTION_INIT時,一個Oracle錯誤只能和一個用戶自定義異常相關聯。在異常處理內部,SQLCODE和SQLERRM將會返回發生Oracle錯誤的代碼和錯誤消息,但是不會返回用戶定義的消息。
使用RAISE_APPLICATION_ERROR
你可以使用內置函數RAISE_APPLICATION_ERROR以創建自己的錯誤消息,這可能要比已命名的異常更具說明性。用戶定義消息從塊中傳遞到調用環境中的方式和ORACLE錯誤是一樣的。語法如下:
RAISE_APPLICATION_ERROR(error_number,error_message,[keep_errors]);
error_number是從-200000到-20999之間的參數,error_message是與此錯誤相關的正文,不能多於512個字節。而keep_errors是一個布爾值,是可選的,如果爲TRUE,那麼新的錯誤將被添加到已經引發的錯誤列表中(如果有的話)。如果爲FALSE(這是缺省的設置),那麼新的錯誤將替換錯誤的當前列表。
例如下面的這個例子將在爲一個新的學生註冊以前檢查是否在班級中有足夠的地方容納他。
view plaincopy to clipboardprint?
CREATE OR REPLACE PROCEDURE Register (
/* Registers the student identified by the p_StudentID parameter in the class
identified by the p_Department and p_Course parameters. Before calling
ClassPackage.AddStudent, which actually adds the student to the class, this
procedure verifies that there is room in the class, and that the class
exists. */
p_StudentID IN students.id%TYPE,
p_Department IN classes.department%TYPE,
p_Course IN classes.course%TYPE) AS
v_CurrentStudents NUMBER; -- Current number of students in the class
v_MaxStudents NUMBER; -- Maximum number of students in the class
BEGIN
/* Determine the current number of students registered, and the maximum
number of students allowed to register. */
SELECT current_students, max_students
INTO v_CurrentStudents, v_MaxStudents
FROM classes
WHERE course = p_Course
AND department = p_Department;
/* Make sure there is enough room for this additional student. */
IF v_CurrentStudents + 1 > v_MaxStudents THEN
RAISE_APPLICATION_ERROR(-20000, 'Can''t add more students to ' ||
p_Department || ' ' || p_Course);
END IF;
/* Add the student to the class. */
ClassPackage.AddStudent(p_StudentID, p_Department, p_Course);
EXCEPTION
WHEN NO_DATA_FOUND THEN
/* Class information passed to this procedure doesn't exist. Raise an error
to let the calling program know of this. */
RAISE_APPLICATION_ERROR(-20001, p_Department || ' ' || p_Course ||
' doesn''t exist!');
END Register;
/
CREATE OR REPLACE PROCEDURE Register (
/* Registers the student identified by the p_StudentID parameter in the class
identified by the p_Department and p_Course parameters. Before calling
ClassPackage.AddStudent, which actually adds the student to the class, this
procedure verifies that there is room in the class, and that the class
exists. */
p_StudentID IN students.id%TYPE,
p_Department IN classes.department%TYPE,
p_Course IN classes.course%TYPE) AS
v_CurrentStudents NUMBER; -- Current number of students in the class
v_MaxStudents NUMBER; -- Maximum number of students in the class
BEGIN
/* Determine the current number of students registered, and the maximum
number of students allowed to register. */
SELECT current_students, max_students
INTO v_CurrentStudents, v_MaxStudents
FROM classes
WHERE course = p_Course
AND department = p_Department;
/* Make sure there is enough room for this additional student. */
IF v_CurrentStudents + 1 > v_MaxStudents THEN
RAISE_APPLICATION_ERROR(-20000, 'Can''t add more students to ' ||
p_Department || ' ' || p_Course);
END IF;
/* Add the student to the class. */
ClassPackage.AddStudent(p_StudentID, p_Department, p_Course);
EXCEPTION
WHEN NO_DATA_FOUND THEN
/* Class information passed to this procedure doesn't exist. Raise an error
to let the calling program know of this. */
RAISE_APPLICATION_ERROR(-20001, p_Department || ' ' || p_Course ||
' doesn''t exist!');
END Register;
/
5、異常的傳播
1)在執行部分引發的異常
當一個異常是在塊的執行部分引發的,PL/SQL使用下面的方法決定要激活哪個異常處理器:
如果當前塊對該異常設置了處理器,那麼執行它併成功完成該塊的執行,然後控制會轉給包含塊。
如果當前塊沒有對當前異常定義處理器,那麼通過在包含塊中引發它來傳播異常。然後對包含塊執行步驟一。
2)在聲明部分引發的異常
如果在聲明部分的賦值操作引發了一個異常,那麼該異常將立即傳播給包含塊。發生這種情況以後,在前面給出的法則將進一步被應用到異常的傳播上。儘管在當前塊中有一個處理器,它也不會被執行。
3)在異常處理部分引發的異常
在異常處理器中也可能引發異常,這可以是通過RAISE語句顯式引發的,也可以是由運行時刻錯誤隱含引發的。無論怎樣,異常都立即被傳播給包含塊,這和聲明部分引發的異常相類似。
6、使用異常的準則
1)異常的範圍
異常像變量一樣,也是有一定範圍的。如果用戶自定義異常傳播到它的範圍之外,就不能再通過名稱引用它。
view plaincopy to clipboardprint?
BEGIN
DECLARE
e_UserDefinedException EXCEPTION;
BEGIN
RAISE e_UserDefinedException;
END;
EXCEPTION
/* e_UserDefinedException is out of scope here - can only be
handled by an OTHERS handler */
WHEN OTHERS THEN
/* Just re-raise the exception, which will be propagated to the
calling environment */
RAISE;
END;
/
BEGIN
DECLARE
e_UserDefinedException EXCEPTION;
BEGIN
RAISE e_UserDefinedException;
END;
EXCEPTION
/* e_UserDefinedException is out of scope here - can only be
handled by an OTHERS handler */
WHEN OTHERS THEN
/* Just re-raise the exception, which will be propagated to the
calling environment */
RAISE;
END;
/
一般而言,如果打算將用戶自定義的錯誤傳播到代碼塊之外,最好的方法就是在包中定義該異常,以使其在該代碼塊之外仍可見,或使用RAISE_APPLICATION_ERROR引發該異常。如果創建一個成爲GLOBALS的包,並在其中定義了一個e_UserDefinedException異常,那麼這個異常在外部塊中仍然可見。
如下例所示
view plaincopy to clipboardprint?
CREATE OR REPLACE PACKAGE Globals AS
/* This package contains global declarations. Objects declared here will
be visible via qualified references for any other blocks or procedures.
Note that this package does not have a package body. */
/* A user-defined exception. */
e_UserDefinedException EXCEPTION;
END Globals;
/
--有了這個和GLOBALS包以後,就可以重寫前面的代碼:
BEGIN
BEGIN
RAISE Globals.e_UserDefinedException;
END;
EXCEPTION
/* Since e_UserDefinedException is still visible, we can handle it
explicitly */
WHEN Globals.e_UserDefinedException THEN --引用包中定義異常
/* Just re-raise the exception, which will be propagated to the
calling environment */
RAISE;
END;
/
CREATE OR REPLACE PACKAGE Globals AS
/* This package contains global declarations. Objects declared here will
be visible via qualified references for any other blocks or procedures.
Note that this package does not have a package body. */
/* A user-defined exception. */
e_UserDefinedException EXCEPTION;
END Globals;
/
--有了這個和GLOBALS包以後,就可以重寫前面的代碼:
BEGIN
BEGIN
RAISE Globals.e_UserDefinedException;
END;
EXCEPTION
/* Since e_UserDefinedException is still visible, we can handle it
explicitly */
WHEN Globals.e_UserDefinedException THEN --引用包中定義異常
/* Just re-raise the exception, which will be propagated to the
calling environment */
RAISE;
END;
/
2)避免未處理的異常
優秀的編程經驗是在整個程序中避免出現任何未經過處理的異常。這可以通過在程序的最頂層使用一個OTHERS子程序來實現。該處理子程序可以只登記錯誤並登記錯誤發生的位置,通過這種方法,就可以保證每個錯誤都會得到檢查。
如下例所示
view plaincopy to clipboardprint?
DECLARE
v_errornumber number;
v_errortext varchar2(200);
Begin
…
EXCEPTION
WHEN OTHERS THEN
v_errornumber:=SQLCODE;
v_errortext:=SUBSTR(SQLERRM,1,200);
INSERT INTO log_table(code,message,info)
VALUES
(v_errornumber,v_errortext,’Oracle error occurred at’||TO_CHAR(SYSDATE,’DD-MON-YY HH24:MI:SS’));
END;
DECLARE
v_errornumber number;
v_errortext varchar2(200);
Begin
…
EXCEPTION
WHEN OTHERS THEN
v_errornumber:=SQLCODE;
v_errortext:=SUBSTR(SQLERRM,1,200);
INSERT INTO log_table(code,message,info)
VALUES
(v_errornumber,v_errortext,’Oracle error occurred at’||TO_CHAR(SYSDATE,’DD-MON-YY HH24:MI:SS’));
END;
3)標識錯誤發生的位置
由於整個代碼塊都使用同一個異常處理部分檢查並處理異常,所以很難確定引發這個錯誤的是哪一條SQL語句。考慮下面示例
view plaincopy to clipboardprint?
BEGIN
SELECT…
SELECT..
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
--which select statement raised the exception?
END;
--解決上述問題的方法有兩種。第一種是添加一個標識該SQL語句的計數器:
DECLARE
V_selectcounter NUMBER:=1;
BEGIN
SELECT…
V_selectcounter NUMBER:=2;
SELECT…
V_selectcounter NUMBER:=3;
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO log_table(info) VALUES(‘NO DATA FOUND IN SELECT’||v_selectcounter);
END;
--另一種方法是將每一條語句都放置在它自己的子塊中:
BEGIN
BEGIN
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO log_table(info) VALUES(‘NO DATA FOUND IN SELECT 1’);
END;
BEGIN
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO log_table(info) VALUES(‘NO DATA FOUND IN SELECT 2’);
END;
BEGIN
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO log_table(info) VALUES(‘NO DATA FOUND IN SELECT 3’);
END;
END;
BEGIN
SELECT…
SELECT..
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
--which select statement raised the exception?
END;
--解決上述問題的方法有兩種。第一種是添加一個標識該SQL語句的計數器:
DECLARE
V_selectcounter NUMBER:=1;
BEGIN
SELECT…
V_selectcounter NUMBER:=2;
SELECT…
V_selectcounter NUMBER:=3;
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO log_table(info) VALUES(‘NO DATA FOUND IN SELECT’||v_selectcounter);
END;
--另一種方法是將每一條語句都放置在它自己的子塊中:
BEGIN
BEGIN
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO log_table(info) VALUES(‘NO DATA FOUND IN SELECT 1’);
END;
BEGIN
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO log_table(info) VALUES(‘NO DATA FOUND IN SELECT 2’);
END;
BEGIN
SELECT…
EXCEPTION
WHEN NO_DATA_FOUND THEN
INSERT INTO log_table(info) VALUES(‘NO DATA FOUND IN SELECT 3’);
END;
END;
7、異常代碼的編寫風格
1)RAISE_APPLICATION_ERROR和RAISE的比較
RAISE_APPLICATION_ERROR
RAISE
允許我們填寫自己的錯誤消息文本,該文本可以包含應用程序專用的數據
不允許包含消息文本
不能使用已命名的異常處理子程序進行捕獲,只能使用OTHERS處理子程序進行捕獲
可以使用已命名的處理子程序捕獲該異常,只要該異常在它自己的異常範圍內即可
通常而言,推薦對設計給終端用戶看的錯誤,使用RAISE_APPLICATION_ERROR。因爲對於他們而言,具體的錯誤編號和描述性文本非常有用。而另一方面,對設計爲由程序直接進行處理的異常,推薦使用RAISE。
2)將異常作爲控制語句
因爲引發異常會將程序的控制邏輯轉移到代碼塊的異常處理部分,所以可以將RAISE語句用作控制語句,就像GOTO語句一樣。例如,如果我們有很深的嵌套循環,並需要立即從中退出的時候,這可能會非常有用。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/wh62592855/archive/2009/10/28/4736501.aspx