If an SQL statement fails inside a native SQL procedure and you coded nothing, the procedure stops and the caller sees the error. Handlers are how SQL PL in DB2 for z/OS catches NOT FOUND, warnings, and exceptions — CONTINUE, EXIT, or UNDO — using SQLSTATE, named conditions, SIGNAL, RESIGNAL, and GET DIAGNOSTICS. This page is the WHENEVER chapter for people who live in BEGIN … END instead of COBOL.
Every SQL statement produces a completion: success (SQLSTATE class 00), warning (01), not found (02), or exception (anything else). In a COBOL program you might WHENEVER NOT FOUND GO TO … or check SQLCODE after each statement. In SQL PL you declare handlers in the compound statement. IBM compares them directly to WHENEVER.
General form:
12DECLARE handler-type HANDLER FOR condition sql-procedure-statement;
When a statement raises a matching condition, Db2 runs that sql-procedure-statement (often a SET, or a nested BEGIN that logs and RESIGNALs). When the handler body finishes successfully, the handler type decides where execution continues. If no handler matches an exception, the compound (and usually the whole CALL) fails with that SQLSTATE.
Declaration order still applies: variables and conditions first, cursors second, handlers third, executable SQL last. A handler can only see names declared above it in that compound.
| Type | After the handler body succeeds |
|---|---|
| CONTINUE | Resume after the statement that raised the condition |
| EXIT | Resume at the end of the compound that declared the handler |
| UNDO | Roll back that ATOMIC compound, run the handler, then end the compound |
CONTINUE is the cursor-loop workhorse. NOT FOUND on FETCH should not kill the procedure; it should set a flag and let WHILE see the flag.
1234DECLARE V_AT_END INT DEFAULT 0; DECLARE NOT_FOUND CONDITION FOR '02000'; DECLARE CONTINUE HANDLER FOR NOT_FOUND SET V_AT_END = 1;
After FETCH hits end-of-data, the handler SETs V_AT_END, then execution continues at the statement after FETCH. Your WHILE V_AT_END = 0 test then fails and the loop ends. IBM also documents SQLEXCEPTION CONTINUE handlers that swallow expected errors (DROP TABLE before CREATE in a demo procedure) — use that pattern only when the error is truly ignorable.
Special resume rule: if the condition fired while evaluating a search condition of IF, CASE, WHILE, or REPEAT (not a statement nested inside THEN), CONTINUE resumes after END IF / END CASE / END WHILE / END REPEAT. That is why testing SQLSTATE in the WHILE condition itself is brittle; set a variable in the handler and test the variable.
EXIT runs the handler body, then continues at the end of the compound that declared the handler. If that compound is the procedure body, the procedure returns. Use EXIT for “this CALL cannot continue, but set a message for the caller first.”
123DECLARE NO_TABLE CONDITION FOR '42704'; DECLARE EXIT HANDLER FOR NO_TABLE SET OUT_BUFFER = 'Table does not exist';
SQLSTATE 42704 is “name is an undefined name” (missing table/object). The handler copies a readable string to an OUT parameter, then the BEGIN ends. The caller still needs a convention: maybe also SET P_RC = 8 so they do not treat a blank buffer as success.
UNDO is EXIT plus a rollback of SQL changes made in that compound statement. The compound must be ATOMIC. Before the handler body runs, Db2 undoes those changes (the block behaves as one unit). After the handler body, control is at the end of the compound, like EXIT.
123456P1: BEGIN ATOMIC DECLARE UNDO HANDLER FOR SQLEXCEPTION SET P_MSG = 'Unit rolled back'; UPDATE ACCOUNTS SET BALANCE = BALANCE - P_AMT WHERE ID = P_FROM; UPDATE ACCOUNTS SET BALANCE = BALANCE + P_AMT WHERE ID = P_TO; END P1;
If the second UPDATE fails, UNDO restores the first UPDATE’s change inside this ATOMIC block (subject to IBM’s ATOMIC/savepoint rules and what COMMIT you are allowed to issue in that context). NOT ATOMIC compounds cannot declare UNDO — there is no single unit to undo. Introductory z/OS handler examples stress CONTINUE and EXIT because those cover 90% of procedures; UNDO belongs with ATOMIC money-move blocks.
UNDO does not replace a well-designed unit of work with COMMIT/ROLLBACK at the application boundary. Nested COMMIT inside ATOMIC is restricted. Know whether your shop wants the procedure to COMMIT ON RETURN or leave the UOW to the caller.
You can handle a class, a raw SQLSTATE, or a name you define:
| Condition | Meaning |
|---|---|
| SQLEXCEPTION | SQLSTATE class not 00, 01, or 02 — hard errors |
| SQLWARNING | Class 01 — warnings; SQLCODE usually positive except +100 |
| NOT FOUND | Class 02 (02000) — no row for SELECT INTO / FETCH |
| SQLSTATE 'xxxxx' | One specific five-character SQLSTATE |
| condition-name | A name you DECLARE … CONDITION FOR 'xxxxx' |
123DECLARE DUPKEY CONDITION FOR '23505'; DECLARE EXIT HANDLER FOR DUPKEY SET P_RC = 8;
You may list several conditions on one handler: DECLARE CONTINUE HANDLER FOR NOT FOUND, SQLWARNING SET V_NOTE = 1. That single SET then covers both classes.
In the outermost compound you may declare:
12DECLARE SQLSTATE CHAR(5) DEFAULT '00000'; DECLARE SQLCODE INTEGER DEFAULT 0;
Db2 sets them after each SQL statement: SQLSTATE is the five-character class+subclass (portable); SQLCODE is the integer Db2 code (negative for errors, +100 for not found, other positives for warnings). Handlers are keyed off SQLSTATE (and classes), not off SQLCODE numbers — you cannot DECLARE CONDITION FOR SQLCODE -803, you DECLARE for '23505'.
Assignment to SQLSTATE/SQLCODE is ignored by handlers; the next statement overwrites them. If a handler needs the original code, GET DIAGNOSTICS or copy into V_SQLCODE as the first statement of the handler. After a handler completes successfully, Db2 typically resets the return codes to success (00000 / 0) so the following statement does not look like it failed.
SIGNAL raises a condition on purpose: bad input, a business rule, “this department is frozen.” You may SIGNAL a condition-name or SQLSTATE VALUE 'xxxxx', optionally SET MESSAGE_TEXT = string-expression (up to the documented diagnostic length; IBM describes expressions of CHAR/VARCHAR used as message text).
1234IF P_DEPT IS NULL THEN SIGNAL SQLSTATE VALUE '75001' SET MESSAGE_TEXT = 'Department is required'; END IF;
Do not SIGNAL class 00 (success). Application-defined SQLSTATEs commonly use a class such as 7–9 so they do not collide with IBM-defined states. If a handler matches, it runs. If not, the routine fails and CALL sees the SQLSTATE. SIGNAL typically surfaces as SQLCODE +438 (warning-class states) or -438 (error-class states) in the SQLCA, with your text in the message field.
RESIGNAL is for handlers. Bare RESIGNAL rethrows the condition being handled so an outer handler or the caller still fails after you logged it. RESIGNAL SQLSTATE VALUE '…' SET MESSAGE_TEXT = … maps the low-level error to a friendlier application state.
12345678DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN GET DIAGNOSTICS CONDITION 1 V_MSG = MESSAGE_TEXT; INSERT INTO HR.ERRLOG(TS, TXT) VALUES (CURRENT TIMESTAMP, V_MSG); RESIGNAL; END;
Without RESIGNAL, an EXIT handler that only INSERTs into a log would swallow the error and look like success at the end of the compound. Logging plus RESIGNAL is the usual “audit then fail” pattern.
GET DIAGNOSTICS copies items from the diagnostics area without changing SQLSTATE as a side effect of the GET itself. Common CONDITION 1 items:
STATEMENT-level items include ROW_COUNT after INSERT/UPDATE/DELETE. GET DIAGNOSTICS must run before another SQL statement overwrites the diagnostics area — make it the first thing in the handler.
1234GET DIAGNOSTICS CONDITION 1 V_STATE = RETURNED_SQLSTATE, V_CODE = DB2_RETURNED_SQLCODE, V_MSG = MESSAGE_TEXT;
123456789101112131415161718192021222324252627282930CREATE PROCEDURE HR.SUM_DEPT (IN P_DEPT CHAR(3), OUT P_TOTAL DECIMAL(11,2), OUT P_MSG VARCHAR(100)) LANGUAGE SQL READS SQL DATA BEGIN DECLARE V_SAL DECIMAL(9,2); DECLARE V_AT_END INT DEFAULT 0; DECLARE SQLSTATE CHAR(5) DEFAULT '00000'; DECLARE C1 CURSOR FOR SELECT SALARY FROM DSN8C10.EMP WHERE WORKDEPT = P_DEPT; DECLARE CONTINUE HANDLER FOR NOT FOUND SET V_AT_END = 1; DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN GET DIAGNOSTICS CONDITION 1 P_MSG = MESSAGE_TEXT; SET P_TOTAL = NULL; END; SET P_TOTAL = 0; SET P_MSG = 'OK'; OPEN C1; FETCH C1 INTO V_SAL; WHILE V_AT_END = 0 DO SET P_TOTAL = P_TOTAL + V_SAL; FETCH C1 INTO V_SAL; END WHILE; CLOSE C1; END
NOT FOUND is CONTINUE so the loop can end cleanly. Any real exception EXITs after copying MESSAGE_TEXT to P_MSG. That split — expected end-of-data versus unexpected failure — is the whole point of multiple handlers.
A handler is a grown-up standing next to you while you do homework. If you reach the end of the worksheet (NOT FOUND), CONTINUE says “that’s fine, put your pencil down and go to the next instruction.” EXIT says “stop this whole desk’s work; we’re done here.” UNDO says “erase everything you wrote on this special ATOMIC page, then stop.” SIGNAL is you raising your hand to report a problem on purpose. RESIGNAL is the grown-up, after writing the problem in a logbook, raising their hand so the teacher (the caller) still knows something went wrong. GET DIAGNOSTICS is reading the note the teacher already wrote about what happened.
1. Without any handler, an SQL error in a native SQL procedure typically:
2. After a CONTINUE handler runs successfully:
3. UNDO handlers require:
4. SIGNAL SQLSTATE VALUE '75001' SET MESSAGE_TEXT = 'bad dept' does what?
5. SQLSTATE '02000' is which general condition?