この章では、PL/SQLプログラムの制御の流れを構造化する方法を示します。 PL/SQLで提供される条件テスト、ループおよび分岐を使用すると、優れた構造を持つプログラムを作成できます。
ここでのトピック:
図4-1に、手続き型コンピュータ・プログラムで使用される基本制御構造を示します。
選択構造は、条件をテストし、条件の真偽に応じて一連の文を実行します。 条件とは、BOOLEAN
値を戻す任意の変数または式です。 反復構造は、ある条件が真の間、一連の文を繰り返して実行します。 順次構造は、一連の文を、出現する順番にそのまま実行します。
IF
文は、条件の値に応じて、異なる一連の文を実行します。 IF
文には、IF-THEN
、IF-THEN-ELSE
およびIF-THEN-ELSIF
の3つの形式があります。 IF
文の構文の説明は、「IF文」を参照してください。
CASE
文は、単一の条件を評価して多数の代替アクションから選択するコンパクトな手段です。 3つ以上の選択肢がある場合は、CASE
文が有効です。 CASE
文の構文の説明は、「CASE文」を参照してください。
ここでのトピック:
IF
文の最も単純な形式であるIF-THENは、例4-1で示すように、キーワードTHEN
とEND
IF
(ENDIF
ではない)によって囲まれた一連の文に条件を関連付けます。
一連の文は、条件がTRUE
である場合にのみ実行されます。 条件がFALSE
またはNULL
である場合、IF
文は何も実行しません。 いずれの場合も、制御は次の文に渡されます。
例4-1 単純なIF-THEN文
SQL> DECLARE 2 sales NUMBER(8,2) := 10100; 3 quota NUMBER(8,2) := 10000; 4 bonus NUMBER(6,2); 5 emp_id NUMBER(6) := 120; 6 BEGIN 7 IF sales > (quota + 200) THEN 8 bonus := (sales - quota)/4; 9 10 UPDATE employees SET salary = 11 salary + bonus 12 WHERE employee_id = emp_id; 13 END IF; 14 END; 15 / PL/SQL procedure successfully completed. SQL>
IF
文の2つ目の形式であるIF-THEN-ELSEは、例4-2で示すように、キーワードELSE
を追加し、その後に一連の代替文を続けます。
ELSE
句の中の文は、条件がFALSE
またはNULL
に評価された場合にのみ実行されます。 IF-THEN-ELSE
文を使用すると、一連の文のどちらかが確実に実行されます。
例4-2 単純なIF-THEN-ELSE文の使用
SQL> DECLARE 2 sales NUMBER(8,2) := 12100; 3 quota NUMBER(8,2) := 10000; 4 bonus NUMBER(6,2); 5 emp_id NUMBER(6) := 120; 6 BEGIN 7 IF sales > (quota + 200) THEN 8 bonus := (sales - quota)/4; 9 ELSE 10 bonus := 50; 11 END IF; 12 13 UPDATE employees 14 SET salary = salary + bonus 15 WHERE employee_id = emp_id; 16 END; 17 / PL/SQL procedure successfully completed. SQL>
IF
文はネストできます。 例4-3は、ネストしたIF-THEN-ELSE
文を示しています。
例4-3 ネストしたIF-THEN-ELSE文
SQL> DECLARE 2 sales NUMBER(8,2) := 12100; 3 quota NUMBER(8,2) := 10000; 4 bonus NUMBER(6,2); 5 emp_id NUMBER(6) := 120; 6 BEGIN 7 IF sales > (quota + 200) THEN 8 bonus := (sales - quota)/4; 9 ELSE 10 IF sales > quota THEN 11 bonus := 50; 12 ELSE 13 bonus := 0; 14 END IF; 15 END IF; 16 17 UPDATE employees 18 SET salary = salary + bonus 19 WHERE employee_id = emp_id; 20 END; 21 / PL/SQL procedure successfully completed. SQL>
複数の選択肢から選択する必要がある場合があります。 キーワードELSIF
(ELSEIF
またはELSE
IF
ではない)を使用すると、例4-4で示すように条件を追加できます。
最初の条件がFALSE
またはNULL
の場合、ELSIF
句は別の条件をテストします。 IF
文は任意の数のELSIF
句を持つことができます。最後のELSE
句はオプションです。 条件は上から下に向かって1つずつ評価されます。 いずれかの条件がTRUE
である場合は、それに対応する一連の文が実行され、制御は次の文に移ります。 すべての条件がFALSEまたはNULL
である場合、例4-4で示すように、ELSE
句の一連の文が実行されます。
例4-4 IF-THEN-ELSIF文の使用
SQL> DECLARE 2 sales NUMBER(8,2) := 20000; 3 bonus NUMBER(6,2); 4 emp_id NUMBER(6) := 120; 5 BEGIN 6 IF sales > 50000 THEN 7 bonus := 1500; 8 ELSIF sales > 35000 THEN 9 bonus := 500; 10 ELSE 11 bonus := 100; 12 END IF; 13 14 UPDATE employees 15 SET salary = salary + bonus 16 WHERE employee_id = emp_id; 17 END; 18 / PL/SQL procedure successfully completed. SQL>
sales
の値が50000よりも大きい場合は、1番目と2番目の条件がTRUE
になります。 ただし、2番目の条件はテストされないため、bonus
には1500という正しい値が代入されます。 1番目の条件がTRUE
に評価されると、それに対応する文が実行され、制御はUPDATE
文に移ります。
例4-5はIF-THEN-ELSE
文の別の例です。
例4-5 IF-THEN文の拡張
SQL> DECLARE 2 grade CHAR(1); 3 BEGIN 4 grade := 'B'; 5 6 IF grade = 'A' THEN 7 DBMS_OUTPUT.PUT_LINE('Excellent'); 8 ELSIF grade = 'B' THEN 9 DBMS_OUTPUT.PUT_LINE('Very Good'); 10 ELSIF grade = 'C' THEN 11 DBMS_OUTPUT.PUT_LINE('Good'); 12 ELSIF grade = 'D' THEN 13 DBMS_OUTPUT. PUT_LINE('Fair'); 14 ELSIF grade = 'F' THEN 15 DBMS_OUTPUT.PUT_LINE('Poor'); 16 ELSE 17 DBMS_OUTPUT.PUT_LINE('No such grade'); 18 END IF; 19 END; 20 / Very Good PL/SQL procedure successfully completed. SQL>
IF
文と同様に、CASE
文では一連の文を選択して実行できます。 ただし、CASE
文では、順序を選択するために複数のブール式ではなく選択子を使用します。 選択子は複数の選択肢から1つ選択するために値が使用される式です。
例4-5のコードを、例4-6に示すようにCASE
文を使用して書き直します。
例4-6 単純なCASE文
SQL> DECLARE 2 grade CHAR(1); 3 BEGIN 4 grade := 'B'; 5 6 CASE grade 7 WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent'); 8 WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good'); 9 WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('Good'); 10 WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair'); 11 WHEN 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor'); 12 ELSE DBMS_OUTPUT.PUT_LINE('No such grade'); 13 END CASE; 14 END; 15 / Very Good PL/SQL procedure successfully completed. SQL>
CASE
文の方が読みやすく効率的です。 長いIF-THEN-ELSIF
文はできるかぎりCASE
文として書き直してください。
CASE
文は、キーワードCASE
で始まります。 キーワードの後に選択子(前述の例では変数grade
)があります。 選択子式は、どんなに複雑でもかまいません。 たとえば、ファンクション・コールを含めることができます。 ただし、通常は、1個の変数で構成されています。 選択子式が評価されるのは1度のみです。 生成される値は、BLOB
、BFILE
、オブジェクト型、PL/SQLレコード、索引付き表またはVARRAY、ネストした表以外であれば、どのようなPL/SQLデータ型でもかまいません。
選択子の後に1つ以上のWHEN
句があり、各句が順番にチェックされます。 選択子の値によって、どの句が実行されるかが決定されます。 選択子の値がWHEN
句の式の値と等しければ、そのWHEN
句が実行されます。 たとえば、最後の例では、grade
が'C'
であれば、'Good'
が出力されます。 実行が失敗することはなく、WHEN
句が1つでも実行されると、制御が次の文に渡されます。
ELSE
句の機能は、IF
文のELSE
句に似ています。 前述の例では、学年がWHEN
句のオプションの1つでなければ、ELSE
句が選択され、'No such grade'
という句が出力されます。 ELSE
句はオプションです。 ただし、ELSE
句を省略すると、PL/SQLでは次の暗黙的なELSE
句が追加されます。
ELSE RAISE CASE_NOT_FOUND;
ELSE
句を省略しても、常にデフォルト・アクションがあります。 CASE
文がどのWHEN
句にも一致せず、ELSE
句を省略している場合、PL/SQLは事前定義された例外CASE_NOT_FOUND
を呼び出します。
CASE
文は、キーワードEND
CASE
で終了します。 この2つのキーワードは、空白で区切る必要があります。
PL/SQLブロックと同様に、CASE
文にもラベルを付けることができます。 ラベルは二重の山カッコで囲んだ未宣言の識別子で、CASE
文の先頭に置きます。 オプションとして、CASE
文の末尾にもラベル名を付けることができます。
CASE
文の実行中に呼び出された例外は、通常の方法で処理されます。 つまり、通常の実行は中止され、PL/SQLブロックまたはサブプログラムの例外処理部に制御が移ります。
CASE
文はCASE
式の代替であり、各WHEN
句が式になっています。 詳細は、「CASE式」を参照してください。
PL/SQLには、単純なCASE
文と同様の検索CASE
文も用意されています。その書式は例4-7で示すとおりです。
検索CASE
式に選択子はありません。また、この式のWHEN
句には、結果がいずれかの型の値になる式ではなく、ブール値になる検索条件が含まれています。
例4-7の検索CASE
文は、例4-6の単純なCASE
文と論理的に等価です。
例4-7 検索CASE文
SQL> DECLARE 2 grade CHAR(1); 3 BEGIN 4 grade := 'B'; 5 6 CASE 7 WHEN grade = 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent'); 8 WHEN grade = 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good'); 9 WHEN grade = 'C' THEN DBMS_OUTPUT.PUT_LINE('Good'); 10 WHEN grade = 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair'); 11 WHEN grade = 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor'); 12 ELSE DBMS_OUTPUT.PUT_LINE('No such grade'); 13 END CASE; 14 END; 15 / Very Good PL/SQL procedure successfully completed. SQL>
例4-7と例4-6のいずれの場合も、ELSE
句をEXCEPTION
部に置き換えることができます。 例4-8は、例4-7と論理的に等価です。
例4-8 CASE文でのELSE句にかわるEXCEPTIONの使用
SQL> DECLARE 2 grade CHAR(1); 3 BEGIN 4 grade := 'B'; 5 6 CASE 7 WHEN grade = 'A' THEN DBMS_OUTPUT.PUT_LINE('Excellent'); 8 WHEN grade = 'B' THEN DBMS_OUTPUT.PUT_LINE('Very Good'); 9 WHEN grade = 'C' THEN DBMS_OUTPUT.PUT_LINE('Good'); 10 WHEN grade = 'D' THEN DBMS_OUTPUT.PUT_LINE('Fair'); 11 WHEN grade = 'F' THEN DBMS_OUTPUT.PUT_LINE('Poor'); 12 END CASE; 13 14 EXCEPTION 15 WHEN CASE_NOT_FOUND THEN 16 DBMS_OUTPUT.PUT_LINE('No such grade'); 17 END; 18 / Very Good PL/SQL procedure successfully completed. SQL>
検索条件は順番に評価されます。 各検索条件のブール値によって、どのWHEN
句が実行されるかが決定されます。 検索条件がTRUE
になると、そのWHEN
句が実行されます。 WHEN
句が1つでも実行されると、制御が次の文に渡されるため、後続の検索条件は評価されません。
TRUE
になる検索条件がなければ、ELSE
句が実行されます。 ELSE
句はオプションです。 ただし、ELSE
句を省略すると、PL/SQLでは次の暗黙的なELSE
句が追加されます。
ELSE RAISE CASE_NOT_FOUND;
検索CASE
文の実行中に呼び出された例外は、通常の方法で処理されます。 つまり、通常の実行は中止され、PL/SQLブロックまたはサブプログラムの例外処理部に制御が移ります。
IF new_balance < minimum_balance THEN overdrawn := TRUE; ELSE overdrawn := FALSE; END IF; IF overdrawn = TRUE THEN RAISE insufficient_funds; END IF;
ブール式の値はブール変数に直接代入できます。 1番目のIF
文は、次のように単純な代入に置き換えることができます。
overdrawn := new_balance < minimum_balance;
ブール変数はそれ自身がTRUEまたはFALSEです。 2番目のIF
文の条件は、次のように単純化できます。
IF overdrawn THEN ...
可能ならば、IF
文をネストするのではなく、ELSIF
句を使用してください。 それによって、読みやすく、理解しやすいコードになります。 次のIF
文を比較してください。
IF condition1 THEN statement1; ELSE IF condition2 THEN statement2; ELSE IF condition3 THEN statement3; END IF; END IF; END IF; IF condition1 THEN statement1; ELSIF condition2 THEN statement2; ELSIF condition3 THEN statement3; END IF;
これらの文は論理的に等価ですが、2番目の文の方が論理が明快です。
単一の式を複数の値と比較する場合は、IF
とELSIF
句の組合せのかわりに単一のCASE
文を使用すると、論理を簡素化できます。
LOOP
文は、一連の文を複数回実行します。 PL/SQLには、次のLOOP文が用意されています。
基本LOOP
WHILE
LOOP
FOR
LOOP
カーソルFOR
LOOP
PL/SQLには、ループを終了するために次の文が用意されています。
EXIT
EXIT-WHEN
PL/SQLには、ループの現行の反復を終了するために次の文が用意されています。
CONTINUE
CONTINUE-WHEN
EXIT
文およびCONTINUE
文は、ループ内の任意の場所に置くことができます。ただし、ループの外には置くことができません。 PL/SQLブロックを通常終了より前の段階で終了させる場合は、RETURN
文を使用します(「RETURN文」を参照)。
LOOP
文、EXIT
文およびCONTINUE
文の構文は、第13章「PL/SQLの言語要素」を参照してください。
ここでのトピック:
カーソルFOR-LOOP
については、「カーソルFOR LOOP」を参照してください。
最も単純なLOOP
文は、キーワードLOOP
とEND
LOOP
で一連の文を囲む基本ループです。次に例を示します。
LOOP
sequence_of_statements
END LOOP;
ループが反復されるたびに一連の文が実行され、制御がループの先頭に戻ります。
基本ループ内ではCONTINUE
文およびCONTINUE-WHEN
文を使用できますが、無限ループが発生しないようにするには、EXIT
文またはEXIT-WHEN
文を使用する必要があります。
基本ループの構文は、「LOOP文」を参照してください。
EXIT
文が検出されると、例4-9に示すように、ループはただちに終了し、制御はEND
LOOP
の直後の文に移ります。
EXIT
文の構文は、「EXIT文」を参照してください。
例4-9 EXIT文
SQL> DECLARE 2 x NUMBER := 0; 3 BEGIN 4 LOOP 5 DBMS_OUTPUT.PUT_LINE 6 ('Inside loop: x = ' || TO_CHAR(x)); 7 8 x := x + 1; 9 10 IF x > 3 THEN 11 EXIT; 12 END IF; 13 END LOOP; 14 -- After EXIT, control resumes here 15 16 DBMS_OUTPUT.PUT_LINE 17 (' After loop: x = ' || TO_CHAR(x)); 18 END; 19 / Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop: x = 3 After loop: x = 4 PL/SQL procedure successfully completed. SQL>
EXIT-WHEN
文が検出されると、WHEN
句内の条件が評価されます。 条件の評価結果がTRUEならば、ループは終了し、制御はEND
LOOP
の直後の文に移ります。 条件の評価結果がTRUEになるまで、EXIT-WHEN
文は(その条件の評価を除いて)NULL
文と同様に動作するため、ループを終了しません。 例4-10に示すように、ループ内の文によって、条件の値を変更する必要があります。
EXIT-WHEN
文はTHEN
... IF
... EXIT
形式の文のかわりとして使用できます。例4-10は、例4-9と論理的に等価です。
EXIT-WHEN
文の構文は、「EXIT文」を参照してください。
例4-10 EXIT-WHEN文の使用
SQL> DECLARE 2 x NUMBER := 0; 3 BEGIN 4 LOOP 5 DBMS_OUTPUT.PUT_LINE 6 ('Inside loop: x = ' || TO_CHAR(x)); 7 8 x := x + 1; 9 10 EXIT WHEN x > 3; 11 END LOOP; 12 13 -- After EXIT statement, control resumes here 14 DBMS_OUTPUT.PUT_LINE 15 ('After loop: x = ' || TO_CHAR(x)); 16 END; 17 / Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop: x = 3 After loop: x = 4 PL/SQL procedure successfully completed. SQL>
CONTINUE
文が検出されると、例4-11に示すように、ループの現行の反復はただちに終了し、制御はループの次の反復に移ります。
CONTINUE
文はサブプログラムまたはメソッドの境界を越えることはできません。
CONTINUE
文の構文は、「CONTINUE文」を参照してください。
例4-11 CONTINUE文
SQL> DECLARE 2 x NUMBER := 0; 3 BEGIN 4 LOOP -- After CONTINUE statement, control resumes here 5 DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR(x)); 6 x := x + 1; 7 8 IF x < 3 THEN 9 CONTINUE; 10 END IF; 11 12 DBMS_OUTPUT.PUT_LINE 13 ('Inside loop, after CONTINUE: x = ' || TO_CHAR(x)); 14 15 EXIT WHEN x = 5; 16 END LOOP; 17 18 DBMS_OUTPUT.PUT_LINE (' After loop: x = ' || TO_CHAR(x)); 19 END; 20 / 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 PL/SQL procedure successfully completed. SQL>
注意: リリース11.1の時点では、CONTINUE はPL/SQLのキーワードです。 使用しているプログラムでCONTINUE というサブプログラムが起動されると、警告が表示されます。 |
CONTINUE-WHEN
文が検出されると、WHEN
句内の条件が評価されます。 条件の評価結果がTRUEならば、ループの現行の反復は終了し、制御は次の反復に移ります。 条件の評価結果がTRUEになるまで、CONTINUE-WHEN
文は(その条件の評価を除いて)NULL
文と同様に動作するため、反復を終了しません。 ただし、条件の値はそれぞれの反復で異なる場合があるため、CONTINUE
は一部の反復を終了し、一部の反復は終了しません。
CONTINUE-WHEN
文はIF
...形式の文のかわりとして使用できます。 THEN
... CONTINUE
例4-12は、例4-11と論理的に等価です。
CONTINUE-WHEN
文はサブプログラムまたはメソッドの境界を越えることはできません。
CONTINUE-WHEN
文の構文は、「CONTINUE文」を参照してください。
例4-12 CONTINUE-WHEN文
SQL> DECLARE 2 x NUMBER := 0; 3 BEGIN 4 LOOP -- After CONTINUE statement, control resumes here 5 DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR(x)); 6 x := x + 1; 7 CONTINUE WHEN x < 3; 8 DBMS_OUTPUT.PUT_LINE 9 ('Inside loop, after CONTINUE: x = ' || TO_CHAR(x)); 10 EXIT WHEN x = 5; 11 END LOOP; 12 DBMS_OUTPUT.PUT_LINE (' After loop: x = ' || TO_CHAR(x)); 13 END; 14 / 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 PL/SQL procedure successfully completed. SQL>
PL/SQLブロックと同様に、ループにもラベルを付けることができます。 オプション・ラベルは二重の山カッコで囲んだ未宣言の識別子で、LOOP
文の先頭に置きます。 また、LOOP
文の末尾にもラベル名を付けることができます。 ラベル付きのループをネストする場合は、末尾のラベルを使用してわかりやすくします。
どちらの形式のEXIT
文でも、カレント・ループにかぎらず、任意の外側のループも終了させることができます。 これを行うには、終了する外側のループにラベルを付けます。 次に、例4-13に示すように、EXIT
文でそのラベルを使用します。 ラベルを付けた外側のループが、内側のループを含めて終了します。
どちらの形式のCONTINUE
文でも、ラベル付きループの現行の反復を終了させ、任意の外側のループを終了させることができます。
例4-13 ラベル付きループ
SQL> DECLARE 2 s PLS_INTEGER := 0; 3 i PLS_INTEGER := 0; 4 j PLS_INTEGER; 5 BEGIN 6 <<outer_loop>> 7 LOOP 8 i := i + 1; 9 j := 0; 10 <<inner_loop>> 11 LOOP 12 j := j + 1; 13 s := s + i * j; -- Sum several products 14 EXIT inner_loop WHEN (j > 5); 15 EXIT outer_loop WHEN ((i * j) > 15); 16 END LOOP inner_loop; 17 END LOOP outer_loop; 18 DBMS_OUTPUT.PUT_LINE 19 ('The sum of products equals: ' || TO_CHAR(s)); 20 END; 21 / The sum of products equals: 166 PL/SQL procedure successfully completed. SQL>
WHILE-LOOP
文は、条件がTRUEに評価されるかぎり、ループ本体の文を実行します。
WHILE condition LOOP
sequence_of_statements
END LOOP;
ループを反復する前に条件が評価されます。 条件がTRUE
ならば、一連の文が実行されてから、ループの先頭で制御が再開します。 FALSE
またはNULL
の場合、ループは実行されず、制御は次の文に渡されます。 WHILE-LOOP
文を使用する例については、例1-12を参照してください。
反復の回数は条件に依存し、ループが終了するまでわかりません。 条件はループの先頭でテストされるため、一連の文が一度も実行されない可能性もあります。
いくつかの言語は、条件をループの先頭ではなく末尾でテストするLOOP
UNTIL
構造またはREPEAT
UNTIL
構造を持っています。そのため、一連の文は1回以上実行されます。 PL/SQLでの等価のコードを次に示します。
LOOP sequence_of_statements EXIT WHEN boolean_expression END LOOP;
WHILE
ループが1回以上実行されるようにするには、初期化済のブール変数を条件の中で使用します。
done := FALSE; WHILE NOT done LOOP sequence_of_statements done := boolean_expression END LOOP;
ループの中の文でブール変数に新しい値を代入して、無限ループを回避します。
単純なFOR
ループは、指定された整数の範囲内(lower_bound
..
upper_bound
)で反復して実行されます。 反復の回数はループに入る前からわかっています。 反復の範囲はFOR
ループに入った段階で評価され、それ以降は評価されません。 lower_bound
とupper_bound
が等しい場合、ループ本体は一度のみ実行されます。
例4-14に示すように、一連の文は1から500までの整数につき、1回ずつ実行されます。反復が1回行われるたびに、ループ・カウンタが1つ増えます。
例4-14 単純なFOR-LOOP文
SQL> BEGIN 2 FOR i IN 1..3 LOOP 3 DBMS_OUTPUT.PUT_LINE (TO_CHAR(i)); 4 END LOOP; 5 END; 6 / 1 2 3 PL/SQL procedure successfully completed. SQL>
デフォルトでは、反復は下限から上限の向きに進みます。 キーワードREVERSE
を使用した場合、反復は上限から下限に下向きに進みます。 反復が1回行われるたびに、ループ・カウンタが1つ減ります。 この場合でも、範囲の上限と下限は(降順ではなく)昇順に書きます。
例4-15 反転FOR-LOOP文
SQL> BEGIN
2 FOR i IN REVERSE 1..3 LOOP
3 DBMS_OUTPUT.PUT_LINE (TO_CHAR(i));
4 END LOOP;
5 END;
6 /
3
2
1
PL/SQL procedure successfully completed.
SQL>
FOR
ループの中では、ループ・カウンタを参照できますが、変更することはできません。 次に例を示します。
SQL> BEGIN 2 FOR i IN 1..3 LOOP 3 IF i < 3 THEN 4 DBMS_OUTPUT.PUT_LINE (TO_CHAR(i)); 5 ELSE 6 i := 2; 7 END IF; 8 END LOOP; 9 END; 10 / 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 SQL>
FOR
ループで、整数の範囲のかわりにSQL問合せを使用することをお薦めします。 この手法を使用すると、単純な構文で、問合せを実行し、結果セットのすべての行を処理できます。 詳細は、「カーソルFOR LOOP」を参照してください。
ここでのトピック:
ループ範囲の境界にはリテラル、変数または式を使用できますが、整数に評価されるものにする必要があります。 それ以外の場合、PL/SQLは事前定義の例外VALUE_ERROR
を呼び出します。 下限は1である必要はありませんが、ループ・カウンタの増分値(または減分値)は1である必要があります。
例4-16 FOR-LOOP範囲の種類
SQL> DECLARE 2 first INTEGER := 1; 3 last INTEGER := 10; 4 high INTEGER := 100; 5 low INTEGER := 12; 6 BEGIN 7 -- Bounds are numeric literals: 8 9 FOR j IN -5..5 LOOP 10 NULL; 11 END LOOP; 12 13 -- Bounds are numeric variables: 14 15 FOR k IN REVERSE first..last LOOP 16 NULL; 17 END LOOP; 18 19 -- Lower bound is numeric literal, 20 -- Upper bound is numeric expression: 21 22 FOR step IN 0..(TRUNC(high/low) * 2) LOOP 23 NULL; 24 END LOOP; 25 END; 26 / PL/SQL procedure successfully completed. SQL>
内部的に、PL/SQLはPLS_INTEGER
一時変数に境界の値を代入します。さらに、必要に応じてその値を最も近い整数に四捨五入します。 PLS_INTEGER
の大きさの範囲は、32ビットで表すと、-2147483648から2147483647です。 範囲外の数値を評価した場合、PL/SQLが代入をすると、数値オーバーフローのエラーが発生します。 詳細は、「PLS_INTEGERおよびBINARY_INTEGERデータ型」を参照してください。
言語によっては、STEP
句を使用して異なる増分値(たとえば、1ではなく5)を指定できるものがあります。 PL/SQLはこのような構造を持っていませんが、作成するのは簡単です。 FOR
ループの内部で、ループ・カウンタへの各参照に新しい増分値を乗じます。
例4-17では、本日の日付を索引付き表の要素5、10および15に代入します。
例4-18に示すように、PL/SQLでは、範囲に変数を使用することでループの範囲を実行時に指定できます。
例4-18 実行時のLOOP範囲の指定
SQL> CREATE TABLE temp ( 2 emp_no NUMBER, 3 email_addr VARCHAR2(50) 4 );Table created.SQL> SQL> DECLARE 2 emp_count NUMBER; 3 BEGIN 4 SELECT COUNT(employee_id) INTO emp_count 5 FROM employees; 6 7 FOR i IN 1..emp_count LOOP 8 INSERT INTO temp 9 VALUES(i, 'to be added later'); 10 END LOOP; 11 END; 12 / PL/SQL procedure successfully completed. SQL>
ループ範囲の下限が上限より大きい場合、例4-19に示すように、ループ本体は実行されず、制御は次の文に移ります。
例4-19 下限が上限より大きいFOR-LOOP
SQL> CREATE OR REPLACE PROCEDURE p 2 (limit IN INTEGER) IS 3 BEGIN 4 FOR i IN 2..limit LOOP 5 DBMS_OUTPUT.PUT_LINE 6 ('Inside loop, limit is ' || i); 7 END LOOP; 8 9 DBMS_OUTPUT.PUT_LINE 10 ('Outside loop, limit is ' || TO_CHAR(limit)); 11 END; 12 / Procedure created. SQL> BEGIN 2 p(3); 3 END; 4 / Inside loop, limit is 2 Inside loop, limit is 3 Outside loop, limit is 3 PL/SQL procedure successfully completed. SQL> BEGIN 2 p(1); 3 END; 4 / Outside loop, limit is 1 PL/SQL procedure successfully completed. SQL>
ループ・カウンタはループの中でしか定義されません。 そのため、その変数名をループの外側からは参照できません。 例4-20に示すように、ループが終了すると、ループ・カウンタは未定義になります。
例4-20 ループの外側のカウンタ変数の参照
SQL> BEGIN 2 FOR i IN 1..3 LOOP 3 DBMS_OUTPUT.PUT_LINE 4 ('Inside loop, i is ' || TO_CHAR(i)); 5 END LOOP; 6 7 DBMS_OUTPUT.PUT_LINE 8 ('Outside loop, i is ' || TO_CHAR(i)); 9 END; 10 / ('Outside loop, i is ' || TO_CHAR(i)); * ERROR at line 8: ORA-06550: line 8, column 39: PLS-00201: identifier 'I' must be declared ORA-06550: line 7, column 3: PL/SQL: Statement ignored SQL>
ループ・カウンタはINTEGER
型のローカル変数として暗黙的に宣言されているため、ループ・カウンタを宣言する必要はありません。 ローカル宣言はグローバル宣言を隠すため、最も安全な方法は、例4-21に示すように、ループ変数に既存の変数と同じ名前を使用しないことです。
例4-21 ループ変数としての既存の変数の使用
SQL> DECLARE 2 i NUMBER := 5; 3 BEGIN 4 FOR i IN 1..3 LOOP 5 DBMS_OUTPUT.PUT_LINE 6 ('Inside loop, i is ' || TO_CHAR(i)); 7 END LOOP; 8 9 DBMS_OUTPUT.PUT_LINE 10 ('Outside loop, i is ' || TO_CHAR(i)); 11 END; 12 / Inside loop, i is 1 Inside loop, i is 2 Inside loop, i is 3 Outside loop, i is 5 PL/SQL procedure successfully completed. SQL>
例4-21でグローバル変数を参照する場合は、例4-22に示すように、ラベルとドット表記法を使用する必要があります。
例4-22 ループ・カウンタと同じ名前のグローバル変数の参照
SQL> <<main>> 2 DECLARE 3 i NUMBER := 5; 4 BEGIN 5 FOR i IN 1..3 LOOP 6 DBMS_OUTPUT.PUT_LINE 7 ('local: ' || TO_CHAR(i) || ', global: ' || TO_CHAR(main.i)); 8 END LOOP; 9 END main; 10 / local: 1, global: 5 local: 2, global: 5 local: 3, global: 5 PL/SQL procedure successfully completed. SQL>
ネストされたFOR
ループにも同じ有効範囲規則が適用されます。 例4-23では、内部と外部のループ・カウンタの名前が同じであるため、内部ループでは外部ループのカウンタを参照するためにラベルとドット表記法が使用されます。
例4-23 内部カウンタと同じ名前の外部カウンタの参照
SQL> BEGIN 2 <<outer_loop>> 3 FOR i IN 1..3 LOOP 4 <<inner_loop>> 5 FOR i IN 1..3 LOOP 6 IF outer_loop.i = 2 THEN 7 DBMS_OUTPUT.PUT_LINE 8 ( 'outer: ' || TO_CHAR(outer_loop.i) || ' inner: ' 9 || TO_CHAR(inner_loop.i)); 10 END IF; 11 END LOOP inner_loop; 12 END LOOP outer_loop; 13 END; 14 / outer: 2 inner: 1 outer: 2 inner: 2 outer: 2 inner: 3 PL/SQL procedure successfully completed. SQL>
EXIT
文を使用すると、FOR
ループを途中で終了させることができます。 例4-24に示すように、ループは通常は10回実行されますが、FETCH
文が行を戻さなくなると、ループはそれまで何回実行されていてもただちに終了します。
例4-24 FOR LOOPでのEXIT
SQL> DECLARE
2 v_employees employees%ROWTYPE;
3 CURSOR c1 is SELECT * FROM employees;
4 BEGIN
5 OPEN c1;
6 -- Fetch entire row into v_employees record:
7 FOR i IN 1..10 LOOP
8 FETCH c1 INTO v_employees;
9 EXIT WHEN c1%NOTFOUND;
10 -- Process data here
11 END LOOP;
12 CLOSE c1;
13 END;
14 /
PL/SQL procedure successfully completed.
SQL>
ネストされたFOR
ループから途中で出る必要があるとします。 カレント・ループのみでなく外側のループも終了するには、例4-25に示すように、終了する外側のループにラベルを付け、EXIT
文でそのラベルを使用します。 ラベル付きループの現行の反復を終了させ、任意の外側のループを終了させるには、CONTINUE
文でラベルを使用します。
例4-25 FOR LOOPでのラベル付きEXIT
SQL> DECLARE
2 v_employees employees%ROWTYPE;
3 CURSOR c1 is SELECT * FROM employees;
4 BEGIN
5 OPEN c1;
6
7 -- Fetch entire row into v_employees record:
8 <<outer_loop>>
9 FOR i IN 1..10 LOOP
10 -- Process data here
11 FOR j IN 1..10 LOOP
12 FETCH c1 INTO v_employees;
13 EXIT outer_loop WHEN c1%NOTFOUND;
14 -- Process data here
15 END LOOP;
16 END LOOP outer_loop;
17
18 CLOSE c1;
19 END;
20 /
PL/SQL procedure successfully completed.
SQL>
GOTO
文とNULL
文は、PL/SQLプログラミングにとってIF
文やLOOP
文ほど重要なものではありません。 通常、GOTO
文は必要ありません。 ただし、GOTO文を使用すると論理を単純化できる場合もあります。 NULL
文には、条件文の意味とアクションを明確にすることによって、コードをわかりやすくする効果があります。
GOTO
文を多用すると、コードの理解やメンテナンスが困難になる可能性があります。 GOTO
文の使用は最小限にしてください。 たとえば、深くネストされた構造からエラー処理ルーチンに分岐する場合は、GOTO
文を使用するのではなく、例外を呼び出してください。 PL/SQLの例外処理メカニズムについては、第11章「PL/SQLエラーの処理」で説明しています。
ここでのトピック:
GOTO
文はラベルに無条件に分岐する場合に使用します。 ラベルは有効範囲の中で他と重複しないもので、実行可能文かPL/SQLブロックの前に置かれている必要があります。 GOTO
文が実行されると、ラベルが付けられた文またはブロックに制御が移ります。
例4-26 単純なGOTO文
SQL> DECLARE 2 p VARCHAR2(30); 3 n PLS_INTEGER := 37; 4 BEGIN 5 FOR j in 2..ROUND(SQRT(n)) LOOP 6 IF n MOD j = 0 THEN 7 p := ' is not a prime number'; 8 GOTO print_now; 9 END IF; 10 END LOOP; 11 12 p := ' is a prime number'; 13 14 <<print_now>> 15 DBMS_OUTPUT.PUT_LINE(TO_CHAR(n) || p); 16 END; 17 / 37 is a prime number PL/SQL procedure successfully completed. SQL>
ラベルは、ブロックの前(例4-22)または文の前(例4-26)にのみ置くことができます。例4-27のように文の途中に置くことはできません。
例4-27 不適切なラベル配置
SQL> DECLARE 2 done BOOLEAN; 3 BEGIN 4 FOR i IN 1..50 LOOP 5 IF done THEN 6 GOTO end_loop; 7 END IF; 8 <<end_loop>> 9 END LOOP; 10 END; 11 / 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 execute commit forall merge pipe purge SQL>
例4-27を修正する場合は、例4-28に示すように、NULL
文を追加します。
例4-28 GOTOをラベルに使用するためにNULLを使用
SQL> DECLARE 2 done BOOLEAN; 3 BEGIN 4 FOR i IN 1..50 LOOP 5 IF done THEN 6 GOTO end_loop; 7 END IF; 8 <<end_loop>> 9 NULL; 10 END LOOP; 11 END; 12 / PL/SQL procedure successfully completed. SQL>
GOTO
文は、例4-29に示すように、カレント・ブロックから外側のブロックに分岐できます。
例4-29 GOTO文を使用した外側のブロックへの分岐
SQL> DECLARE 2 v_last_name VARCHAR2(25); 3 v_emp_id NUMBER(6) := 120; 4 BEGIN 5 <<get_name>> 6 SELECT last_name INTO v_last_name 7 FROM employees 8 WHERE employee_id = v_emp_id; 9 10 BEGIN 11 DBMS_OUTPUT.PUT_LINE (v_last_name); 12 v_emp_id := v_emp_id + 5; 13 14 IF v_emp_id < 120 THEN 15 GOTO get_name; 16 END IF; 17 END; 18 END; 19 / Weiss PL/SQL procedure successfully completed. SQL>
このGOTO
文では、参照されたラベルが置かれている最初の外側のブロックに分岐します。
GOTO
文は、IF
文、CASE
文、LOOP
文またはサブブロックには分岐できません。
GOTO
文では、あるIF
文の句から句へ分岐したり、あるCASE
文のWHEN
句から別の句へ分岐することはできません。
GOTO
文では、外部のブロックからサブブロック(内側のBEGIN-END
ブロック)には分岐できません。
GOTO
文では、サブプログラムの外に分岐できません。 サブプログラムを途中で終了するには、RETURN
文を使用するか、またはGOTO
文を使用してサブプログラムの終了直前の場所に分岐します。
GOTO
文では、例外ハンドラからカレント・ブロックBEGIN-END
に分岐できません。 ただし、GOTO
文は、例外ハンドラから外側のブロックに分岐することはできます。
例4-30では、GOTO
文がIF
文に分岐しているため、エラーが発生します。
例4-30 IF文へのGOTO文の無効な分岐
SQL> DECLARE 2 valid BOOLEAN := TRUE; 3 BEGIN 4 GOTO update_row; 5 6 IF valid THEN 7 <<update_row>> 8 NULL; 9 END IF; 10 END; 11 / GOTO update_row; * ERROR at line 4: ORA-06550: line 4, column 3: PLS-00375: illegal GOTO statement; this GOTO cannot branch to label 'UPDATE_ROW' ORA-06550: line 6, column 12: PL/SQL: Statement ignored SQL>
NULL
文は、制御を次の文に渡すことを除き、何も実行しません。 一部の言語では、このような命令をno-op(何もしない)と呼びます。 構文は、「NULL文」を参照してください。
例4-31では、NULL
文によって、販売員のみがコミッションを受け取れることを明確にしています。
例4-31 アクションを実行しないことを明示するNULL文の使用
SQL> DECLARE
2 v_job_id VARCHAR2(10);
3 v_emp_id NUMBER(6) := 110;
4 BEGIN
5 SELECT job_id INTO v_job_id
6 FROM employees
7 WHERE employee_id = v_emp_id;
8
9 IF v_job_id = 'SA_REP' THEN
10 UPDATE employees
11 SET commission_pct = commission_pct * 1.2;
12 ELSE
13 NULL; -- Employee is not a sales rep
14 END IF;
15 END;
16 /
PL/SQL procedure successfully completed.
SQL>
NULL
文を使用すると、プレースホルダおよびスタブ・サブプログラムを簡単に作成できます。 例4-32では、NULL
文を使用してサブプログラムをコンパイルしています。実際の本体は、後で挿入できます。 NULL
文を使用する場合に警告が有効になっていると、unreachable
code
という警告が発生する可能性があります。 「PL/SQLのコンパイル時の警告の概要」を参照してください。
例4-32 サブプログラム作成時のプレースホルダとしてのNULLの使用
SQL> CREATE OR REPLACE PROCEDURE award_bonus 2 (emp_id NUMBER, 3 bonus NUMBER) AS 4 BEGIN -- Executable part starts here 5 NULL; -- Placeholder 6 -- (raises "unreachable code" if warnings enabled) 7 END award_bonus; 8 / Procedure created. SQL>
NULL
文を使用すると、考慮はするがアクションが必要ないことを示すことができます。 例4-33では、NULL
文によって、名前のない例外ではアクションを起こさないことを示しています。
例4-33 WHEN OTHER句でのNULL文の使用
SQL> CREATE OR REPLACE FUNCTION f 2 (a INTEGER, 3 b INTEGER) 4 RETURN INTEGER 5 AS 6 BEGIN 7 RETURN (a/b); 8 EXCEPTION 9 WHEN ZERO_DIVIDE THEN 10 ROLLBACK; 11 WHEN OTHERS THEN 12 NULL; 13 END; 14 / Function created. SQL>
詳細は、例1-16「スタンドアロンPL/SQLプロシージャの作成」を参照してください。