In this chapter, you will learn how to
Use the compound statement
Use labels in both compound statements and loops
Work with the two conditional statements (IF
and CASE
)
Implement the four looping statements (FOR
, WHILE
, REPEAT
, and LOOP
)
Implement the four transfer of control statements (GOTO
, LEAVE
, ITERATE
, and RETURN
)
Sequential execution is the most basic path that program execution can take. With this method, the program starts execution at the first line of the code, followed by the next, and continues until the final statement in the code has been executed. This approach works fine for very simple tasks but tends to lack usefulness because it can only handle one situation. Programs often need to be able to decide what to do in response to changing circumstances. By controlling a code’s execution path, a specific piece of code can then be used to intelligently handle more than one situation.
Flow of control statements are used to control the sequence of statement execution. Statements such as IF
and CASE
are used to conditionally execute blocks of SQL PL statements, while other statements, such as WHILE
and REPEAT
, are typically used to execute a set of statements repetitively until a task is complete.
Although there are many flow of control statements to be discussed in this chapter, there are three main categories: conditional statements, loop statements, and transfer of control statements. Before jumping into a discussion on flow of control statements, it is important to first understand the use of compound statements.
Of all the SQL control statements, the compound statement is the easiest to work with and understand. Compound statements are used to group a set of related lines of code. You can declare variables, cursors, and condition handlers, and use flow of control statements within a compound statement. Cursors and condition handlers are discussed in Chapter 5, “Understanding and Using Cursors and Result Sets” and Chapter 6, “Condition Handling,” respectively.
BEGIN
and END
are keywords that define a compound statement. The BEGIN
keyword defines the starting line of code for the compound statement, while the END
keyword defines the final line of code. Compound statements are used to control variable scoping and for executing more than a single statement where a single statement is expected, such as within a condition handler (this topic is explored more fully in Chapter 6, “Condition Handling”).
On LUW and iSeries, for nested compound statements, each compound statement has its own scope. Only variables and the like that have been declared within the same compound statement or within enclosing compound statements can be seen. That is, statements within one compound statement may not be able to refer to variables and values that are declared within another compound statement, even if both compound statements are part of the same SQL procedure body.
It is perfectly logical and, in most cases, completely valid to define as many compound statements as needed within an SQL procedure. These compound statements are typically used to introduce scoping and a logical separation of related statements.
There is a specific order for declaring variables, conditions, cursors, and handlers within a compound statement. Specifically, the order of declarations must proceed as follows:
BEGIN variable declarations condition declarations cursor declarations handler declarations assignment, flow of control, SQL statements, and other compound statements END
On iSeries and zSeries, variable and condition declarations can be mixed, but for portability of procedures, you should follow the order specified.
Don’t worry if you are not familiar with some of these terms; they are discussed in greater detail later in the book. Also, notice that one or many compound statements can be nested within other compound statements. In such cases, the same order of declarations continues to apply at each level.
It is important to understand the type of variable scoping (or visibility) that occurs when a compound statement has been defined. Specifically:
Scoping is illustrated in Figure 4.1.
Example 4.1. Variable scoping example for LUW and iSeries.
CREATE PROCEDURE nested_compound () LANGUAGE SQL SPECIFIC nested_compound -- applies to LUW and iSeries BEGIN -- (1) DECLARE v_outer1 INT; DECLARE v_outer2 INT; BEGIN -- (2) DECLARE v_inner1 INT; DECLARE v_inner2 INT; SET v_outer1 = 100; -- (3) SET v_inner1 = 200; -- (4) END; -- (5) SET v_outer2 = 300; -- (6) SET v_inner2 = 400; -- (7) END -- (8)
In the previous figure, Lines (1) and (8) define the outer compound statement, while Lines (2) and (5) define the inner compound statement.
On LUW, all statements, except the statement shown in Line (7), will succeed. This statement fails because an outer compound statement cannot see a variable declared within an inner compound statement. You will receive an SQLSTATE 42703
error with the message 'V_INNER2' is not valid in the context where it is used
.
On iSeries, the error is caught at procedure create time, and the CREATE PROCEDURE
statement fails with SQLSTATE 42618
with the message Variable V_INNER2 not defined or not usable
.
Scoping can be especially useful in the case of looping and exception handling, allowing the program flow to jump from one compound statement to another.
The next few sections describe two distinct types of compound statements, each of which serve a different purpose.
The previous example illustrated a NOT ATOMIC
compound statement and is the default type used in SQL procedures. If an unhandled error (that is, no condition handler has been declared for the SQLSTATE
raised) occurs within the compound statement, any work that is completed before the error will not be rolled back, but will not be committed either. The group of statements can only be rolled back if the unit of work is explicitly rolled back using ROLLBACK
or ROLLBACK TO SAVEPOINT
. You can also COMMIT
successful statements if it makes sense to do so.
The syntax for a NOT ATOMIC
compound statement is shown in Figure 4.2.
Example 4.2. The NOT ATOMIC compound statement syntax diagram.
.-NOT ATOMIC--. >>-+---------+--BEGIN----+-------------+------------------------> '-label:--' ... Misc. Statements; ... >-------------------------------------+---END--+--------+------>< '-label--'
The optional label
is used to define a name for the code block. The label can be used to qualify SQL variables declared within the compound statement. If the ending label is used, it must be the same as the beginning label. You will learn more about labels in section, “Using Labels,” in this chapter.
The SQL procedure illustrated in Figure 4.3 demonstrates the non-atomicity of NOT ATOMIC
compound statements.
Example 4.3. An example of a NOT ATOMIC compound statement.
CREATE PROCEDURE not_atomic_proc () LANGUAGE SQL SPECIFIC not_atomic_proc -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries nap: BEGIN NOT ATOMIC -- Procedure logic INSERT INTO cl_sched (class_code, day) VALUES ('R11:TAA', 1); SIGNAL SQLSTATE '70000'; -- (1) INSERT INTO cl_sched (class_code, day) VALUES ('R22:TBB', 2); END nap
Although the SIGNAL
statement on Line (1) has not been introduced, it is sufficient to understand that the statement is used to explicitly raise an error. Additionally, because this error is unhandled, the procedure will exit right after the error. More information can be found in Chapter 6, “Condition Handling”.
After calling this procedure, you will see that although an error has been raised halfway through execution, the first INSERT
successfully inserted a row into the atomic_test
table. You need to realize, however, that the procedure itself does not issue either COMMIT
or ROLLBACK
implicitly.
ATOMIC
compound statements are supported in DB2 for LUW and iSeries only. In DB2 zSeries, the NOT ATOMIC
compound statement is the default and the only type used in SQL procedures.
The ATOMIC
compound statement, as the name suggests, can be thought of as a singular whole. If any unhandled error conditions arise within it, all statements that have executed up to that point are considered to have failed as well and are therefore rolled back.
On LUW, ATOMIC
compound statements cannot be nested inside other ATOMIC
compound statements. iSeries does not have this restriction.
In addition, you cannot use SAVEPOINT
s or issue explicit COMMIT
s or ROLLBACK
s from within an ATOMIC
compound statement.
The syntax to declare an ATOMIC
compound statement is shown in Figure 4.4.
Example 4.4. ATOMIC compound statement syntax diagram.
>>-+---------+--BEGIN ATOMIC------------------------------------> '-label:--' ... Misc. Statements; ... >-------------------------------------+---END--+--------+------>< '-label--'
A label is used in the same way as with a NOT ATOMIC
compound statement.
The example in Figure 4.5 illustrates the behavior of an ATOMIC
compound statement. It is quite similar to the NOT ATOMIC
example shown in Figure 4.4, and only differs in name and in the fact that it uses an ATOMIC
compound statement.
Example 4.5. ATOMIC compound statement example for LUW and iSeries.
CREATE PROCEDURE atomic_proc () LANGUAGE SQL SPECIFIC atomic_proc -- applies to LUW and iSeries ap: BEGIN ATOMIC -- Procedure logic INSERT INTO cl_sched (class_code, day) VALUES ('R33:TCC', 3); SIGNAL SQLSTATE '70000'; -- (1) INSERT INTO cl_sched (class_code, day) VALUES ('R44:TDD', 4); END ap
When the error condition of SQLSTATE 70000
is raised on Line (1), the unhandled error causes procedure execution to stop. Unlike the NOT ATOMIC
example in Figure 4.3, the first INSERT
statement will be rolled back, resulting in a table with no inserted rows from this procedure.
Labels can be used to name any executable statement, which includes compound statements and loops. By using labels, you can have the flow of execution either jump out of a compound statement or loop, or jump to the beginning of a compound statement or loop.
Optionally, you may supply a corresponding label for the END
of a compound statement. If an ending label is supplied, it must be the same as the label used at its beginning.
Each label must be unique within the body of an SQL procedure.
A label can also be used to avoid ambiguity if a variable with the same name has been declared in more than one compound statement of the stored procedure. A label can be used to qualify the name of an SQL variable.
Figure 4.6 shows the use of a label to name a compound statement and also how to avoid ambiguous references to similarly named variables. It uses two variables of the same name (v_ID
) defined in two differently labeled compound statements.
Example 4.6. A labeled compound statement example for LUW and iSeries.
-- Example applies to LUW and iSeries only -- because nested compound statements are used CREATE PROCEDURE show_label (OUT p_WorkerID INT) LANGUAGE SQL SPECIFIC show_label -- applies to LUW and iSeries sl1: BEGIN -- Declare variables DECLARE v_ID INT; -- (1) -- New compound statement sl2: BEGIN -- Declare variables DECLARE v_ID INT; -- (2) -- Procedure logic SET sl1.v_ID = 1; -- (3) SET sl2.v_ID = 2; -- (4) SET v_ID = 3; -- (5) SET p_WorkerID = sl2.v_ID; -- (6) END sl2; END sl1
You can see that, to avoid ambiguity, the two v_ID
variables defined in Lines (1) and (2) are qualified with the label of the compound statement in which they were defined at Lines (3), (4), and (6). When qualification is not used, as in Line (5), the variable will be qualified with the label of the compound statement from which it is being referenced. So the value assignment at Line (5) will actually assign a value of 3 to sl2.v
_ID
, which means that p_Worker
ID
in Line (6) will also be assigned a value of 3.
Additionally, the label of the compound statement or loop can be used with the LEAVE
statement to exit the labeled compound statement or loop. Labels can also be used with the ITERATE
statement to jump back to the labeled beginning of a LOOP
. These SQL PL statements are covered in greater detail later on in this chapter.
Conditional statements allow stored procedures to make decisions. They are used to define multiple branches of execution based on whether or not a condition was met.
A commonly used conditional statement is the IF
statement, where a branch of execution can be taken if a specific condition is satisfied. IF
statements can also define a branch of execution for when a condition is not met.
Another conditional statement in SQL PL is the CASE
statement, which is similar to an IF statement, but the branching decision is more flexible.
The most commonly used approach for conditional execution is the IF
statement. There are essentially three different types of IF
statements.
The simplest form of the IF
statement does something if a condition is true, and nothing otherwise.
But what happens if you want to do one thing if a condition is true and something else if it is false? This is where the ELSE
clause comes in handy. When used in conjunction with an IF
statement, you can do something IF
a condition is true and something ELSE
if the condition is false.
Thirdly, ELSEIF
is used to branch to multiple code paths based on mutually exclusive conditions in the same manner as an IF
statement. You can make use of an ELSEIF
statement to rewrite a ladder of nested IF ... ELSE
statements for readability. Your procedure can specify an unlimited number of ELSEIF
statements.
The syntax of an IF
statement is depicted in Figure 4.7.
Example 4.7. An IF syntax diagram.
>>-IF--search-condition-THEN-----------------------------------> .------------------------------. V | >--------SQL-procedure-statement-;---+-------------------------> .------------------------------------------------------------------------ V | >----+--------------------------------------------------------------------+--+> | .-------------------------------. | | V | | '-ELSEIF--search-condition--THEN-----SQL-procedure-statement--;---+--' >-----+------------------------------------------+--END IF----->< | .------------------------------. | | V | | '-ELSE-----SQL-procedure-statement-;---+---'
The search-condition
specifies the condition for which an SQL statement should be invoked. If the condition is false, processing continues to the next search-condition
, until either a condition is true or processing reaches the ELSE
clause.
SQL
-procedure-statement
specifies the statements to be invoked if the preceding search-condition
is true. If no search-condition
evaluates to true, then the SQL
-procedure-statement
following the ELSE
keyword is invoked.
The snippet of an SQL procedure shown in Figure 4.8 demonstrates how the rating of an employee determines the raise in salary and bonus that he or she will receive.
Example 4.8. An IF statement example.
CREATE PROCEDURE demo_if ( IN p_rating INT , IN p_employee_number char(6) ) LANGUAGE SQL SPECIFIC demo_if -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries di: BEGIN -- Procedure logic IF p_rating = 1 THEN -- (1) UPDATE employee SET salary = salary * 1.10, bonus = 1000 WHERE empno = p_employee_number; ELSEIF p_rating = 2 THEN -- (2) UPDATE employee SET salary = salary * 1.05, bonus = 500 WHERE empno = p_employee_number; ELSE -- (3) UPDATE employee SET salary = salary * 1.03, bonus = 0 WHERE empno = p_employee_number; END IF; END di
On Line (1), an employee with a 1 rating can expect a raise of 10 percent and a bonus of $1,000. On Line (2), an employee with a 2 rating earns a 5 percent raise with a $500 bonus. On Line (3), all other employees can expect a 3 percent pay hike with no bonus.
You are not simply limited to mathematical operators such as equals (=) and greater than (>). You can also use the SQL keywords NOT
, AND
, and OR
to build conditions in your IF
statements.
The CASE
statement provides the ability to evaluate a list of options based on the value of a single variable. You would most likely choose to use a CASE
statement if you have a large decision tree and all branches depend on the value of the same variable. Otherwise, you would be better off using a series of IF
, ELSEIF
, and ELSE
statements. The syntax diagram for the CASE
statement is shown in Figure 4.9.
Example 4.9. A CASE statement syntax diagram.
>>-CASE----+-| searched-case-statement-when-clause |-+----------> '-| simple-case-statement-when-clause |---' >----END CASE-------------------------------------------------->< simple-case-statement-when-clause |---expression--------------------------------------------------> .-------------------------------------------------------------. | .-------------------------------. | V V | | >--------WHEN--expression--THEN-----SQL-procedure-statement--;---+--+> >-----+------------------------------------------+--------------| | .------------------------------. | | V | | '-ELSE-----SQL-procedure-statement--;---+--' searched-case-statement-when-clause .-------------------------------------------------------------------. | .-------------------------------. | V V | | |------WHEN--search-condition--THEN-----SQL-procedure-statement--;---+--+-> >----+------------------------------------------+---------------| | .------------------------------. | | V | | '-ELSE-----SQL-procedure-statement--;---+--'
The CASE
statement has two general forms: one that uses a simple-case-statement-when-clause
, and another that uses a searched-case-statement-when-clause
.
In the simple-case-statement-when-clause
, the expression prior to the first WHEN
keyword is tested for equality with the value of each expression that follows the WHEN
keyword. If the expression results in the same value, the SQL
-procedure-statement
following the THEN
keyword is executed. Otherwise, comparisons are continued between the first expression and the expression following the next WHEN
clause. If the result does not match any of the search conditions and an ELSE
clause is present, the statements in the ELSE
clause are processed.
In a searched-case-statement-when-clause
, the search-condition
following each WHEN
keyword is evaluated. If search-condition
evaluates to true, the statements in the associated THEN
clause are processed. If it evaluates to false, the next search-condition
is evaluated. If no search-condition
evaluates to true and an ELSE
clause is present, the statements in the ELSE
clause are processed.
Both forms of the CASE statement require END CASE
to denote the end of the statement.
The example that you have already seen in Figure 4.8 could be rewritten as shown in Figure 4.10 using the simple-case-statement-when-clause
.
Example 4.10. A simple CASE example.
CREATE PROCEDURE demo_simple_case ( IN p_rating INT , IN p_employee_number char(6) ) LANGUAGE SQL SPECIFIC demo_simple_case -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries dsc: BEGIN -- Procedure logic CASE p_rating WHEN 1 THEN -- (1) Note: WHEN argument is a value UPDATE EMPLOYEE SET SALARY = SALARY *1.10, BONUS = 1000 WHERE EMPNO = p_employee_number; WHEN 2 THEN -- (2) UPDATE EMPLOYEE SET SALARY = SALARY *1.05, BONUS = 500 WHERE EMPNO = p_employee_number; ELSE UPDATE EMPLOYEE -- (3) SET SALARY = SALARY *1.03, BONUS = 0 WHERE EMPNO = p_employee_number; END CASE; END dsc
Once again, on Line (1) an employee with a rating of 1 can expect a raise of 10 percent and a bonus of $1,000. On Line (2), an employee with a rating of 2 earns a 5 percent raise and a bonus of $500, while on Line (3), all other employees can simply expect a raise of 3 percent and no bonus.
Perhaps there have been some recent changes to the rating system, where there is now a wider range of ratings that employees can receive. Now, two employees with slightly different ratings can earn the same raise and bonus. Obviously, the code needs to be updated.
Figure 4.11 reflects the changes to the rating system and shows how to handle this using a searched-case-statement-when-clause
. Note that the WHEN
clause now contains a condition.
Example 4.11. A searched CASE example.
CREATE PROCEDURE demo_searched_case ( IN p_rating INT , IN p_employee_number char(6) ) LANGUAGE SQL SPECIFIC demo_searched_case -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries dsc: BEGIN -- Procedure logic CASE WHEN p_rating >= 1 AND p_rating < 4 THEN -- (1) UPDATE EMPLOYEE SET SALARY = SALARY *1.10, BONUS = 1000 WHERE EMPNO = p_employee_number; WHEN p_rating >= 4 AND p_rating < 8 THEN UPDATE EMPLOYEE SET SALARY = SALARY *1.05, BONUS = 500 WHERE EMPNO = p_employee_number; ELSE UPDATE EMPLOYEE SET SALARY = SALARY *1.03, BONUS = 0 WHERE EMPNO = p_employee_number; END CASE; END dsc
As you can see, the code now handles a range of ratings for each condition of the CASE
statement. For example, on Line (1), an employee with a rating that falls between 1 and 3 inclusive will receive a raise of 10 percent and a bonus of $1,000.
Loops allow you to execute a set of statements repeatedly until a certain condition is reached. The loop-terminating condition may be defined at the beginning, in the middle, or at the end of the loop using the WHILE LOOP
, and REPEAT
statements, respectively. Also, a FOR
loop is available for iterating over a read-only result set and its terminating condition is when no more rows are left to read. Once the loop-terminating condition has been met, looping ceases and the flow of control continues on the line directly following the loop.
The WHILE
and REPEAT
loops are typically used when you do not know how many times to iterate through the loop prior to entering it. You should use the WHILE
loop when you may not want to execute the loop even once, and the REPEAT
loop when you want to ensure that the statements within the loop are executed at least once. The FOR
loop is used for situations where you need to iterate over a read-only result set, using result set values for some purpose such as defining the value of a variable. LOOP
is generally used if you have multiple exit conditions for the loop, perhaps at various possible locations.
The FOR
loop statement is supported on DB2 for LUW and iSeries only. It is used to iterate over a read-only result set that is defined by its select-statement
. Looping will cease when there are no rows left in the result set. You can easily use a WHILE
loop to achieve the same result in DB2 for zSeries.
The syntax is depicted in Figure 4.12.
Example 4.12. A FOR statement syntax diagram.
>>-+--------+--FOR--for-loop-name--AS---------------------------> '-label:-' >--+----------------------------------------------+-------------> | | '-cursor-name--CURSOR--+-----------+--FOR------' '-WITH HOLD-' .----------------------------. V | >--select-statement--DO----SQL-procedure-statement--;-+---------> >--END FOR--+-------+------------------------------------------>< '-label-'
The for-loop-name
specifies a label for the implicit compound statement generated to implement the FOR
statement. It follows the rules for the label of a compound statement. The for-loop-name
can used to qualify the column names in the result set as returned by the select-statement
.
The cursor-name
simply names the cursor that is used to select rows from the result set. If not specified, DB2 will automatically generate a unique cursor name internally.
The WITH HOLD
option dictates the behavior of cursors across multiple units of work. It is described in detail in Chapter 5, “Understanding and Using Cursors and Result Sets.”
The column names of the select-statement
must be unique, and a FROM
clause specifying a table (or multiple tables if doing some kind of JOIN
or UNION
) is required. The table(s) and column(s) referenced must exist prior to the loop being executed. This allows you to iterate over result sets that are formed from tables which exist prior to invoking the stored procedure, or tables that have been created by a previous SQL PL statement (such as declared user-temporary tables, which are discussed in Chapter 10, “Leveraging DB2 Application Development Features”).
The FOR
loop is essentially a CURSOR
defined by the select-statement
. This CURSOR
cannot be referenced outside of the FOR
loop, however, so OPEN
, FETCH
, and CLOSE
statements will result in error.
In Figure 4.13, the FOR
loop is used to iterate over all rows of the employee table (because no WHERE
clause is being used). For each row, it generates a full name from the values out of three columns—last name, first name, and middle name initial.
Example 4.13. A FOR loop example.
-- Example applies to LUW and iSeries only -- because of the usage of FOR loops. CREATE PROCEDURE demo_for_loop ( ) LANGUAGE SQL SPECIFIC demo_for_loop -- applies to LUW and iSeries dfl: BEGIN -- Declare variables DECLARE v_fullname VARCHAR(50); -- Procedure logic FOR v_row AS SELECT firstnme, midinit, lastname -- (1) FROM employee DO SET v_fullname = v_row.lastname || ', ' || v_row.firstnme || ' ' || v_row.midinit; -- (2) INSERT INTO tname VALUES (v_fullname); -- (3) END FOR; END dfl
You can see the defining select-statement
on Line (1) and where the columns of the result set are being concatenated together to form the v_fullname
on Line (2). Finally, this newly formed v_fullname
is inserted into a table called tname
on Line (3).
To test the procedure in Figure 4.13, you need to have a table created as
CREATE TABLE tname ( fullname VARCHAR(50) )
The defining feature of a WHILE
loop is that its looping condition is evaluated prior to initial loop execution and all following loop iterations. The WHILE
loop will continue to execute until the looping condition evaluates to false.
When defining the looping condition, be sure to specify a full conditional statement (that includes operators). Otherwise, your SQL procedure will not build. For example,
WHILE (variable) DO statement1; statement2; END WHILE;
is not enough. You need to use an operator, as in
WHILE (variable = 1) DO statement1; statement2; END WHILE;
The syntax for the WHILE
loop is illustrated in Figure 4.14.
Example 4.14. A WHILE loop syntax diagram.
>>-+---------+--WHILE--search-condition--DO---------------------> '-:label--' .-------------------------------. V | >--------SQL-procedure-statement--;---+--END WHILE--------------> >-----+--------+----------------------------------------------->< '-label--'
The search-condition
specifies a condition that is evaluated before each execution of the loop. If the condition is true, the SQL-procedure-statements
in the loop are processed.
Figure 4.15 illustrates how to use a WHILE
loop to sum all integer values between n and m (which are assumed to be positive and provided by input parameters to the procedure).
Example 4.15. A simple WHILE loop example.
CREATE PROCEDURE sum_mn ( IN p_start INT , IN p_end INT , OUT p_sum INT ) LANGUAGE SQL SPECIFIC sum_mn -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries smn: BEGIN DECLARE v_temp INTEGER DEFAULT 0; DECLARE v_current INTEGER; SET v_current = p_start; WHILE (v_current <= p_end) DO SET v_temp = v_temp + v_current; -- (1) SET v_current = v_current + 1; END WHILE; SET p_sum = v_temp; END smn
This example is fairly simple and is intended to show you how the WHILE
loop works using as little code as possible. More commonly, however, a WHILE
loop is used to repeatedly perform SQL procedure statements, such as FETCH
(for retrieving row values from a cursor). For examples of using WHILE
loops with cursor operations such as OPEN
, FETCH
and CLOSE
, see Chapter 5, “Understanding and Using Cursors and Result Sets.”
In the WHILE
loop, you saw that the looping condition is evaluated at the very beginning of the loop. If the looping condition evaluates to false at this first examination, then the loop body will not execute at all.
In some cases, however, it may be necessary that the loop be executed at least once. This is where the REPEAT
loop is useful. A REPEAT
loop ensures that at least one iteration of the loop is completed. This is the case because the looping condition is not evaluated until the final line of code in the loop.
The syntax for the REPEAT
loop is shown in Figure 4.16.
Example 4.16. A REPEAT loop syntax diagram.
.-------------------------------. V | >>-+---------+--REPEAT-------SQL-procedure-statement--;---+-----> '-label:--' >----UNTIL--search-condition---END REPEAT----+--------+-------->< '-label--'
In Figure 4.17, the procedure in Figure 4.15 is re-implemented using REPEAT
.
Example 4.17. A REPEAT loop example.
CREATE PROCEDURE sum_mn2 ( IN p_start INT , IN p_end INT , OUT p_sum INT ) LANGUAGE SQL SPECIFIC sum_mn2 -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries smn2: BEGIN DECLARE v_temp INTEGER DEFAULT 0; DECLARE v_current INTEGER; SET v_current = p_start; REPEAT SET v_temp = v_temp + v_current; -- (1) SET v_current = v_current + 1; UNTIL (v_current > p_end) END REPEAT; SET p_sum = v_temp; END smn2
If you execute both the WHILE
loop example in Figure 4.15 and the REPEAT
loop example in Figure 4.17 with p_start
smaller than p_end
, you will see the difference between the two. The WHILE
loop example in Figure 4.15 generates p_sum
with 0. The statement on Line (1) in Figure 4.15 is never executed because the condition in the WHILE
clause is not met. The REPEAT
loop example generates p_sum
with the value of p_start
instead because the statement on Line (1) in Figure 4.17 is always executed once.
The LOOP
statement is somewhat different from the other types of loops that you have seen thus far. The LOOP
does not have a terminating condition clause that is part of its declaration statement. It will continue to loop until some other piece of code inside it explicitly forces the flow of control to jump to some point outside of the loop.
LOOP
will commonly have some logic that eventually branches to a LEAVE
statement. You can also use a GOTO
statement instead of a LEAVE
, but the use of GOTO
is discouraged.
The LOOP
syntax is illustrated in Figure 4.18.
Example 4.18. A LOOP syntax diagram.
.-------------------------------. V | >>-+---------+--LOOP-------SQL-procedure-statement--;---+-------> '-label:--' >----END LOOP----+--------+------------------------------------>< '-label--'
An example of using LOOP
is deferred until the discussion on LEAVE in the next section.
Transfer of control statements are used to tell the SQL procedure where to continue execution. This unconditional branching can be used to cause the flow of control to jump from one point to another point, which can either precede or follow the transfer of control statement.
SQL PL supports four such statements: GOTO
, LEAVE
, ITERATE
, and RETURN
. Each will be discussed in detail in the following sections.
ROLLBACK
and COMMIT
statements can also be used within the procedure body. These two are introduced here for completeness, even though they are more transaction control statements than flow of control statements.
GOTO
is a straightforward and basic flow of control statement that causes an unconditional change in the flow of control. It is used to branch to a specific user-defined location using labels defined in the procedure.
Usage of the GOTO
statement is generally considered to be poor programming practice and is not recommended. Extensive use of GOTO
tends to lead to unreadable code especially when procedures grow long. Besides, GOTO
is not necessary because there are better statements available to control the execution path. There are no specific situations that require the use of GOTO
; instead, it is more often used for convenience (or lack of effort).
The GOTO
syntax is shown in Figure 4.19.
Example 4.19. The GOTO syntax diagram.
>>-GOTO--label-------------------------------------------------><
You should be aware of a few additional scope considerations:
If the GOTO
statement is defined in a FOR
statement, the label must be defined inside the same FOR
statement, unless it is in a nested FOR
statement or nested compound statement.
If the GOTO
statement is defined in a compound statement, label
must be defined inside the same compound statement, unless it is in a nested FOR
statement or nested compound statement.
If the GOTO
statement is defined in a handler, label
must be defined in the same handler, following the other scope rules.
If the GOTO
statement is defined outside of a handler, label
must not be defined within a handler.
If label
is not defined within a scope that the GOTO
statement can reach, an error is returned (SQLSTATE 42736
).
Good programming practice should limit the use of the GOTO
statement in your SQL procedure. The use of GOTO decreases the readability of your code since it causes execution to jump to a new line contained anywhere within the procedure body. This spaghetti code can be difficult to understand, debug, and maintain.
The GOTO
statement is local to the SQL procedure which declares it. The label that a GOTO
statement could jump to must be defined within the same SQL procedure as the GOTO
statement, and don’t forget that scoping rules still apply.
In Figure 4.20, an SQL procedure is used to increase the salary of those employees who have been with the company for more than one year. The employee’s serial number and rating are passed into the stored procedure, which then returns an output parameter of the newly calculated salary. The employee’s salary is increased based on his or her rating.
Example 4.20. Another GOTO example.
CREATE PROCEDURE adjust_salary ( IN p_empno CHAR(6) , IN p_rating INTEGER , OUT p_adjusted_salary DECIMAL (8,2) ) LANGUAGE SQL SPECIFIC adjust_salary -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries ads: BEGIN -- Declare variables DECLARE v_new_salary DECIMAL (9,2); DECLARE v_service DATE; -- Procedure logic SELECT salary, hiredate INTO v_new_salary, v_service FROM employee WHERE empno = p_empno; IF v_service > (CURRENT DATE - 1 year) THEN GOTO bye; -- (1) END IF; IF p_rating = 1 THEN SET v_new_salary = v_new_salary + (v_new_salary * .10); -- (2) ELSEIF p_rating = 2 THEN SET v_new_salary = v_new_salary + (v_new_salary * .05); -- (3) END IF; UPDATE employee -- (4) SET salary = v_new_salary WHERE empno = p_empno; bye: -- (5) SET p_adjusted_salary = v_new_salary; END ads
This SQL procedure makes use of the GOTO
statement on Line (1) to avoid increasing the salary of those employees who have not yet been with the company for more than a year.
To try the example on your own, use any valid employee number from the EMPNO
column in the EMPLOYEE
table as p_empno
. Use either 1 or 2 for p_rating
.
CALL adjust_salary('000010', 2, ?)
If the employee has worked for the company for more than a year, he or she is given a 5 or 10 percent raise if he or she received a rating of 2 or 1, respectively, on Lines (1) and (3). The EMPLOYEE
table is updated to reflect the new salary on Line (4).
If it is discovered that the employee has not yet worked with the company for at least one year, the GOTO
exit statement causes execution to jump to the second-last line of code on Line (5) in the procedure. The p_adjusted_salary
is simply set to the original salary, and no changes are made to the EMPLOYEE
table.
The LEAVE
statement is used to transfer the flow of control out of a loop or compound statement. The syntax for the command, shown in Figure 4.21, is trivial.
Example 4.21. The LEAVE syntax diagram.
>>-LEAVE--label------------------------------------------------><
Figure 4.22 illustrates how to use LOOP
and LEAVE
.
Example 4.22. An example of LOOP and LEAVE.
CREATE PROCEDURE verify_ids ( IN p_id_list VARCHAR(100) , OUT p_status INT ) LANGUAGE SQL SPECIFIC verify_ids -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries vid: BEGIN DECLARE v_current_id VARCHAR(10); DECLARE v_position INT; DECLARE v_remaining_ids VARCHAR(100); DECLARE v_tmp INT; DECLARE SQLCODE INT DEFAULT 0; SET v_remaining_ids = p_id_list; SET p_status = 0; L1: LOOP SET v_position = LOCATE (':',v_remaining_ids); --(1) -- take off the first id from the list SET v_current_id = SUBSTR (v_remaining_ids, 1, v_position); --(2) IF LENGTH(v_remaining_ids) - v_position > 0 THEN --(3) SET v_remaining_ids = SUBSTR (v_remaining_ids, v_position+1); ELSE SET v_remaining_ids = ''; END IF; -- take off the colon in last position of the current token SET v_current_id = SUBSTR (v_current_id, 1, v_position-1); --(4) -- determine if employee exists SELECT 1 INTO v_tmp FROM employee where empno = v_current_id; --(5) IF (SQLCODE <> 0) THEN -- employee id does not exist SET p_status=-1; LEAVE L1; --(6) END IF; IF length(v_remaining_ids) = 0 THEN LEAVE L1; END IF; END LOOP; END vid
The SQL procedure in Figure 4.22 takes a colon-separated list of employee IDs as input. For example, this input might look like:
000310:000320:000330:
The list is then parsed—in Lines (1) through (4)—to determine if all employee IDs are valid by verifying if the employee exists on Line (5). If any IDs in the list are not valid, the LOOP
immediately exits using LEAVE
on Line (6). If all employee IDs in the list are valid, the result of p_status
is 0. Otherwise, the result of p_status
is -1 to indicate an error.
The ITERATE
statement is used to cause the flow of control to return to the beginning of a labeled LOOP
. The syntax for ITERATE
, depicted in Figure 4.23, is simple.
Example 4.23. An ITERATE syntax diagram.
>>-ITERATE--label----------------------------------------------><
The example in Figure 4.24 is similar to the example in Figure 4.22, except that instead of exiting on the first invalid employee ID, the procedure returns the number of valid IDs found. ITERATE
at Line (1) is used to return to the top of the LOOP
whenever an invalid ID is encountered so that it is not counted.
Example 4.24. An ITERATE example.
CREATE PROCEDURE verify_ids2 ( IN p_id_list VARCHAR(100) , OUT p_status INT ) LANGUAGE SQL SPECIFIC verify_ids2 -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries vid: BEGIN DECLARE v_current_id VARCHAR(10); DECLARE v_position INT; DECLARE v_remaining_ids VARCHAR(100); DECLARE v_tmp INT; DECLARE SQLCODE INT DEFAULT 0; SET v_remaining_ids = p_id_list; SET p_status = 0; L1: LOOP SET v_position = LOCATE (':',v_remaining_ids); -- take off the first id from the list SET v_current_id = SUBSTR (v_remaining_ids, 1, v_position); IF LENGTH(v_remaining_ids) - v_position > 0 THEN SET v_remaining_ids = SUBSTR (v_remaining_ids, v_position+1); ELSE SET v_remaining_ids = ''; END IF; -- take off the colon in last position of the current token SET v_current_id = SUBSTR (v_current_id, 1, v_position-1); -- determine if employee exists SELECT 1 INTO v_tmp FROM employee where empno = v_current_id; IF (SQLCODE <> 0) THEN -- employee id does not exist IF length(v_remaining_ids) > 0 THEN ITERATE L1; --(1) ELSE LEAVE L1; END IF; END IF; SET p_status = p_status + 1; IF length(v_remaining_ids) = 0 THEN LEAVE L1; END IF; END LOOP; END vid
RETURN
is used to unconditionally and immediately terminate an SQL procedure by returning the flow of control to the caller of the stored procedure.
It is mandatory that when RETURN
statement is issued, it returns an integer value. If the return value is not provided, the default is 0. The value returned is typically used to indicate success or failure of the stored procedure’s execution. This value can be a literal, variable, or an expression as long as it is an integer or evaluates to an integer.
You can make use of more than one RETURN
statement in a stored procedure. RETURN
can be used anywhere after the declaration blocks within the SQL procedure body.
The partial syntax for RETURN
in the SQL procedure is illustrated in Figure 4.25.
Example 4.25. The RETURN syntax diagram.
>>-RETURN--+------------+--------------------------------------><
'-expression-'
The following example uses the employee serial number (p_empno
) to check if an employee’s last name, as stored in the database, matches the last name passed in as an input parameter (p_emplastname
).
Example 4.26. A RETURN example.
CREATE PROCEDURE return_test ( IN p_empno CHAR(6) , IN p_emplastname VARCHAR(15) ) LANGUAGE SQL SPECIFIC return_test -- applies to LUW and iSeries -- WLM ENVIRONMENT <env> -- applies to zSeries rt: BEGIN -- Declare variables DECLARE v_lastname VARCHAR(15); -- Procedure logic SELECT lastname INTO v_lastname FROM EMPLOYEE WHERE empno = p_empno; IF v_lastname = p_emplastname THEN -- (1) RETURN 1; -- (2) ELSE -- (3) RETURN -1; -- (4) END IF; END rt
This procedure receives two input parameters: p_emplastname
and p_empno
. If p_emplastname
matches the lastname
in the employee
table identified by the employee number (p_empno
) at Line (1), then the procedure exits with a return value of 1 at Line (2) implying success. If there is no match as shown in Line (3), then the SQL procedure returns with a failure indicated by a -1 return code on Line (4).
The COMMIT
statement is used to complete the current unit of work, and to permanently record any of the changes made inside it to the database.
The syntax is trivial (see Figure 4.27).
Example 4.27. COMMIT statement syntax.
.-WORK--. >>-COMMIT----+-------+-----------------------------------------><
A HOLD
option is also provided for the COMMIT
clause in DB2 for iSeries. If the COMMIT is invoked with the HOLD
option, any open cursors are not closed, and any resources (except locks on table rows) acquired during the unit of work are held. The similar behavior in DB2 for LUW and zSeries is defined at the cursor declaration time. Refer to the FOR
loop discussion in this chapter and Chapter 5, “Understanding and Using Cursors and Result Sets,” for more details.
The ROLLBACK
statement is used to explicitly back out of any database changes that were made within the current unit of work (UOW). A unit of work is a sequence of SQL statements that are atomic for the purposes of recovery. Once the changes have been rolled back, a new unit of work is initiated.
DB2 also supports transaction save points and ROLLBACK TO SAVEPOINT
. A ROLLBACK
will cause the flow of control in your application to return to the previous save point declared within your unit of work.
The syntax for the ROLLBACK
statement is shown in Figure 4.28.
Example 4.28. ROLLBACK statement syntax.
.-WORK--. >>-ROLLBACK----+-------+----------------------------------------> >-----+------------------------------------+------------------->< '-TO SAVEPOINT--+-----------------+--' '-savepoint-name--'
ROLLBACK TO SAVEPOINT
indicates that a partial rollback is to be performed. If no save point is active, an SQL error is returned (SQLSTATE 3B001
).
The save point name indicates which save point to rollback to. After a successful ROLLBACK TO SAVEPOINT
, the save point defined by save point name continues to exist. If the save point name does not exist, an error is returned (SQLSTATE 3B001
). Data and schema changes made since the save point was set are undone.
To rollback the entire transaction, use the ROLLBACK WORK
statement. All save points within the transaction are also released. For more information about save points, refer to Chapter 10, “Leveraging DB2 Application Development Features.”
In addition to the WORK
and TO SAVEPOINT
options of the ROLLBACK
statement, DB2 UDB for iSeries also supports the HOLD
option. When the ROLLBACK
statement is issued with this option, any open cursors are not closed, and any resources (except locks on table rows) acquired during the unit of work are held.
This chapter discussed all SQL PL elements related to flow of control. This included discussions and examples of the following:
Compound statements and scope, which can be defined as ATOMIC
or NOT ATOMIC
to suit your needs
Labels and the various ways in which they are used
IF
and CASE
conditional statements (and their various forms) for more intelligent SQL procedures
Looping statements FOR
, WHILE
, REPEAT
, and LOOP
to perform repetitive sets of SQL statements
GOTO
, LEAVE
, ITERATE
, and RETURN
transfer of control statements
COMMIT
and ROLLBACK
transaction control statements
With these flow of control statements at your disposal, you can write powerful and robust SQL stored procedures.
18.119.248.13