PL/SQLには、条件付き選択文、繰り返し文および順次制御文の3つのカテゴリの制御文があります。
PL/SQLのカテゴリの制御文は、次のとおりです。
条件付き選択文: データ値に応じて、異なる文を実行します。
条件選択文は、IF
およびCASE
です。
ループ文: 一連の異なるデータ値を使用して、同じ文を実行します。
ループ文には、基本LOOP
、FOR
LOOP
およびWHILE
LOOP
があります。
EXIT
文によって、ループの最後に制御が移ります。CONTINUE
文は、現行のループの反復を終了し、次の反復に制御を移します。EXIT
およびCONTINUE
のいずれにもオプションのWHEN
句があり、これによって条件を指定できます。
順次制御文: PL/SQLのプログラミングでは重要ではありません。
順次制御文には、指定された文に移動するGOTO
および何も実行しないNULL
があります。
ここでのトピック
条件付き選択文のIF
およびCASE
は、データ値の違いに応じて異なる文を実行します。
IF
文は、条件に応じて、一連の1つ以上の文を実行またはスキップします。IF
文には次の形式があります。
IF
THEN
IF
THEN
ELSE
IF
THEN
ELSIF
CASE
文を使用すると、一連の条件を基に、対応する文を選択して実行できます。CASE
文には次の形式があります。
単純: 単一の式を評価して、可能性のある複数の値と比較します。
検索: 複数の条件を評価して、最初にTRUEである条件を選択します。
CASE
文は、オプションごとにアクションが異なる場合に適しています。
ここでのトピック
IF
THEN
文は、条件に応じて、一連の1つ以上の文を実行またはスキップします。
IF
THEN
文の構造は、次のとおりです。
IF condition THEN statements END IF;
condition
がTRUEの場合、statements
が実行されますが、それ以外の場合、IF
文は何も実行しません。
構文の詳細は、「IF文」を参照してください。
ヒント:
次のようなIF
文の使用は避けてください。
IF new_balance < minimum_balance THEN overdrawn := TRUE; ELSE overdrawn := FALSE; END IF;
かわりに、BOOLEAN
式の値をBOOLEAN
変数に直接代入してください。
overdrawn := new_balance < minimum_balance;
BOOLEAN
変数は、TRUE
、FALSE
、NULL
のいずれかです。次のようには記述しないでください。
IF overdrawn = TRUE THEN RAISE insufficient_funds; END IF;
かわりに、次のように記述してください。
IF overdrawn THEN RAISE insufficient_funds; END IF;
例4-1 IF THEN文
この例では、sales
の値がquota
+200より大きい場合にのみ、THEN
とEND
IF
の間にある文が実行されます。
DECLARE PROCEDURE p ( sales NUMBER, quota NUMBER, emp_id NUMBER ) IS bonus NUMBER := 0; updated VARCHAR2(3) := 'No'; BEGIN IF sales > (quota + 200) THEN bonus := (sales - quota)/4; UPDATE employees SET salary = salary + bonus WHERE employee_id = emp_id; updated := 'Yes'; END IF; DBMS_OUTPUT.PUT_LINE ( 'Table updated? ' || updated || ', ' || 'bonus = ' || bonus || '.' ); END p; BEGIN p(10100, 10000, 120); p(10500, 10000, 121); END; /
結果:
Table updated? No, bonus = 0. Table updated? Yes, bonus = 125.
IF
THEN
ELSE
文の構造は、次のとおりです。
IF condition THEN statements ELSE else_statements END IF;
condition
の値がTRUEの場合、statements
が実行されますが、それ以外の場合は、else_statements
が実行されます。(構文の詳細は、「IF文」を参照してください。)
例4-2では、sales
の値がquota
+200より大きい場合にのみ、THEN
とELSE
の間にある文が実行されます。それ以外の場合は、ELSE
とEND
IF
の間にある文が実行されます。
例4-3に示すように、IF
文はネストできます。
例4-2 IF THEN ELSE文
DECLARE PROCEDURE p ( sales NUMBER, quota NUMBER, emp_id NUMBER ) IS bonus NUMBER := 0; BEGIN IF sales > (quota + 200) THEN bonus := (sales - quota)/4; ELSE bonus := 50; END IF; DBMS_OUTPUT.PUT_LINE('bonus = ' || bonus); UPDATE employees SET salary = salary + bonus WHERE employee_id = emp_id; END p; BEGIN p(10100, 10000, 120); p(10500, 10000, 121); END; /
結果:
bonus = 50 bonus = 125
例4-3 ネストしたIF THEN ELSE文
DECLARE PROCEDURE p ( sales NUMBER, quota NUMBER, emp_id NUMBER ) IS bonus NUMBER := 0; BEGIN IF sales > (quota + 200) THEN bonus := (sales - quota)/4; ELSE IF sales > quota THEN bonus := 50; ELSE bonus := 0; END IF; END IF; DBMS_OUTPUT.PUT_LINE('bonus = ' || bonus); UPDATE employees SET salary = salary + bonus WHERE employee_id = emp_id; END p; BEGIN p(10100, 10000, 120); p(10500, 10000, 121); p(9500, 10000, 122); END; /
結果:
bonus = 50 bonus = 125 bonus = 0
IF
THEN
ELSIF
文の構造は、次のとおりです。
IF condition_1 THEN statements_1 ELSIF condition_2 THEN statements_2 [ ELSIF condition_3 THEN statements_3 ]... [ ELSE else_statements ] END IF;
IF
THEN
ELSIF
文は、condition
がTRUEである最初のstatements
を実行します。残りの条件は評価されません。TRUEのcondition
がない場合は、else_statements
(存在する場合)が実行されますが、else_statementsも存在しない場合、IF
THEN
ELSIF
文は何も実行しません。(構文の詳細は、「IF文」を参照してください。)
例4-4では、sales
の値が50000より大きい場合、1番目と2番目の条件のいずれもTRUEになります。ただし、1番目の条件がTRUEであるため、bonus
には1500という値が代入され、2番目の条件はテストされません。bonus
に値1500が代入された後、制御はDBMS_OUTPUT
.PUT_LINE
の起動に移ります。
単一のIF
THEN
ELSIF
文は、論理的に等価なネストしたIF
THEN
ELSE
文より理解しやすい文です。
-- IF THEN ELSIF statement IF condition_1 THEN statements_1; ELSIF condition_2 THEN statements_2; ELSIF condition_3 THEN statement_3; END IF; -- Logically equivalent nested IF THEN ELSE statements IF condition_1 THEN statements_1; ELSE IF condition_2 THEN statements_2; ELSE IF condition_3 THEN statements_3; END IF; END IF; END IF;
例4-5では、IF
THEN
ELSIF
文を多数のELSIF
句とともに使用し、単一の値を多くの使用可能な値と比較しています。この目的では、単純なCASE
文の方がより明確になります(例4-6を参照)。
例4-4 IF THEN ELSIF文
DECLARE PROCEDURE p (sales NUMBER) IS bonus NUMBER := 0; BEGIN IF sales > 50000 THEN bonus := 1500; ELSIF sales > 35000 THEN bonus := 500; ELSE bonus := 100; END IF; DBMS_OUTPUT.PUT_LINE ( 'Sales = ' || sales || ', bonus = ' || bonus || '.' ); END p; BEGIN p(55000); p(40000); p(30000); END; /
結果:
Sales = 55000, bonus = 1500. Sales = 40000, bonus = 500. Sales = 30000, bonus = 100.
例4-5 単純なCASE文をシミュレートするIF THEN ELSIF文
DECLARE grade CHAR(1); BEGIN grade := 'B'; IF grade = 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent'); ELSIF grade = 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good'); ELSIF grade = 'C' THEN DBMS_OUTPUT.PUT_LINE('Good'); ELSIF grade = 'D' THEN DBMS_OUTPUT. PUT_LINE('Fair'); ELSIF grade = 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor'); ELSE DBMS_OUTPUT.PUT_LINE('No such grade'); END IF; END; /
結果:
Very Good
単純なCASE
文の構造は、次のとおりです。
CASE selector WHEN selector_value_1 THEN statements_1 WHEN selector_value_2 THEN statements_2 ... WHEN selector_value_n THEN statements_n [ ELSE else_statements ] END CASE;]
selector
は式です(通常は1つの変数)。各selector_value
はリテラルまたは式のいずれかです。(構文の詳細は、「CASE文」を参照してください。)
単純なCASE
文は、selector_value
がselector
と等しい最初のstatements
を実行します。残りの条件は評価されません。いずれのselector_value
もselector
と等しくない場合、CASE
文はelse_statements
(存在する場合)を実行しますが、else_statementsが存在しない場合は、事前定義の例外CASE_NOT_FOUND
を呼び出します。
例4-6では、単純なCASE
文を使用して、単一の値を多くの使用可能な値と比較しています。例4-6のCASE
文は、例4-5のIF
THEN
ELSIF
文と論理的に等価です。
注意:
単純なCASE
式と同様に、単純なCASE
文の選択子の値がNULL
の場合、その選択子がWHEN
NULL
によって一致することはありません(例2-51を参照)。かわりに、検索CASE
文をWHEN
condition
IS
NULL
とともに使用します(例2-53 を参照)。
例4-6 単純なCASE文
DECLARE grade CHAR(1); BEGIN grade := 'B'; CASE grade WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent'); WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good'); WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('Good'); WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair'); WHEN 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor'); ELSE DBMS_OUTPUT.PUT_LINE('No such grade'); END CASE; END; /
結果:
Very Good
検索CASE
文の構造は、次のとおりです。
CASE WHEN condition_1 THEN statements_1 WHEN condition_2 THEN statements_2 ... WHEN condition_n THEN statements_n [ ELSE else_statements ] END CASE;]
検索CASE
文は、condition
がTRUEである最初のstatements
を実行します。残りの条件は評価されません。TRUEのcondition
がない場合、CASE
文はelse_statements
(存在する場合)を実行しますが、else_statementsも存在しない場合は、事前定義の例外CASE_NOT_FOUND
を呼び出します。(構文の詳細は、「CASE文」を参照してください。)
例4-7
の検索CASE文は、例4-6
の単純なCASE文と論理的に等価です。
例4-7と例4-6のいずれの場合も、ELSE
句をEXCEPTION
部に置き換えることができます。例4-8は、例4-7と論理的に等価です。
例4-7 検索CASE文
DECLARE grade CHAR(1); BEGIN grade := 'B'; CASE WHEN grade = 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent'); WHEN grade = 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good'); WHEN grade = 'C' THEN DBMS_OUTPUT.PUT_LINE('Good'); WHEN grade = 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair'); WHEN grade = 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor'); ELSE DBMS_OUTPUT.PUT_LINE('No such grade'); END CASE; END; /
結果:
Very Good
例4-8 CASE文でのELSE句にかわるEXCEPTION
DECLARE grade CHAR(1); BEGIN grade := 'B'; CASE WHEN grade = 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent'); WHEN grade = 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good'); WHEN grade = 'C' THEN DBMS_OUTPUT.PUT_LINE('Good'); WHEN grade = 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair'); WHEN grade = 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor'); END CASE; EXCEPTION WHEN CASE_NOT_FOUND THEN DBMS_OUTPUT.PUT_LINE('No such grade'); END; /
結果:
Very Good
ループ文は、一連の異なる値を使用して、同じ文を実行します。ループ文には次のものがあります。
基本LOOP
FOR
LOOP
カーソルFOR
LOOP
WHILE
LOOP
ループを終了する文には次のものがあります。
EXIT
EXIT
WHEN
現行のループの反復を終了する文には次のものがあります。
CONTINUE
CONTINUE
WHEN
EXIT
、EXIT
WHEN
、CONTINUE
およびCONTINUE
WHEN
は、ループ内の任意の場所に置くことができますが、ループの外に置くことはできません。ループの外の文に制御を移してループまたは現行のループの反復を終了することができるGOTO文のかわりに、これらの文を使用することをお薦めします。(例外が呼び出された場合もループは終了します。例外の詳細は、「例外処理の概要」を参照してください。)
LOOP
文にはラベルを付けることができ、LOOP
文をネストすることもできます。わかりやすさを向上させるために、ネステッド・ループにはラベルを付けることをお薦めします。END
LOOP
文のラベルと同じループ文の先頭のラベルが一致していることを確認する必要があります(コンパイラはチェックしません)。
ここでのトピック
カーソルFOR
LOOP
の詳細は、「カーソルFOR LOOP文による問合せ結果セットの処理」を参照してください。
基本LOOP
文の構造は、次のとおりです。
[ label ] LOOP statements END LOOP [ label ];
ループが反復されるたびにstatements
が実行され、制御がループの先頭に戻ります。無限ループが発生しないように、文または例外の呼び出しによってループを終了する必要があります。
関連項目:
EXIT
文は、現行のループの反復を無条件で終了し、カレント・ループまたはラベルが付けられている外側のループのいずれかの最後に制御を移します。
例4-9では、基本LOOP
文内のEXIT
文が無条件でカレント・ループの最後に制御を移します。
関連項目:
例4-9 EXIT文が含まれている基本LOOP文
DECLARE x NUMBER := 0; BEGIN LOOP DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR(x)); x := x + 1; IF x > 3 THEN EXIT; END IF; END LOOP; -- After EXIT, control resumes here DBMS_OUTPUT.PUT_LINE(' After loop: x = ' || TO_CHAR(x)); END; /
結果:
Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop: x = 3 After loop: x = 4
EXIT
WHEN
文は、WHEN
句内の条件がTRUEの場合、現行のループの反復を終了し、カレント・ループまたはラベルが付けられている外側のループのいずれかの最後に制御を移します。
制御がEXIT
WHEN
文に達するたびに、WHEN
句内の条件が評価されます。条件がTRUEでない場合、EXIT
WHEN
文は何も実行しません。例4-10に示すように、無限ループが発生しないように、ループ内の文で条件がTRUEになるようにする必要があります。
例4-10では、x
が3より大きい場合、基本LOOP
文内のEXIT
WHEN
文がカレント・ループの最後に制御を移します。例4-10は例4-9と論理的に等価です。
関連項目:
例4-11では、基本LOOP
文が別の基本LOOP文にネストされていて、両方にラベルが付けられています。内部ループにはEXIT
WHEN
文が2つあり、1つは内部ループを終了し、もう1つは外部ループを終了します。
内部ループ内のEXIT
WHEN
文から外部ループに制御を移すことができるのは、外部ループにラベルが付けられている場合のみです。
例4-12では、外部ループにはラベルが付いていないため、内部ループは制御を外部ループに移すことができません。
例4-10 EXIT WHEN文が含まれている基本LOOP文
DECLARE x NUMBER := 0; BEGIN LOOP DBMS_OUTPUT.PUT_LINE('Inside loop: x = ' || TO_CHAR(x)); x := x + 1; -- prevents infinite loop EXIT WHEN x > 3; END LOOP; -- After EXIT statement, control resumes here DBMS_OUTPUT.PUT_LINE('After loop: x = ' || TO_CHAR(x)); END; /
結果:
Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop: x = 3 After loop: x = 4
例4-11 EXIT WHEN文が含まれているネストされたラベル付き基本LOOP文
DECLARE s PLS_INTEGER := 0; i PLS_INTEGER := 0; j PLS_INTEGER; BEGIN <<outer_loop>> LOOP i := i + 1; j := 0; <<inner_loop>> LOOP j := j + 1; s := s + i * j; -- Sum several products EXIT inner_loop WHEN (j > 5); EXIT outer_loop WHEN ((i * j) > 15); END LOOP inner_loop; END LOOP outer_loop; DBMS_OUTPUT.PUT_LINE ('The sum of products equals: ' || TO_CHAR(s)); END; /
結果:
The sum of products equals: 166
例4-12 EXIT WHEN文が含まれているネストされたラベルなしの基本LOOP文
DECLARE i PLS_INTEGER := 0; j PLS_INTEGER := 0; BEGIN LOOP i := i + 1; DBMS_OUTPUT.PUT_LINE ('i = ' || i); LOOP j := j + 1; DBMS_OUTPUT.PUT_LINE ('j = ' || j); EXIT WHEN (j > 3); END LOOP; DBMS_OUTPUT.PUT_LINE ('Exited inner loop'); EXIT WHEN (i > 2); END LOOP; DBMS_OUTPUT.PUT_LINE ('Exited outer loop'); END; /
結果:
i = 1 j = 1 j = 2 j = 3 j = 4 Exited inner loop i = 2 j = 5 Exited inner loop i = 3 j = 6 Exited inner loop Exited outer loop PL/SQL procedure successfully completed.
CONTINUE
文は、現行のループの反復を無条件で終了し、カレント・ループまたはラベルが付けられている外側のループのいずれかの次の反復に制御を移します。
例4-13では、基本LOOP
文内のCONTINUE
文が無条件でカレント・ループの次の反復に制御を移します。
関連項目:
例4-13 基本LOOP文内のCONTINUE文
DECLARE x NUMBER := 0; BEGIN LOOP -- After CONTINUE statement, control resumes here DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR(x)); x := x + 1; IF x < 3 THEN CONTINUE; END IF; DBMS_OUTPUT.PUT_LINE ('Inside loop, after CONTINUE: x = ' || TO_CHAR(x)); EXIT WHEN x = 5; END LOOP; DBMS_OUTPUT.PUT_LINE (' After loop: x = ' || TO_CHAR(x)); END; /
結果:
Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop, after CONTINUE: x = 3 Inside loop: x = 3 Inside loop, after CONTINUE: x = 4 Inside loop: x = 4 Inside loop, after CONTINUE: x = 5 After loop: x = 5
CONTINUE
WHEN
文は、WHEN
句内の条件がTRUEの場合、現行のループの反復を終了し、カレント・ループまたはラベルが付けられている外側のループのいずれかの次の反復に制御を移します。
制御がCONTINUE
WHEN
文に達するたびに、WHEN
句内の条件が評価されます。条件がTRUEでない場合、CONTINUE
WHEN
文は何も実行しません。
例4-14では、x
が3より小さい場合、基本LOOP
文内のCONTINUE
WHEN
文がカレント・ループの次の反復に制御を移します。例4-14は例4-13と論理的に等価です。
関連項目:
例4-14 基本LOOP文内のCONTINUE WHEN文
DECLARE x NUMBER := 0; BEGIN LOOP -- After CONTINUE statement, control resumes here DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR(x)); x := x + 1; CONTINUE WHEN x < 3; DBMS_OUTPUT.PUT_LINE ('Inside loop, after CONTINUE: x = ' || TO_CHAR(x)); EXIT WHEN x = 5; END LOOP; DBMS_OUTPUT.PUT_LINE (' After loop: x = ' || TO_CHAR(x)); END; /
結果:
Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop, after CONTINUE: x = 3 Inside loop: x = 3 Inside loop, after CONTINUE: x = 4 Inside loop: x = 4 Inside loop, after CONTINUE: x = 5 After loop: x = 5
FOR
LOOP
文は、指定された範囲にループ索引がある場合に1つ以上の文を実行します。この文の構造は、次のとおりです。
[ label ] FOR index IN [ REVERSE ] lower_bound..upper_bound LOOP statements END LOOP [ label ];
REVERSE
が指定されていない場合、index
の値はlower_bound
から始まり、upper_bound
に達するまで、ループを反復するたびに1ずつ増加します。lower_bound
がupper_bound
よりも大きい場合、statements
は実行されません。
REVERSE
が指定されている場合、index
の値はupper_bound
から始まり、lower_bound
に達するまで、ループを反復するたびに1ずつ減少します。upper_bound
がlower_bound
よりも小さい場合、statements
は実行されません。
EXIT
、EXIT
WHEN
、CONTINUE
またはCONTINUE
WHEN
をstatements
内に置くと、ループまたは現行のループの反復を途中で終了させることができます。
ヒント:
問合せの結果セットの行を処理するには、整数の範囲のかわりに問合せが含まれているカーソルFOR
LOOP
を使用してください。詳細は、「 カーソルFOR LOOP文による問合せ結果セットの処理」を参照してください。
関連項目:
例4-15では、index
はi
、lower_bound
は1、upper_bound
は3です。このループは1から3の数値を出力します。
例4-16のFOR
LOOP
文は、例4-15を反転したものであり、3から1の数値を出力します。
一部の言語のFOR
LOOP
には、ループ索引の増分値を1以外に指定することが可能なSTEP
句があります。PL/SQLでSTEP
句をシミュレートするには、ループ索引の参照のたびに、必要な増分値を乗じます。
例4-17では、FOR
LOOP
の索引が実質的に5ずつ増加します。
ここでのトピック
例4-15 FOR LOOP文
BEGIN DBMS_OUTPUT.PUT_LINE ('lower_bound < upper_bound'); FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE (i); END LOOP; DBMS_OUTPUT.PUT_LINE ('lower_bound = upper_bound'); FOR i IN 2..2 LOOP DBMS_OUTPUT.PUT_LINE (i); END LOOP; DBMS_OUTPUT.PUT_LINE ('lower_bound > upper_bound'); FOR i IN 3..1 LOOP DBMS_OUTPUT.PUT_LINE (i); END LOOP; END; /
結果:
lower_bound < upper_bound 1 2 3 lower_bound = upper_bound 2 lower_bound > upper_bound
例4-16 反転FOR LOOP文
BEGIN DBMS_OUTPUT.PUT_LINE ('upper_bound > lower_bound'); FOR i IN REVERSE 1..3 LOOP DBMS_OUTPUT.PUT_LINE (i); END LOOP; DBMS_OUTPUT.PUT_LINE ('upper_bound = lower_bound'); FOR i IN REVERSE 2..2 LOOP DBMS_OUTPUT.PUT_LINE (i); END LOOP; DBMS_OUTPUT.PUT_LINE ('upper_bound < lower_bound'); FOR i IN REVERSE 3..1 LOOP DBMS_OUTPUT.PUT_LINE (i); END LOOP; END; /
結果:
upper_bound > lower_bound 3 2 1 upper_bound = lower_bound 2 upper_bound < lower_bound
例4-17 FOR LOOP文でのSTEP句のシミュレート
DECLARE step PLS_INTEGER := 5; BEGIN FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE (i*step); END LOOP; END; /
結果:
5 10 15
FOR
LOOP
文の索引は、ループに対してローカルであるPLS_INTEGER
型の変数として暗黙的に宣言されます。ループ内の文は索引の値を参照できますが、変更することはできません。ループの外側の文は、索引を参照できません。FOR
LOOP
文の実行後、索引は未定義となります。(ループ索引はループ・カウンタと呼ばれる場合があります。)
例4-18では、FOR
LOOP
文が索引の値を変更しようとしているため、エラーが発生します。
例4-19では、FOR
LOOP
文の外側の文がループ索引を参照しようとしているため、エラーが発生します。
例4-20に示すように、FOR
LOOP
文の索引の名前が、外側のブロックで宣言された変数の名前と同じ場合、ローカルの暗黙的な宣言によって他の宣言が隠されます。
例4-21は、Example 4-20を変更して、外側のブロックで宣言された変数をループ内の文で参照できるようにする方法を示しています。
例4-22では、ネストしたFOR
LOOP
文の索引で同じ名前を使用しています。内部ループでは、外部ループのラベルで参照を修飾して、外部ループの索引を参照しています。また、内部ループでは、意味を明確にするために、それ自体のラベルでそれ自体の索引への参照も修飾しています。
例4-18 索引値を変更しようとするFOR LOOP文
BEGIN FOR i IN 1..3 LOOP IF i < 3 THEN DBMS_OUTPUT.PUT_LINE (TO_CHAR(i)); ELSE i := 2; END IF; END LOOP; END; /
結果:
i := 2; * ERROR at line 6: ORA-06550: line 6, column 8: PLS-00363: expression 'I' cannot be used as an assignment target ORA-06550: line 6, column 8: PL/SQL: Statement ignored
例4-19 外側の文によるFOR LOOP文の索引の参照
BEGIN FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE ('Inside loop, i is ' || TO_CHAR(i)); END LOOP; DBMS_OUTPUT.PUT_LINE ('Outside loop, i is ' || TO_CHAR(i)); END; /
結果:
DBMS_OUTPUT.PUT_LINE ('Outside loop, i is ' || TO_CHAR(i)); * ERROR at line 6: ORA-06550: line 6, column 58: PLS-00201: identifier 'I' must be declared ORA-06550: line 6, column 3: PL/SQL: Statement ignored
例4-20 変数と同じ名前を持つFOR LOOP文の索引
DECLARE i NUMBER := 5; BEGIN FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE ('Inside loop, i is ' || TO_CHAR(i)); END LOOP; DBMS_OUTPUT.PUT_LINE ('Outside loop, i is ' || TO_CHAR(i)); END; /
結果:
Inside loop, i is 1 Inside loop, i is 2 Inside loop, i is 3 Outside loop, i is 5
例4-21 FOR LOOP文による索引と同じ名前を持つ変数の参照
<<main>> -- Label block. DECLARE i NUMBER := 5; BEGIN FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE ( 'local: ' || TO_CHAR(i) || ', global: ' || TO_CHAR(main.i) -- Qualify reference with block label. ); END LOOP; END main; /
結果:
local: 1, global: 5 local: 2, global: 5 local: 3, global: 5
例4-22 同じ索引名のネストしたFOR LOOP文
BEGIN <<outer_loop>> FOR i IN 1..3 LOOP <<inner_loop>> FOR i IN 1..3 LOOP IF outer_loop.i = 2 THEN DBMS_OUTPUT.PUT_LINE ('outer: ' || TO_CHAR(outer_loop.i) || ' inner: ' || TO_CHAR(inner_loop.i)); END IF; END LOOP inner_loop; END LOOP outer_loop; END; /
結果:
outer: 2 inner: 1 outer: 2 inner: 2 outer: 2 inner: 3
FOR
LOOP
文の下限と上限には、数値リテラル、数値変数または数式を使用できます。境界の数値が指定されていない場合、PL/SQLは事前定義の例外VALUE_ERROR
を呼び出します。
例4-24では、FOR
LOOP
文の上限が変数になっており、値は実行時に決定されます。
例4-23 FOR LOOP文の境界
DECLARE first INTEGER := 1; last INTEGER := 10; high INTEGER := 100; low INTEGER := 12; BEGIN -- Bounds are numeric literals: FOR j IN -5..5 LOOP NULL; END LOOP; -- Bounds are numeric variables: FOR k IN REVERSE first..last LOOP NULL; END LOOP; -- Lower bound is numeric literal, -- Upper bound is numeric expression: FOR step IN 0..(TRUNC(high/low) * 2) LOOP NULL; END LOOP; END; /
例4-24 実行時に境界が指定されるFOR LOOP文
DROP TABLE temp; CREATE TABLE temp ( emp_no NUMBER, email_addr VARCHAR2(50) ); DECLARE emp_count NUMBER; BEGIN SELECT COUNT(employee_id) INTO emp_count FROM employees; FOR i IN 1..emp_count LOOP INSERT INTO temp (emp_no, email_addr) VALUES(i, 'to be added later'); END LOOP; END; /
特定の条件が発生した場合はただちにFOR
LOOP
文を終了する必要があるとします。FOR
LOOP
文内のEXIT
WHEN
文に、条件を指定できます。
例4-25では、FOR
LOOP
文が10回実行されますが、中のFETCH
文から行が戻されなくなった場合は、ただちにループが終了します。
次は、途中で終了する必要のあるFOR
LOOP
文が、別のFOR
LOOP
の中にネストされているとします。内側のループが途中で終了した場合に外側のループも終了する必要があるなら、例4-26に示すように、外側のループにラベルを付け、EXIT
WHEN
文にラベル名を指定します。
内側のループが途中で終了しても外側のループの現行の反復を完了する必要があるなら、例4-27に示すように、外側のループにラベルを付け、CONTINUE
WHEN
文にラベル名を指定します。
関連項目:
例外の詳細は、「例外処理の概要」を参照してください。特定の条件が発生した場合は、例外を使用してただちにループを終了させることもできます
例4-25 FOR LOOP文内のEXIT WHEN文
DECLARE
v_employees employees%ROWTYPE;
CURSOR c1 is SELECT * FROM employees;
BEGIN
OPEN c1;
-- Fetch entire row into v_employees record:
FOR i IN 1..10 LOOP
FETCH c1 INTO v_employees;
EXIT WHEN c1%NOTFOUND;
-- Process data here
END LOOP;
CLOSE c1;
END;
/
例4-26 内側のFOR LOOP文内のEXIT WHEN文
DECLARE v_employees employees%ROWTYPE; CURSOR c1 is SELECT * FROM employees; BEGIN OPEN c1; -- Fetch entire row into v_employees record: <<outer_loop>> FOR i IN 1..10 LOOP -- Process data here FOR j IN 1..10 LOOP FETCH c1 INTO v_employees; EXIT outer_loop WHEN c1%NOTFOUND; -- Process data here END LOOP; END LOOP outer_loop; CLOSE c1; END; /
例4-27 内側のFOR LOOP文内のCONTINUE WHEN文
DECLARE v_employees employees%ROWTYPE; CURSOR c1 is SELECT * FROM employees; BEGIN OPEN c1; -- Fetch entire row into v_employees record: <<outer_loop>> FOR i IN 1..10 LOOP -- Process data here FOR j IN 1..10 LOOP FETCH c1 INTO v_employees; CONTINUE outer_loop WHEN c1%NOTFOUND; -- Process data here END LOOP; END LOOP outer_loop; CLOSE c1; END; /
WHILE
LOOP
文は、条件がTRUEの場合に1つ以上の文を実行します。この文の構造は、次のとおりです。
[ label ] WHILE condition LOOP statements END LOOP [ label ];
condition
がTRUEの場合は、statements
が実行され、ループの先頭に制御が戻り、そこでcondition
が再度評価されます。condition
がTRUEでない場合は、WHILE
LOOP
文の後の文に制御が移ります。無限ループが発生しないように、ループ内の文で条件がFALSEまたはNULLになるようにする必要があります。構文の詳細は、「WHILE LOOP文」を参照してください。
EXIT
、EXIT
WHEN
、CONTINUE
またはCONTINUE
WHEN
をstatements
内に置くと、ループまたは現行のループの反復を途中で終了させることができます。
一部の言語は、条件をループの先頭ではなく最後でテストするLOOP
UNTIL
またはREPEAT
UNTIL
構造を持っているため、一連の文は1回以上実行されます。この構造をPL/SQLでシミュレートするには、EXIT
WHEN
文とともに基本LOOP
文を使用します。
LOOP statements EXIT WHEN condition; END LOOP;
例4-28では、1番目のWHILE
LOOP
文内の文は実行されず、2番目のWHILE
LOOP
文内の文が1回実行されます。
例4-28 WHILE LOOP文
DECLARE done BOOLEAN := FALSE; BEGIN WHILE done LOOP DBMS_OUTPUT.PUT_LINE ('This line does not print.'); done := TRUE; -- This assignment is not made. END LOOP; WHILE NOT done LOOP DBMS_OUTPUT.PUT_LINE ('Hello, world!'); done := TRUE; END LOOP; END; /
結果:
Hello, world!
順次制御文であるGOTO
文とNULL
文は、PL/SQLプログラミングではIF
文やLOOP
文ほど重要ではありません。
指定した文に移動するGOTO
文が必要となることはほとんどありません。ただし、これを使用すると論理を単純化できる場合もあります。
何も実行しないNULL
文には、条件文の意味とアクションを明確にすることによって、コードをわかりやすくする効果があります。
ここでのトピック
GOTO
文は無条件に制御をラベルに移します。ラベルは有効範囲の中で他と重複しないもので、実行可能文かPL/SQLブロックの前に置かれている必要があります。GOTO
文が実行されると、ラベルが付けられた文またはブロックに制御が移ります。GOTO
文の制限については、「GOTO文」を参照してください。
GOTO
文の使用は最小限にしてください(多用すると、コードの理解やメンテナンスが困難になります)。深いネスト構造から例外ハンドラに制御を移す場合、GOTO
文は使用しないでください。かわりに、例外を呼び出してください。PL/SQLの例外処理メカニズムの詳細は、「PL/SQLのエラー処理」を参照してください。
ラベルは、ブロックの前(例4-21)または文の前(例4-29)にのみ置くことができます。例4-30のように文の途中に置くことはできません。
例4-30を修正する場合は、例4-31に示すように、NULL
文を追加します。
GOTO
文は、例4-32に示すように、カレント・ブロックから外側のブロックに制御を移すことができます。
このGOTO
文は、参照されたラベルが置かれている最初の外側のブロックに制御を移しています。
例4-33では、GOTO
文がIF
文内に制御を移しているため、エラーが発生します。
例4-29 GOTO文
DECLARE p VARCHAR2(30); n PLS_INTEGER := 37; BEGIN FOR j in 2..ROUND(SQRT(n)) LOOP IF n MOD j = 0 THEN p := ' is not a prime number'; GOTO print_now; END IF; END LOOP; p := ' is a prime number'; <<print_now>> DBMS_OUTPUT.PUT_LINE(TO_CHAR(n) || p); END; /
結果:
37 is a prime number
例4-30 不適切なラベル配置
DECLARE
done BOOLEAN;
BEGIN
FOR i IN 1..50 LOOP
IF done THEN
GOTO end_loop;
END IF;
<<end_loop>>
END LOOP;
END;
/
結果:
END LOOP; * ERROR at line 9: ORA-06550: line 9, column 3: PLS-00103: Encountered the symbol "END" when expecting one of the following: ( begin case declare exit for goto if loop mod null raise return select update while with <an identifier> <a double-quoted delimited-identifier> <a bind variable> << continue close current delete fetch lock insert open rollback savepoint set sql run commit forall merge pipe purge
例4-31 GOTO文によるラベル付きのNULL文への移動
DECLARE done BOOLEAN; BEGIN FOR i IN 1..50 LOOP IF done THEN GOTO end_loop; END IF; <<end_loop>> NULL; END LOOP; END; /
例4-32 GOTO文による外側のブロックへの制御の移動
DECLARE v_last_name VARCHAR2(25); v_emp_id NUMBER(6) := 120; BEGIN <<get_name>> SELECT last_name INTO v_last_name FROM employees WHERE employee_id = v_emp_id; BEGIN DBMS_OUTPUT.PUT_LINE (v_last_name); v_emp_id := v_emp_id + 5; IF v_emp_id < 120 THEN GOTO get_name; END IF; END; END; /
結果:
Weiss
例4-33 GOTO文によるIF文内への無効な制御の移動
DECLARE valid BOOLEAN := TRUE; BEGIN GOTO update_row; IF valid THEN <<update_row>> NULL; END IF; END; /
結果:
GOTO update_row; * ERROR at line 4: ORA-06550: line 4, column 3: PLS-00375: illegal GOTO statement; this GOTO cannot transfer control to label 'UPDATE_ROW' ORA-06550: line 6, column 12: PL/SQL: Statement ignored
NULL
文は、後続の文に制御を移すのみです。一部の言語では、このような命令をno-op(何もしない)と呼びます。
NULL
文は、次のように使用できます。
GOTO
文に対するターゲットとして使用できます(例4-31を参照)。
条件文の意味とアクションを明確にすることによって、わかりやすさを向上できます(例4-34を参照)。
プレースホルダおよびスタブ・サブプログラムを作成できます(例4-35を参照)。
考慮はするがアクションは必要ないことを示すことができます(例4-36を参照)。
例4-34では、NULL
文によって、販売員のみがコミッションを受け取れることを明確にしています。
例4-35では、NULL
文を使用してサブプログラムをコンパイルしています。実際の本体は、後で挿入できます。
注意:
NULL
文を使用する場合に警告が有効になっていると、unreachable
code
という警告が発生する可能性があります。警告の詳細は、「コンパイル時の警告」を参照してください。
例4-36では、NULL
文によって、A、B、C、D、F以外のgradeではアクションを起こさないことを示しています。
例4-34 アクションを実行しないことを明示するNULL文
DECLARE
v_job_id VARCHAR2(10);
v_emp_id NUMBER(6) := 110;
BEGIN
SELECT job_id INTO v_job_id
FROM employees
WHERE employee_id = v_emp_id;
IF v_job_id = 'SA_REP' THEN
UPDATE employees
SET commission_pct = commission_pct * 1.2;
ELSE
NULL; -- Employee is not a sales rep
END IF;
END;
/
例4-35 サブプログラム作成時のプレースホルダとしてのNULL文
CREATE OR REPLACE PROCEDURE award_bonus ( emp_id NUMBER, bonus NUMBER ) AUTHID DEFINER AS BEGIN -- Executable part starts here NULL; -- Placeholder -- (raises "unreachable code" if warnings enabled) END award_bonus; /
例4-36 単純なCASE文のELSE句でのNULL文
CREATE OR REPLACE PROCEDURE print_grade (
grade CHAR
) AUTHID DEFINER AS
BEGIN
CASE grade
WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent');
WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good');
WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('Good');
WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair');
WHEN 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor');
ELSE NULL;
END CASE;
END;
/
BEGIN
print_grade('A');
print_grade('S');
END;
/
結果:
Excellent