この章では、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プロシージャの作成」を参照してください。