Chapter 4
IN THIS CHAPTER
Identifying error conditions
Discovering SQLSTATE
Dealing with whatever comes your way
Using the WHENEVER clause
Checking the diagnostics areas
Seeing an example of a constraint violation
Putting more constraints on an existing table
Interpreting SQLSTATE information
Handling exceptions
Wouldn’t it be great if every application you wrote worked perfectly every time? Yeah, and it would also be really cool to win $210 million in the Powerball lottery. Unfortunately, both possibilities are equally unlikely to happen. Error conditions of one sort or another are inevitable, so it’s helpful to know what causes them.
SQL’s mechanism for returning error information to you is the status parameter (or host variable)
. Based on the contents of SQLSTATE
SQLSTATE
, you can take different actions to remedy the error condition. The WHENEVER
directive, for example, enables you to take a predetermined action whenever a specified condition is met — if SQLSTATE
has a nonzero value, to take one example. You can also find detailed status information about the SQL statement that you just executed in the diagnostics area.
In this chapter, I explain these helpful error-handling facilities and how to use them. First, however, I show you the conditions that may cause those error-handling facilities to be invoked.
When people say that a person has a condition, they usually mean that something is wrong with that person; he’s sick or injured. People usually don’t bother to mention that a person is in good condition; rather, we talk about people who are in serious condition or, even worse, in critical condition. This idea is similar to the way that programmers talk about the condition of an SQL statement. The execution of an SQL statement can lead to a successful result, to a questionable result, or to an outright erroneous result. Each of these possible results corresponds to a condition.
Every time an SQL statement executes, the database server places a value in the status parameter SQLSTATE
. SQLSTATE
is a 5-character field, accepting the 26 uppercase letters and the numerals 0 through 9. The value placed in SQLSTATE
indicates whether the preceding SQL statement executed successfully. If it didn’t execute successfully, the value of SQLSTATE
provides some information about the error.
The first two of the five characters of SQLSTATE
(the class value) give you the major news about whether the preceding SQL statement executed successfully, returned a result that may or may not have been successful, or produced an error. Table 4-1 shows the four possible results.
TABLE 4-1 SQLSTATE Class Values
Class |
Description |
|
Successful completion |
|
Warning |
|
Not found |
Other |
Exception |
The following list further explains the class values:
00
: Indicates that the preceding SQL statement executed successfully. This is a very welcome result — most of the time.01
: Indicates a warning, meaning that something unusual happened during the execution of the SQL statement. This occurrence may or may not be an error; the database management system (DBMS) can’t tell. The warning is a heads-up to the developer, suggesting that perhaps she should check the preceding SQL statement carefully to ensure that it’s operating correctly.02
: Indicates that no data was returned as a result of the execution of the preceding SQL statement. This result may or may not be good news, depending on what the developer was trying to do with the statement. Sometimes an empty result set is exactly what the developer wanted the SQL statement to return.00
, 01
, or 02
: Indicates an error condition. An indication of the nature of the error appears in the three characters that hold the subclass value. The two characters of the class code, plus the three characters of the subclass code, together comprise the five characters of SQLSTATE
.The SQL standard defines any class code that starts with the letters A through H or the numerals 0 through 4; therefore, these class codes mean the same thing in any implementation. Class codes that start with the letters I through Z or the numerals 5 through 9 are left open for implementers (the people who build DBMSs) to define because the SQL specification can’t anticipate every condition that may occur in every implementation. Implementers should use these nonstandard class codes as little as possible, however, to prevent migration problems from one DBMS to another. Ideally, implementers should use the standard codes most of the time and the nonstandard codes only under the most unusual circumstances.
Because SQLSTATE
updates after every SQL operation, you can check it after every statement executes. If SQLSTATE
contains 00000
(successful completion), you can proceed with the next operation. If it contains anything else, you may want to branch out of the main line of your code to handle the situation. The specific class code and subclass code that an SQLSTATE
contains determines which of several possible actions you should take.
To use SQLSTATE
in a module language program, in which SQL statements are called from a module by a host program written in a procedural language such as C, include a reference to it in your procedure definitions, as in the following example:
PROCEDURE POWERPLANT
(SQLSTATE, :enginename CHAR (20), :displacement SMALLINT,
:hp INTEGER, :cylinders INTEGER, :valves INTEGER
INSERT INTO ENGINES
(EngineName, Displacement, Horsepower, Cylinders, Valves)
VALUES
(:enginename, :displacement, :hp, :cylinders, :valves) ;
At the appropriate spot in your procedural language program, you can make values available for the parameters (perhaps by soliciting them from the user) and then call up the procedure. The syntax of this operation varies from one language to another but looks something like this:
enginename = "289HP" ;
displacement = 289 ;
hp = 271 ;
cylinders = 8 ;
valves = 16 ;
POWERPLANT(state, enginename, displacement, hp, cylinders, valves);
The state of SQLSTATE
is returned in the variable state. Your program can examine this variable and then take the appropriate action based on the variable’s contents.
You can have your program look at SQLSTATE
after the execution of every SQL statement. The question then is what to do with the knowledge that you gain. Depending on the contents of SQLSTATE
, you may want your program to branch to a procedure that handles the existing situation. Examples here would be along the lines of the following:
00
, you probably don’t want to do anything. You want execution to proceed as you originally planned.01
or 02
, you may or may not want to take special action. If you expected a “warning” or “not found” indication, you probably want to let execution proceed normally. If you didn’t expect either of these class codes, you probably want to have execution branch to a procedure specifically designed to handle the unexpected, but not totally unanticipated, warning or not-found result.SQLSTATE
. If multiple different exceptions are possible, there should be an exception-handling procedure for each one because different exceptions often require different responses. Some errors may be correctable, or you may find a workaround. Other errors may be fatal, calling for termination of the application.You can put a condition handler within a compound statement. To create a condition handler, you must first declare the condition that it will handle. The condition declared can be some sort of exception, or it can just be something that is true. Table 4-2 lists the possible conditions and includes a brief description of what causes each type of condition.
TABLE 4-2 Conditions That May Be Specified in a Condition Handler
Condition |
Description |
|
Specific |
|
|
|
|
|
|
Following is an example of a condition declaration:
DECLARE constraint_violation CONDITION
FOR SQLSTATE VALUE '23000' ;
If a condition occurs that invokes a handler, the action specified by the handler executes. This action is an SQL statement, which can be a compound statement. If the handler action completes successfully, the handler effect executes. Following is a list of the three possible handler effects:
CONTINUE
: Continues execution immediately after the statement that caused the handler to be invoked.EXIT
: Continues execution after the compound statement that contains the handler.UNDO
: Undoes the work of the preceding statements in the compound statement and continues execution after the statement that contains the handler.If the handler was able to correct whatever problem invoked the handler, the CONTINUE
effect may be appropriate. The EXIT
effect may be appropriate if the handler didn’t fix the problem but the changes made to the compound statement don’t need to be undone. The UNDO
effect is appropriate if you want to return the database to the state it was in before the compound statement started execution. Consider the following example:
BEGIN ATOMIC
DECLARE constraint_violation CONDITION
FOR SQLSTATE VALUE '23000' ;
DECLARE UNDO HANDLER
FOR constraint_violation
RESIGNAL ;
INSERT INTO students (StudentID, Fname, Lname)
VALUES (:sid, :sfname, :slname) ;
INSERT INTO roster (ClassID, Class, StudentID)
VALUES (:cid, :cname, :sid) ;
END ;
If either of the INSERT
statements causes a constraint violation, such as adding a record with a primary key that duplicates an existing primary key in the table, SQLSTATE
assumes a value of 23000
, thus setting the constraint_violation
condition to a TRUE
value. This action causes the handler to undo any changes that have been made to any tables by either INSERT
command. The RESIGNAL
statement transfers control back to the procedure that called the currently executing procedure.
If both INSERT
statements execute successfully, execution continues with the statement following the END
keyword.
In the preceding example, consider this possibility: What if an exception occurred that returned an SQLSTATE
value other than 23000
? Something is definitely wrong, but the exception handler that you coded can’t handle it. What happens now? Because the current procedure doesn’t know what to do, a RESIGNAL
occurs, bumping the problem up to the next-higher level of control. If the problem isn’t handled at that level, it continues to be elevated to higher levels until it is handled or causes an error condition in the main application.
What’s the point of knowing that an SQL operation didn’t execute successfully if you can’t do anything about it? If an error occurs, you don’t want your application to continue executing as though everything is fine. You need to be able to acknowledge the error and do something to correct it. If you can’t correct the error, at the very least you want to inform the user of the problem and bring the application to a graceful termination.
The WHENEVER
directive is the SQL mechanism for dealing with execution exceptions. WHENEVER
is actually a declaration and, therefore, is located in your application’s SQL declaration section, before the executable SQL code. The syntax is as follows:
WHENEVER <condition> <action> ;
The condition may be either SQLERROR
or NOT FOUND
. The action may be either CONTINUE
or GOTO
address. SQLERROR
is TRUE
if SQLSTATE
has a class code other than 00
, 01
, or 02
. NOT FOUND
is TRUE
if SQLSTATE
is 02000
.
If the action is CONTINUE
, nothing special happens, and the execution continues normally. If the action is GOTO
address (or GO TO
address), execution branches to the designated address in the program. At the branch address, you can put a conditional statement that examines SQLSTATE
and takes different actions based on what it finds. Here are two examples of this scenario:
WHENEVER SQLERROR GO TO error_trap ;
or
WHENEVER NOT FOUND CONTINUE ;
The GO TO
option is simply a macro. The implementation (that is, the embedded language precompiler) inserts the following test after every EXEC
SQL statement:
IF SQLSTATE <> '00000'
AND SQLSTATE <> '00001'
AND SQLSTATE <> '00002'
THEN GOTO error_trap;
The CONTINUE
option is essentially a NO-OP
that says “ignore this.”
Although SQLSTATE
can give you some information about why a particular statement failed, the information is pretty brief, so SQL provides for the capture and retention of additional status information in diagnostics areas. Multiple diagnostics areas are maintained in the form of a last-in-first-out (LIFO) stack. Information on the most recent error appears at the top of the stack. The additional status information in a diagnostics area can be particularly helpful in cases in which the execution of a single SQL statement generates multiple warnings followed by an error. SQLSTATE
reports the occurrence of only one error, but the diagnostics area has the capacity to report on multiple errors — ideally, all errors.
The diagnostics area is a DBMS-managed data structure that has two components:
In the SET TRANSACTION
statement (described in Book 4, Chapter 2), you can specify DIAGNOSTICS SIZE
. The SIZE
that you specify is the number of detail areas allocated for status information. If you don’t include a DIAGNOSTICS SIZE
clause in your SET TRANSACTION
statement, your DBMS assigns its default number of detail areas, whatever that happens to be.
The header area contains ten items, as listed in Table 4-3.
TABLE 4-3 Diagnostics Header Area
Fields |
Data Type |
|
Exact numeric with no fractional part |
|
Exact numeric with no fractional part |
|
VARCHAR (>=128) |
|
Exact numeric with no fractional part |
|
VARCHAR (>=128) |
|
Exact numeric with no fractional part |
|
Exact numeric with no fractional part |
|
Exact numeric with no fractional part |
|
Exact numeric with no fractional part |
|
Exact numeric with no fractional part |
The following list describes these items in more detail:
NUMBER
field is the number of detail areas that have been filled with diagnostic information about the current exception.ROW_COUNT
field holds the number of rows affected if the preceding SQL statement was an INSERT
, UPDATE
, or DELETE
statement.COMMAND_FUNCTION
field describes the SQL statement that was just executed.COMMAND_FUNCTION_CODE
field gives the code number for the SQL statement that was just executed. Every command function has an associated numeric code.DYNAMIC_FUNCTION
field contains the dynamic SQL statement.DYNAMIC_FUNCTION_CODE
field contains a numeric code corresponding to the dynamic SQL statement.MORE
field may be either Y
or N
. Y
indicates that there are more status records than the detail area can hold. N
indicates that all the status records generated are present in the detail area. Depending on your implementation, you may be able to expand the number of records you can handle by using the SET TRANSACTION
statement.TRANSACTIONS_COMMITTED
field holds the number of transactions that have been committed.TRANSACTIONS_ROLLED_BACK
field holds the number of transactions that have been rolled back.TRANSACTION_ACTIVE
field holds 1
if a transaction is currently active and 0
otherwise. A transaction is deemed to be active if a cursor is open or if the DBMS is waiting for a deferred parameter.The detail areas contain data on each individual error, warning, or success condition. Each detail area contains 28 items, as Table 4-4 shows.
TABLE 4-4 Diagnostics Detail Area
Fields |
Data Type |
|
Exact numeric with no fractional part |
|
CHAR (6) |
|
VARCHAR (>=128) |
|
Exact numeric with no fractional part |
|
Exact numeric with no fractional part |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
Exact numeric with no fractional part |
|
Exact numeric with no fractional part |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
|
VARCHAR (>=128) |
I give brief descriptions of some of the entries in Table 4-4 next, and more detailed coverage of other entries in later sections of this chapter.
CONDITION_NUMBER
holds the sequence number of the detail area. If a statement generates five status items that fill five detail areas, the CONDITION_NUMBER
for the fifth detail area is 5. To retrieve a specific detail area for examination, use a GET DIAGNOSTICS
statement (described in “Interpreting SQLSTATE Information,” later in this chapter) with the desired CONDITION_NUMBER
. RETURNED_SQLSTATE
holds the SQLSTATE
value that caused this detail area to be filled.
CLASS_ORIGIN
tells you the source of the class code value returned in SQLSTATE
. If the SQL standard defines the value, the CLASS_ORIGIN
is ISO 9075
. If your DBMS implementation defines the value, CLASS_ORIGIN
holds a string identifying the source of your DBMS. SUBCLASS_ORIGIN
tells you the source of the subclass code value returned in SQLSTATE
.
CLASS_ORIGIN
is important. If you get an SQLSTATE
of 22012
, for example, the values indicate that it’s in the range of standard SQLSTATE
s, so you know that it means the same thing in all SQL implementations. If the SQLSTATE
is 22500
, however, the first two characters are in the standard range and indicate a data exception, but the last three characters are in the implementation-defined range. Finally, if SQLSTATE
is 90001
, it’s completely in the implementation-defined range. SQLSTATE
values in the implementation-defined range can mean different things in different implementations, even though the code itself may be the same.
So how do you find out the detailed meaning of 22500
or the meaning of 90001
? You must look in the implementer’s documentation. Which implementer? If you’re using CONNECT
to connect to data sources, you could be connecting to several products at once. To determine which one produced the error condition, look at CLASS_ORIGIN
and SUBCLASS_ORIGIN
: They have values that identify each implementation. You can test CLASS_ORIGIN
and SUBCLASS_ORIGIN
to see whether they identify implementers for which you have the SQLSTATE
listings. The actual values placed in CLASS_ORIGIN
and SUBCLASS_ORIGIN
are implementer-defined, but they also are expected to be self-explanatory company names.
If the error reported is a constraint violation, the CONSTRAINT_CATALOG
, CONSTRAINT_SCHEMA
, and CONSTRAINT_NAME
fields identify the constraint being violated.
The constraint violation information is probably the most important information that GET DIAGNOSTICS
provides. I discuss GET DIAGNOSTICS
in “Interpreting SQLSTATE
Information,” later in this chapter.
Consider the following EMPLOYEE table:
CREATE TABLE EMPLOYEE (
ID CHAR(5) CONSTRAINT EmpPK PRIMARY KEY,
Salary DEC(8,2) CONSTRAINT EmpSal CHECK Salary > 0,
Dept CHAR(5) CONSTRAINT EmpDept,
REFERENCES DEPARTMENT) ;
Now consider this DEPARTMENT table:
CREATE TABLE DEPARTMENT (
DeptNo CHAR(5),
Budget DEC(12,2) CONSTRAINT DeptBudget
CHECK(Budget >= SELECT SUM(Salary) FROM EMPLOYEE,
WHERE EMPLOYEE.Dept=DEPARTMENT.DeptNo),
…);
Consider an INSERT
as follows:
INSERT INTO EMPLOYEE VALUES(:ID_VAR, :SAL_VAR, :DEPT_VAR);
Suppose that you get an SQLSTATE
of 23000
. You look it up in your SQL documentation, and it says “integrity constraint violation.” Now what? That SQLSTATE
value means that one of the following situations is true:
PRIMARY KEY
constraint.CHECK
constraint on Salary.REFERENCES
constraint on Dept.CHECK
constraint in the Budget column of DEPARTMENT. (Recall that if you change the database, all constraints that may be affected are checked, not just those defined in the immediate table.)Under normal circumstances, you would need to do a great deal of testing to figure out what’s wrong with that INSERT
, but you can find out what you need to know by using GET DIAGNOSTICS
as follows:
DECLARE ConstNameVar CHAR(18) ;
GET DIAGNOSTICS EXCEPTION 1
ConstNameVar = CONSTRAINT_NAME ;
Assuming that SQLSTATE
is 23000
, this GET DIAGNOSTICS
sets ConstNameVar
to EmpPK
, EmpSal
, EmpDept
, or DeptBudget
. Notice that in practice, you may also want to obtain the CONSTRAINT_SCHEMA
and CONSTRAINT_CATALOG
to uniquely identify the constraint given by CONSTRAINT_NAME
.
This use of GET DIAGNOSTICS
— determining which of several constraints has been violated — is particularly important when ALTER TABLE
is used to add constraints that didn’t exist when you wrote the program, as in this example:
ALTER TABLE EMPLOYEE
ADD CONSTRAINT SalLimit CHECK(Salary < 200000) ;
Now if you insert data into EMPLOYEE or update the Salary column of EMPLOYEE, you get an SQLSTATE
of 23000
if Salary exceeds 200000. You can program your INSERT
statement so that if you get an SQLSTATE
of 23000
and don’t recognize the particular constraint name that GET DIAGNOSTICS
returns, you can display a helpful message, such as Invalid INSERT: Violated constraint SalLimit
.
CONNECTION_NAME
and ENVIRONMENT_NAME
identify the connection and environment to which you are connected at the time the SQL statement is executed.
If the report deals with a table operation, CATALOG_NAME
, SCHEMA_NAME
, and TABLE_NAME
identify the table. COLUMN_NAME
identifies the column within the table that caused the report to be made. If the situation involves a cursor, CURSOR_NAME
gives its name.
Sometimes, a DBMS produces a string of natural-language text to explain a condition. The MESSAGE_TEXT
item is for this kind of information. The contents of this item depend on the implementation; the SQL standard doesn’t define them explicitly. If you do have something in MESSAGE_TEXT
, its length in characters is recorded in MESSAGE_LENGTH
, and its length (in octets) is recorded in MESSAGE_OCTET_LENGTH
. If the message is in normal ASCII characters, MESSAGE_LENGTH
equals MESSAGE_OCTET_LENGTH
. If, on the other hand, the message is in Kanji or some other language whose characters require more than an octet to express, MESSAGE_LENGTH
differs from MESSAGE_OCTET_LENGTH
.
To retrieve diagnostic information from a diagnostics area header, use the following:
GET DIAGNOSTICS status1 = item1 [, status2 = item2]… ;
statusn
is a host variable or parameter; itemn
can be any of the keywords NUMBER
, MORE
, COMMAND_FUNCTION
, DYNAMIC_FUNCTION
, or ROW_COUNT
.
To retrieve diagnostic information from a diagnostics detail area, the syntax is as follows:
GET DIAGNOSTICS EXCEPTION <condition number>
status1 = item1 [, status2 = item2]… ;
Again, statusn
is a host variable or parameter, and itemn
is any of the 28 keywords for the detail items listed in Table 4-4 (refer to “The diagnostics detail area,” earlier in this chapter). The condition number is — surprise! — the detail area’s CONDITION_NUMBER
item.
When SQLSTATE
indicates an exception condition by holding a value other than 00000
, 00001
, or 00002
, you may want to handle the situation by taking one of the following actions:
WHENEVER
clause to branch to an exception-handling routine or perform some other action.BEGIN
and END
keywords.Following is an example of a compound-statement exception handler:
BEGIN
DECLARE ValueOutOfRange EXCEPTION FOR SQLSTATE '74001' ;
INSERT INTO ENGINES
(Displacement)
VALUES
(:displacement) ;
SIGNAL ValueOutOfRange ;
MESSAGE 'Process the next displacement value.'
EXCEPTION
WHEN ValueOutOfRange THEN
MESSAGE 'Handling the displacement range error' ;
WHEN OTHERS THEN
RESIGNAL ;
END
With one or more DECLARE
statements, you can give names to specific SQLSTATE
values that you suspect may arise. The INSERT
statement is the one that may cause an exception to occur. If the value of :displacement
exceeds the maximum value for a SMALLINT
data item, SQLSTATE
is set to 74001
. The SIGNAL
statement signals an exception condition. It clears the top diagnostics area. It sets the RETURNED_SQLSTATE
field of the diagnostics area to the SQLSTATE
for the named exception. If no exception has occurred, the series of statements represented by the MESSAGE
'Process the next displacement value'
statement is executed. If an exception has occurred, however, that series of statements is skipped, and the EXCEPTION
statement is executed.
If the exception was a ValueOutOfRange
exception, the series of statements represented by the MESSAGE
'Handling the displacement range error'
statement is executed. If any other exception occurred, the RESIGNAL
statement is executed. RESIGNAL
merely passes control of execution to the calling parent procedure. That procedure may have additional error-handling code to deal with exceptions other than the expected ValueOutOfRange
error.
3.145.111.183