Exceptions: Advanced Concepts - Oracle PL/SQL by Example, Fifth Edition (2015)

Oracle PL/SQL by Example, Fifth Edition (2015)

Chapter 10. Exceptions: Advanced Concepts


In this chapter, you will learn about

Image RAISE_APPLICATION_ERROR

Image EXCEPTION_INIT Pragma

Image SQLCODE and SQLERRM


In Chapters 8 and 9, you encountered the concept of error handling as well as built-in and user-defined exceptions. You also learned about the rules that govern exception scope, propagation, and ways to re-raise an exception.

In this chapter you will conclude your exploration of error handling and exceptions with a study of advanced topics. After working through this chapter, you will be able to associate an error number with an error message, and will be able to trap a runtime error when you have an Oracle error number but no name by which the error can be referenced.

Lab 10.1: RAISE_APPLICATION_ERROR


After this Lab, you will be able to

Image Use RAISE_APPLICATION_ERROR


RAISE_APPLICATION_ERROR is a special built-in procedure provided by Oracle. It allows programmers to create meaningful error messages for a specific application. The RAISE_APLICATION_ERROR procedure works with user-defined exceptions, and its syntax is shown in Listing 10.1.

Listing 10.1 Two forms of the RAISE_APPLICATION_ERROR Procedure

RAISE_APPLICATION_ERROR (error_number, error_message);

Or

RAISE_APPLICATION_ERROR (error_number, error_message, keep_errors);

As you can see, there are two forms of the RAISE_APPLICATION_ERROR procedure. The first form contains only two parameters: error_number and error_message. The error_number is the number of the error that a programmer associates with a specific error message; it can range from –20,999 to –20,000. The error_message is the text of the error, and it can contain up to 2048 characters.

The second form of RAISE_APPLICATION_ERROR contains one additional parameter: keep_errors. It is an optional Boolean parameter. If keep_errors is set to TRUE, the new error will be added to the list of errors that have already been raised. This list of errors is called the error stack. If keep_errors is set to FALSE, the new error replaces the error stack. The default value for the parameter keep_errors is FALSE.

The RAISE_APPLICATION_ERROR procedure works with unnamed user-defined exceptions. That is, it associates the number of the error with the text of the error. In turn, the user-defined exception does not have a name associated with it.

Consider the following example used in Chapter 9, ch09_2a.sql. This example illustrates the use of the named user-defined exception and the RAISE statement. Within the example, compare the modified version using the unnamed user-defined exception and theRAISE_APPLICATION_ERROR procedure. The named user-defined exception and the RAISE statement are shown in bold.

For Example ch10_1a.sql (Chapter 9, example ch09_2a.sql)

DECLARE
v_student_id STUDENT.STUDENT_ID%TYPE := &sv_student_id;
v_total_courses NUMBER;
e_invalid_id EXCEPTION;
BEGIN
IF v_student_id < 0
THEN
RAISE e_invalid_id;
END IF;
SELECT COUNT(*)
INTO v_total_courses
FROM enrollment
WHERE student_id = v_student_id;

DBMS_OUTPUT.PUT_LINE ('The student is registered for '||v_total_courses||' courses');
DBMS_OUTPUT.PUT_LINE ('No exception has been raised');
EXCEPTION
WHEN e_invalid_id
THEN
DBMS_OUTPUT.PUT_LINE ('An ID cannot be negative');
END;

Now, examine the modified example (affected statements are shown in bold):

For Example ch10_1b.sql

DECLARE
v_student_id STUDENT.STUDENT_ID%TYPE := &sv_student_id;
v_total_courses NUMBER;
BEGIN
IF v_student_id < 0
THEN
RAISE_APPLICATION_ERROR (–20000, 'An ID cannot be negative');
END IF;

SELECT COUNT(*)
INTO v_total_courses
FROM enrollment
WHERE student_id = v_student_id;

DBMS_OUTPUT.PUT_LINE ('The student is registered for '|| v_total_courses||' courses');
END;

The second version of the example does not include the name of the exception, the RAISE statement, or the error-handling section of the PL/SQL block. Instead, it has a single RAISE_APPLICATION_ERROR statement.


Did You Know?

Even though the RAISE_APPLICATION_ERROR is a built-in procedure, it is referenced as a statement when used in the PL/SQL block.


Both versions of the example achieve the same result: The processing stops if a negative number is provided for the variable v_student_id. However, the second version of this example produces output that has the look and feel of an error message.

Now, run both versions of the example with the value of –4 for the variable v_student_id. The first version of the example produces the following output:

An ID cannot be negative

The second version of the example produces the following output:

ORA-20000: An ID cannot be negative
ORA-06512: at line 7

The output produced by the first version of the example contains the error message “An ID cannot be negative.” The same error message generated by the second version of the example resembles an error message generated by the system, because the error number ORA-20000 precedes the error message. Also, note that when you run these examples in SQL Developer, the error message produced by the first version of the example appears in the Dbms Output window, whereas the same error message produced by the second version of the example appears in the Script Output window.

The RAISE_APPLICATION_ERROR procedure can work with built-in exceptions as well. Consider the following example:

For Example ch10_2a.sql

DECLARE
v_student_id STUDENT.STUDENT_ID%TYPE := &sv_student_id;
v_name VARCHAR2(50);
BEGIN
SELECT first_name||' '||last_name
INTO v_name
FROM student
WHERE student_id = v_student_id;
DBMS_OUTPUT.PUT_LINE (v_name);
EXCEPTION
WHEN NO_DATA_FOUND
THEN
RAISE_APPLICATION_ERROR (-20001, 'This ID is invalid');
END;

When the user enters a value of 100 for the student ID, the example produces the following output:

ORA-20001: This ID is invalid
ORA-06512: at line 13

The built-in exception NO_DATA_FOUND is raised because there is no record in the STUDENT table corresponding to this value of the student ID. However, the number of the error message does not refer to the exception NO_DATA_FOUND; rather, the error message “This ID is invalid” is displayed.

The RAISE_APPLICATION_ERROR procedure allows programmers to return error messages in a manner that is consistent with Oracle errors. However, it is up to the programmer to maintain the relationship between the error numbers and the error messages. For example, you have designed an application to maintain the enrollment information on students. In this application you have associated the error text “This ID is invalid” with the error number ORA-20001. This error message can be used by your application for any invalid ID. Once you have associated the error number (ORA-20001) with a specific error message (“This ID is invalid”), you should not assign this error number to another error message. If you do not maintain the relationship between error numbers and error messages, the error-handling interface of your application might become very confusing to the users and to yourself.

Lab 10.2: EXCEPTION_INIT Pragma


After this lab, you will be able to

Image Use the EXCEPTION_INIT Pragma


Often your programs need to handle an Oracle error that has a particular number associated with it, but lacks a name by which it can be referenced. In this situation, you are unable to write a handler to trap this error, but you can use a construct called pragma to handle the exception. A pragma is a special instruction to the PL/SQL compiler that is processed at the time of the compilation. The EXCEPTION_INIT pragma allows you to associate an Oracle error number with a name for a user-defined error. Once you associate an error name with an Oracle error number, you can reference the error and write a handler for it.

The EXCEPTION_INIT pragma appears in the declaration section of a block as shown in Listing 10.2.

Listing 10.2 Associating the EXCEPTION_INIT Pragma with a User-Defined Exception

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

Notice that the declaration of the user-defined exception appears before the EXCEPTION_INIT pragma where it is used. The EXCEPTION_INIT pragma has two parameters: exception_name and error_code. The exception_name is the name of your exception, and theerror_code is the number of the Oracle error you want to associate with your exception. Consider the following example:

For Example ch10_3a.sql

DECLARE
v_zip ZIPCODE.ZIP%TYPE := '&sv_zip';
BEGIN
DELETE FROM zipcode
WHERE zip = v_zip;
DBMS_OUTPUT.PUT_LINE ('Zip '||v_zip||' has been deleted');
COMMIT;
END;

In this example, the record corresponding to the value of the ZIP code provided by a user is deleted from the ZIPCODE table. Next, the message that a specific ZIP code has been deleted is displayed on the screen.

Take a look at the results produced by this example when the user enters 06870 for the value of v_zip:

ORA-02292: integrity constraint (STUDENT.STU_ZIP_FK)violated - child record found
ORA-06512: at line 4

The error message generated by this example occurs because you are trying to delete a record from the ZIPCODE table while its child records exist in the STUDENT table, thereby violating the referential integrity constraint STU_ZIP_FK. In other words, there is a record with a foreign key (STU_ZIP_FK) in the STUDENT table (child table) that references a record in the ZIPCODE table (parent table).

This error has Oracle error number ORA-02292 assigned to it, but it does not have a name. To handle this error in the script, you need to associate the error number with a user-defined exception.

Suppose you modify the example as follows (all changes are shown in bold):

For Example ch10_3b.sql

DECLARE
v_zip ZIPCODE.ZIP%TYPE := '&sv_zip';
e_child_exists EXCEPTION;
PRAGMA EXCEPTION_INIT(e_child_exists, -2292);
BEGIN
DELETE FROM zipcode
WHERE zip = v_zip;

DBMS_OUTPUT.PUT_LINE ('Zip '||v_zip||' has been deleted');
COMMIT;
EXCEPTION
WHEN e_child_exists
THEN
DBMS_OUTPUT.PUT_LINE ('Delete students for this ZIP code first');
END;

In this version of the script, you declare the exception e_child_exists. You then associate this exception with the error number –2292. Note that you do not use ORA-02292 in the EXCEPTION_INIT pragma. Next, you add the exception-handling section to the PL/SQL block, thereby trapping this error.

Did you notice that even though the exception e_child_exists is a user-defined exception, you did not use the RAISE statement, as you did in Chapter 9? This is because you have associated the user-defined exception with the specific Oracle error number. Recall that even though an Oracle exception has a number associated with it, it must be referenced by its name in the exception-handling section. Since Oracle error number –2292 does not have a name associated with it, you performed that association explicitly via the EXCEPTION_INIT pragma.

When you run this version of the example using the same value of ZIP code, it produces the following output:

Delete students for this zipcode first

This output contains a new error message displayed by the DBMS_OUTPUT.PUT_LINE statement. It is more descriptive than the previous version of the output. Remember that the user of the program probably does not know about the referential integrity constraints existing in the database. Therefore, the EXCEPTION_INIT pragma improves the readability of your error-handling interface. If the need arises, you can use multiple EXCEPTION_INIT pragmas in your program.

Lab 10.3: SQLCODE and SQLERRM


After this lab, you will be able to

Image Use SQLCODE and SQLERRM


In Chapter 8, you learned about the Oracle exception OTHERS. All Oracle errors can be trapped with the help of the OTHERS exception handler, as illustrated in the following example:

For Example ch10_4a.sql

DECLARE
v_zip VARCHAR2(5) := '&sv_zip';
v_city VARCHAR2(15);
v_state CHAR(2);
BEGIN
SELECT city, state
INTO v_city, v_state
FROM zipcode
WHERE zip = v_zip;

DBMS_OUTPUT.PUT_LINE (v_city||', '||v_state);

EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE ('An error has occurred');
END;

When the user enters 07458 for the value of the ZIP code, this example produces the following output:

An error has occurred

This output informs you that an error occurred at runtime, but you do not know what the error is and what caused it. Maybe there is no record in the ZIPCODE table corresponding to the value provided at runtime, maybe there is a data type mismatch caused by the SELECT INTO statement, or maybe the SELECT INTO statement returned more than one row. As you can see, even though this is a simple example, a number of runtime errors might potentially occur.

Of course, you cannot always identify every possible runtime error that might occur when a program is running. Therefore, it is a good practice to include the OTHERS exception handler in your script. To improve the error-handling interface of your program, the Oracle platform provides two built-in functions, SQLCODE and SQLERRM, that can be used with the OTHERS exception handler. The SQLCODE function returns the Oracle error number, and the SQLERRM function returns the error message. The maximum length of a message returned by the SQLERRM function is 512 bytes, which is the maximum length of an Oracle database error message.

Consider what happens if you modify the preceding example by adding the SQLCODE and SQLERRM functions as follows (modifications are highlighted in bold):

For Example ch10_4b.sql

DECLARE
v_zip VARCHAR2(5) := '&sv_zip';
v_city VARCHAR2(15);
v_state CHAR(2);
v_err_code NUMBER;
v_err_msg VARCHAR2(200);
BEGIN
SELECT city, state
INTO v_city, v_state
FROM zipcode
WHERE zip = v_zip;

DBMS_OUTPUT.PUT_LINE (v_city||', '||v_state);

EXCEPTION
WHEN OTHERS
THEN
v_err_code := SQLCODE;
v_err_msg := SUBSTR(SQLERRM, 1, 200);
DBMS_OUTPUT.PUT_LINE ('Error code: '||v_err_code);
DBMS_OUTPUT.PUT_LINE ('Error message: '||v_err_msg);
END;

When executed, this version of the example produces the following output:

Error code: -6502
Error message: ORA-06502: PL/SQL: numeric or value error: character string buffer too small

This version of the script includes two new variables: v_err_code and v_err_msg. In the exception-handling section of the block, the value returned by the SQLCODE function is assigned to the variable v_err_code, and the value returned by the SQLERRM function is assigned to the variable v_err_msg. Next, the error number and the error message are displayed on the screen via the DBMS_OUTPUT.PUT_LINE statements.

Notice that this output is more informative than the output produced by the previous version of the example because it displays the error message. Once you know which runtime error has occurred in your program, you can take steps to prevent its recurrence.

Generally, the SQLCODE function returns a negative number for an error number. However, there are a few exceptions:

Image When the SQLCODE function is referenced outside the exception section, it returns 0 for the error code. The value of 0 means successful completion.

Image When the SQLCODE function is used with the user-defined exception, it returns +1 for the error code.

Image The SQLCODE function returns a value of 100 when the NO_DATA_FOUND exception is raised.

The SQLERRM function accepts an error number as a parameter, and it returns an error message corresponding to the error number. Usually, it works with the value returned by the SQLCODE function. However, you can provide the error number yourself if such a need arises. Consider the following example:

For Example ch10_5a.sql

BEGIN
DBMS_OUTPUT.PUT_LINE ('Error code: '||SQLCODE);
DBMS_OUTPUT.PUT_LINE ('Error message1: '||SQLERRM(SQLCODE));
DBMS_OUTPUT.PUT_LINE ('Error message2: '||SQLERRM(100));
DBMS_OUTPUT.PUT_LINE ('Error message3: '||SQLERRM(200));
DBMS_OUTPUT.PUT_LINE ('Error message4: '||SQLERRM(-20000));
END;

In this example, the SQLCODE and SQLERRM functions are used in the executable section of the PL/SQL block. The SQLERRM function accepts the value provided by SQLCODE in the second DBMS_OUTPUT.PUT_LINE statement. In the following DBMS_OUPUT.PUT_LINEstatements, the SQLERRM function accepts the values of 100, 200, and –20,000 respectively. When executed, this example produces the following output:

Error code: 0
Error message1: ORA-0000: normal, successful completion
Error message2: ORA-01403: no data found
Error message3: -200: non-ORACLE exception
Error message4: ORA-20000:

The first DBMS_OUTPUT.PUT_LINE statement displays the value of the SQLCODE function. Because no exception has been raised, it returns 0. Next, the value returned by the SQLCODE function is accepted as a parameter by the SQLERRM function. This function returns the message “ORA-0000: normal, successful completion.” Next, the SQLERRM function accepts 100 as its parameter and returns “ORA-01402: no data....” Notice that when the SQLERRM function accepts 200 as its parameter, it is not able to find an Oracle exception that corresponds to the error number 200. Finally, when the SQLERRM function accepts –20,000 as its parameter, no error message is returned. Recall that –20,000 is an error number that can be associated with a named user-defined exception.

Summary

In this chapter, you have concluded your exploration of error handling and exceptions. You learned how to use the RAISE_APPLICATION_ERROR procedure and the SQLCODE and SQLERMM functions to create meaningful error messages in your code. In addition, you learned about theEXCEPTION_INIT pragma, which enables you to associate a user-defined exception with the Oracle error number.


By the Way

The companion website provides additional exercises and suggested answers for this chapter, with discussion related to how those answers resulted. The main purpose of these exercises is to help you test the depth of your understanding by utilizing all of the skills that you have acquired throughout this chapter.