5 PL/SQLの制御文
PL/SQLには、条件付き選択文、繰り返し文および順次制御文の3つのカテゴリの制御文があります。
PL/SQLの制御文のカテゴリは次のとおりです。
-
条件付き選択文: データ値に応じて、異なる文を実行します。
条件選択文は、
IF
およびCASE
です。 -
ループ文: 一連の異なるデータ値を使用して、同じ文を実行します。
ループ文には、基本
LOOP
、FOR
LOOP
およびWHILE
LOOP
があります。EXIT
文は、制御をループの終わりに転送します。CONTINUE
文は、現在のループの反復を終了し、制御を次の反復に転送します。EXIT
およびCONTINUE
のいずれにもオプションのWHEN
句があり、これによって条件を指定できます。 -
順次制御文: PL/SQLのプログラミングでは重要ではありません。
順次制御文には、指定された文に移動する
GOTO
および何も実行しないNULL
があります。
5.1 条件付き選択文
条件付き選択文のIF
およびCASE
は、データ値の違いに応じて異なる文を実行します。
IF
文は、条件に応じて、一連の1つ以上の文を実行またはスキップします。IF
文には次の形式があります。
-
IF
THEN
-
IF
THEN
ELSE
-
IF
THEN
ELSIF
CASE
文を使用すると、一連の条件を基に、対応する文を選択して実行できます。CASE
文には次の形式があります。
-
単純な
CASE
文は、単一の式を評価して、複数の可能性のある値と比較します。 -
検索
CASE
文は、複数の条件を評価して、最初にTRUEになる条件を選択します。
CASE
文は、オプションごとにアクションが異なる場合に適しています。
5.1.1 IF THEN文
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;
例5-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.
5.1.2 IF THEN ELSE文
IF
THEN
ELSE
文の構造は、次のとおりです。
IF condition THEN statements ELSE else_statements END IF;
condition
の値がTRUEの場合、statements
が実行されますが、それ以外の場合は、else_statements
が実行されます。
例5-3に示すように、IF
文はネストできます。
構文の詳細は、「IF文」を参照してください。
例5-2 IF THEN ELSE文
この例では、sales
の値がquota
+200より大きい場合にのみ、THEN
とELSE
の間にある文が実行されます。それ以外の場合は、ELSE
とEND
IF
の間にある文が実行されます。
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
例5-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
5.1.3 IF THEN ELSIF文
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
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;
構文の詳細は、「IF文」を参照してください。
例5-4 IF THEN ELSIF文
この例では、sales
の値が50000より大きい場合、1番目と2番目の条件のいずれもTRUEになります。ただし、1番目の条件がTRUEであるため、bonus
には1500という値が代入され、2番目の条件はテストされません。bonus
に値1500が代入された後、制御はDBMS_OUTPUT
.PUT_LINE
の起動に移ります。
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.
例5-5 単純なCASE文をシミュレートするIF THEN ELSIF文
この例では、IF
THEN
ELSIF
文を多数のELSIF
句とともに使用し、単一の値を多くの使用可能な値と比較しています。この目的では、単純なCASE
文の方がより明確になります(例5-6を参照)。
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
5.1.4 単純なCASE文
単純な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
を呼び出します。
例5-6では、単純なCASE
文を使用して、単一の値を多くの使用可能な値と比較しています。例5-6のCASE
文は、例5-5のIF
THEN
ELSIF
文と論理的に等価です。
注意:
単純なCASE
式と同様に、単純なCASE
文の選択子の値がNULL
の場合、その選択子がWHEN
NULL
によって一致することはありません(例3-51を参照)。かわりに、検索CASE
文をWHEN
condition
IS
NULL
とともに使用します(例3-53を参照)。
例5-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
5.1.5 検索CASE文
検索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文」を参照してください。)
例5-7の検索CASE
文は、例5-6の単純なCASE
文と論理的に等価です。
例5-7と例5-6のいずれの場合も、ELSE
句をEXCEPTION
部に置き換えることができます。例5-8は、例5-7と論理的に等価です。
例5-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
例5-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
5.2 LOOP文
ループ文は、一連の異なる値を使用して同じ文を反復実行します。
LOOP
文は、次の3つの部分で構成されています。
- イテランド(ループ変数と呼ぶこともあります): ループ・ヘッダーからループ本体に値を渡します
- 繰返しコントロール: ループの値を生成します
- ループ本体: 値ごとに1回実行されます
loop_statement ::= [ iteration_scheme ] LOOP loop_body END LOOP [ label ]; iteration_scheme ::= WHILE expression | FOR iterator
ループ文には次のものがあります。
-
基本
LOOP
-
FOR
LOOP
-
カーソル
FOR
LOOP
-
WHILE
LOOP
ループを終了する文には次のものがあります。
-
EXIT
-
EXIT
WHEN
現行のループの反復を終了する文には次のものがあります。
-
CONTINUE
-
CONTINUE
WHEN
EXIT
、EXIT
WHEN
、CONTINUE
およびCONTINUE
WHEN
は、ループの内側の任意の場所に配置できますが、ループの外側に配置することはできません。ループの外側の文に制御を移してループを終了したりループの現行の反復を終了したりできるGOTO
文のかわりに、これらの文を使用することをお薦めします。
例外が呼び出された場合もループは終了します。
LOOP
文にはラベルを付けることができ、LOOP
文をネストすることもできます。わかりやすさを向上させるために、ネステッド・ループにはラベルを付けることをお薦めします。END
LOOP
文のラベルと同じループ文の先頭のラベルが一致していることを確認する必要があります(コンパイラはチェックしません)。
関連項目:
- GOTO文
- CONTINUE文
- 例外の詳細は、「例外処理の概要」を参照してください
-
カーソル
FOR
LOOP
の詳細は、「カーソルFOR LOOP文による問合せ結果セットの処理」を参照してください
5.2.1 基本LOOP文
基本LOOP
文の構造は、次のとおりです。
[ label ] LOOP statements END LOOP [ label ];
ループが反復されるたびにstatements
が実行され、制御がループの先頭に戻ります。無限ループが発生しないように、文または例外の呼び出しによってループを終了する必要があります。
関連項目:
5.2.2 FOR LOOP文の概要
FOR
LOOP
文では、ループ索引の値ごとに1つ以上の文を実行します。
FOR
LOOP
ヘッダーでは、イテレータを指定します。イテレータでは、イテランドと繰返しコントロールを指定します。繰返しコントロールは、ループ本体内でアクセスするためのイテランドに値のシーケンスを提供します。ループ本体にある文は、イテランドの値ごとに1回実行されます。
次の繰返しコントロールを使用できます。
ステップ範囲: 段階的な数値のシーケンスを生成する繰返しコントロール。ステップが指定されていない場合、カウント制御はステップが1の正の整数型のステップ範囲になります。
単一式: 単一式を評価する繰返しコントロール。
繰返し式: 単一式を繰り返し評価する繰返しコントロール。
Values Of: コレクションからのすべての値を順番に生成する繰返しコントロール。このコレクションは、ベクトル値式、カーソル、カーソル変数または動的SQLのいずれかになります。
Indices Of: コレクションからのすべての索引を順番に生成する繰返しコントロール。Values Ofについて示したすべてのコレクション型が許容されますが、Indices Ofは、コレクションがベクトル変数のときに最も有効です。
Pairs Of: コレクションからのすべての索引と値のペアを生成する繰返しコントロール。Values Ofに許容されるすべてのコレクションがPairs Ofにも許容されます。Pairs Of繰返しコントロールには、2つのイテランドが必要になります。
カーソル: カーソル、カーソル変数または動的SQLからのレコードをすべて生成する繰返しコントロール。
FOR
LOOP
文の構造は、次のとおりです。
[ label ] for_loop_header statements END LOOP [ label ]; for_loop_header ::= FOR iterator LOOP iterator ::= iterand_decl [, iterand_decl] IN iteration_ctl_seq iterand_decl ::= pls_identifier [ MUTABLE | IMMUTABLE ] [ constrained_type ] iteration_ctl_seq ::= qual_iteration_ctl [,]... qual_iteration_ctl ::= [ REVERSE ] iteration_control pred_clause_seq iteration_control ::= stepped_control | single_expression_control | values_of_control | indices_of_control | pairs_of_control | cursor_control pred_clause_seq ::= [ stopping_pred ] [ skipping_pred ] stopping_pred ::= WHILE boolean_expression skipping_pred ::= WHEN boolean_expression stepped_control ::= lower_bound .. upper_bound [ BY step ] single_expression_control ::= [ REPEAT ] expr
関連項目:
構文およびセマンティクスの詳細は、「FOR LOOP文」を参照してください
5.2.2.1 FOR LOOPのイテランド
FOR
LOOP
文の索引(イテランド)は、ループに対してローカルな変数として暗黙的または明示的に宣言します。
ループ内の文はイテランドの値を参照できますが、変更することはできません。ループの外側の文は、イテランドを参照できません。FOR
LOOP
文の実行後、イテランドは未定義になります。ループ・イテランドは、ループ・カウンタと呼ばれることもあります。
例5-9 索引値を変更しようとするFOR LOOP文
この例では、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; * PLS-00363: expression 'I' cannot be used as an assignment target ORA-06550: line 6, column 8: PL/SQL: Statement ignored
例5-10 外側の文によるFOR LOOP文の索引の参照
この例では、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)); * PLS-00201: identifier 'I' must be declared ORA-06550: line 6, column 3: PL/SQL: Statement ignored
例5-11 変数と同じ名前を持つFOR LOOP文の索引
この例に示すように、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
例5-12 FOR LOOP文による索引と同じ名前を持つ変数の参照
この例は、外側のブロックで宣言された変数をループの内側の文で参照できるように例5-11を変更する方法を示しています。
<<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
例5-13 同じ索引名のネストしたFOR LOOP文
この例では、ネストした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
5.2.2.2 イテランドの可変性
イテランドの可変性プロパティにより、ループ本体での代入を可能にするかどうかが決まります。
イテレータで指定したすべての繰返しコントロールがカーソル・コントロールの場合、イテランドはデフォルトで可変です。それ以外の場合、イテランドは不変です。イテランドのデフォルトの可変性プロパティは、イテランド変数の後ろにキーワードMUTABLE
またはIMMUTABLE
を指定することで、イテランド宣言内で変更できます。
イテランドの可変を宣言する際の考慮事項は、次のとおりです。
- Values Of繰返しコントロールのイテランドまたはPairs Of繰返しコントロールの値のイテランドに対する変更は、その繰返しコントロールで生成される値のシーケンスに影響しません。
- ステップ範囲の繰返しコントロールまたは繰り返される単一式の繰返しコントロールのイテランドに変更を加えることが、そのコントロールの動作と生成される値のシーケンスに影響を与える可能性があります。
- PL/SQLコンパイラは、イテランドを可変にすることで実行時のパフォーマンスに悪影響が現れる可能性を判断できます。また、それについての警告を報告することがあります。
5.2.2.3 複数の繰返しコントロール
複数の繰返しコントロールは、それらをカンマで区切って連鎖できます。
それぞれの繰返しコントロールには、制御式のセットがあります(一部のコントロールにはありません)。この式のセットは、コントロールの開始時に1回評価されます。これらの式の評価や評価された値をイテランドのタイプに変換することで、エラーが発生することがあります。この場合、ループが中断されて、通常の例外処理が発生します。イテランドは、繰返しコントロールのリストからアクセスできます。また、そのタイプのデフォルト値に初期設定されています。そのタイプにNOT NULL制約がある場合、最初の繰返しコントロールの制御式でのイテランドへの参照により、セマンティック・エラーが生成されます。これは、イテランドを暗黙的に初期化できないためです。ある繰返しコントロールがすべて処理されると、イテランドには、その繰返しコントロールの処理中に最後に代入された値が格納され、実行はその次の繰返しコントロールに進みます。イテランドに繰返しコントロールで値が代入されなかった場合は、その繰返しコントロールの開始前に保持していた値が維持されます。可変イテランドの最後の値がループ本体で変更されると、その変更された値は、後続の繰返しコントロールから制御式の評価時に参照できるようになります。
PL/SQLへの複数の繰返しコントロールの拡張
最初の繰返しコントロールが初期化されます。最初の繰返しコントロールのループが評価されます。その次の繰返しコントロールの制御式が評価されます。2番目の繰返しコントロールのループが評価されます。繰返しコントロールがなくなるまで、それぞれの繰返しコントロールとループが順番に評価されます。
例5-14 複数の繰返しコントロールの使用
この例は、ループ変数iが連続して3つの繰返しコントロールの値を取得することを示しています。イテレータの値は、デモ目的で出力されます。ループ制御が完全に消化された場合に次の繰返しコントロールが開始されることを示します。最後の繰返しコントロールが完全に消化されると、ループは完了します。
DECLARE i PLS_INTEGER; BEGIN FOR i IN 1..3, REVERSE i+1..i+10, 51..55 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP; END; /結果:
1 2 3 13 12 11 10 9 8 7 6 5 4 51 52 53 54 55
5.2.2.4 ステップ範囲の繰返しコントロール
ステップ範囲の繰返しコントロールは数値のシーケンスを生成します。
制御式は、下限、上限およびステップです。
stepped_control ::= [ REVERSE ] lower_bound..upper_bound [ BY step ] lower_bound ::= numeric_expression upper_bound ::= numeric_expression step ::= numeric_expression
PL/SQLへのステップ範囲の繰返しコントロールの拡張
繰返しコントロールの初期化時に、それぞれの制御式が評価されてイテランドのタイプに変換されます。Step
は、厳密に正の数値にする必要があります。制御式の評価中に例外が発生すると、ループが中断されて、通常の例外処理が発生します。ステップが指定されていない場合、ステップの値は1になります。ステップ範囲の繰返しコントロールによって生成される値は、ステップごとに下限から上限にまで達します。REVERSE
が指定されていると、その値は上限から下限に向けてステップごとに減少します。イテランドが浮動小数点型の場合、ループ制御値の組合せによっては、丸め誤差のために無限ループが生成されることがあります。これについて報告するセマンティクス分析または動的分析はありません。イテランドが可変でありループ本体で変更されると、変更された値は、その次のイテランドの更新で増分とループ消化テストのために使用されます。これにより、ループで処理される値のシーケンスが変更されることがあります。
例5-15 FOR LOOP文の範囲繰返しコントロール
この例では、iterand
がi
、lower_bound
が1、upper_bound
が3です。このループは1から3の数値を出力します。
BEGIN FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE (i); END LOOP; END; /
結果:
1 2 3
例5-16 反転FOR LOOP文の範囲繰返しコントロール
この例のFOR
LOOP
文では、3から1までの数値が出力されます。ループ変数iは、暗黙的にPLS_INTEGER
(ループのカウントおよび索引付けのデフォルト)として宣言されます。
BEGIN
FOR i IN REVERSE 1..3 LOOP
DBMS_OUTPUT.PUT_LINE (i);
END LOOP;
END;
/
結果:
3 2 1
例5-17 ステップ範囲の繰返しコントロール
BEGIN FOR n NUMBER(5,1) IN 1.0 .. 3.0 BY 0.5 LOOP DBMS_OUTPUT.PUT_LINE(n); END LOOP; END; /
1 1.5 2 2.5 3
例5-18 FOR LOOP文のSTEP句
この例では、FOR
LOOP
により、実質的に索引が5ずつ増加します。
BEGIN
FOR i IN 5..15 BY 5 LOOP
DBMS_OUTPUT.PUT_LINE (i);
END LOOP;
END;
結果:
5 10 15
例5-19 FOR LOOPのステップ範囲イテレータを使用した単純なステップ・フィルタ
この例では、単純なステップのフィルタを示します。このフィルタは、シグナル処理やリダクション・アプリケーションで使用されます。述語では、元のコレクションのK番目ごとの要素を作成対象のコレクションに渡すように指定しています。
FOR i IN start..finish LOOP IF (i - start) MOD k = 0 THEN newcol(i) := col(i) END IF; END LOOP;
ステップ・フィルタは、ステップ範囲イテレータを使用することで実装できます。
FOR i IN start..finish BY k LOOP newcol(i) := col(i) END LOOP;
それと同じフィルタは、修飾式に埋め込んだステップ繰返しコントロールを使用して、新しいコレクションを作成することでも実装できます。
newcol := col_t(FOR I IN start..finish BY k => col(i));
5.2.2.5単一式の繰返しコントロール
単一式の繰返しコントロールは、単一値を生成します。
single_expression_control ::= [ REPEAT ] expr
単一式の繰返しコントロールには、制御式はありません。
イテランドが可変のときに、ループ本体でイテランドを変更したことは、繰返しフォームで式を再評価するときに確認されます。
PL/SQLへの単一式の繰返しコントロールの拡張
式が評価され、その次の値を生成するためにイテランドのタイプに変換されます。停止述語があれば評価されます。TRUE
に評価されない場合は、繰返しコントロールが完全に消化されています。スキップ述語があれば評価されます。TRUE
に評価されない場合は、次のステップにスキップします。ループ本体を評価します。REPEAT
が指定されている場合、式が再度評価されます。それ以外の場合、繰返しコントロールは完全に消化されています。
例5-20 単一式の繰返しコントロール
この例は、1回実行されているループ本体を示しています。
BEGIN FOR i IN 1 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP; END; /
結果:
1
この例では、イテランドが1で始まり、停止述語がtrueに評価されるまでi * 2が繰り返し評価されます。
BEGIN FOR i IN 1, REPEAT i*2 WHILE i < 100 LOOP DBMS_OUTPUT.PUT_LINE(i); END LOOP; END; /
結果:
1 2 4 8 16 32 64
5.2.2.6 コレクションの繰返しコントロール
VALUES OF、INDICES OFおよびPAIRS OF繰返しコントロールは、コレクションから導出されるイテランド用の値のシーケンスを生成します。
collection_iteration_control ::= values_of_control | indices_of_control | pairs_of_control values_of_control ::= VALUES OF expr | VALUES OF (cursor_object) | VALUES OF (sql_statement) | VALUES OF cursor_variable | VALUES OF (dynamic_sql) indices_of_control ::= INDICES OF expr | INDICES OF (cursor_object) | INDICES OF (sql_statement) | INDICES OF cursor_variable | INDICES OF (dynamic_sql) pairs_of_control ::= PAIRS OF expr | PAIRS OF (cursor_object) | PAIRS OF (sql_statement) | PAIRS OF cursor_variable | PAIRS OF (dynamic_sql)
コレクション自体が制御式になります。このコレクションは、ベクトル値式、カーソル・オブジェクト、カーソル変数または動的SQLのいずれかになります。コレクションがnullの場合は、そのコレクションが定義されていて空であるものとして処理されます。
cursor_objectは、明示的なPL/SQLカーソル・オブジェクトです。sql_statementは、繰返しコントロールで直接指定したSQL文に応じて作成される暗黙的なPL/SQLカーソル・オブジェクトです。cursor_variableは、PL/SQL REF CURSOR
オブジェクトです。
VALUES OF繰返しコントロールのイテランドまたはVALUES OF
繰返しコントロールの値イテランドがループ本体で変更されたとしても、そのような変更によって繰返しコントロールがその次に生成する値には影響しません。
ループ本体でコレクションが変更された場合の動作は不定です。ループ本体の実行中に、カーソル変数がイテランド以外からアクセスされた場合の動作は不定です。ほとんどのINDICES OF
繰返しコントロールは、コレクションがベクトル変数でない場合に数列を生成します。
PL/SQLへのVALUES OF繰返しコントロールの拡張
コレクションは評価されてベクトルに代入されます。コレクションが空の場合は、繰返しコントロールが完全に消化されています。一時的な非表示索引が最初の要素(REVERSE
が指定されている場合は最後の要素)の索引で初期化されます。イテランドのその次の値を生成するために、一時的な索引に基づいて値がコレクションからフェッチされます。停止述語があれば評価されます。TRUE
に評価されない場合は、繰返しコントロールが完全に消化されています。スキップ述語があれば評価されます。TRUE
に評価されない場合は、次のステップにスキップします。ループ本体を評価します。索引をベクトル内の次の要素(REVERSEの場合は前の要素)の索引に一時的に進めます。次の値を決定して、繰返しコントロールが完全に消化されるまで各イテランド値で再反復します。
例5-21 VALUES OF
繰返しコントロール
この例では、コレクション・ベクトルの[11, 10, 34]からの値を出力します。繰返しコントロール変数iのイテランド値は、ベクトルの最初の要素、次の要素、および最後の要素の値です。
DECLARE
TYPE intvec_t IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
vec intvec_t := intvec_t(3 => 10, 1 => 11, 100 => 34);
BEGIN
FOR i IN VALUES OF vec LOOP
DBMS_OUTPUT.PUT_LINE(i);
END LOOP;
END;
/
結果:11 10 34
PL/SQLへのINDICES OF繰返しコントロールの拡張
コレクションは評価されてベクトルに代入されます。コレクションが空の場合は、繰返しコントロールが完全に消化されています。イテランドの次の値が決定されます(最初の要素の索引またはREVERSE
が指定されている場合は最後の要素の索引)次の値がイテランドに代入されます。停止述語があれば評価されます。TRUE
に評価されない場合は、繰返しコントロールが完全に消化されています。スキップ述語があれば評価されます。TRUE
に評価されない場合は、次のステップにスキップします。ループ本体が評価されます。イテランドを次の値に進めます。この値は、ベクトル内の次の要素(REVERSE
の場合は前の要素)の索引です。繰返しコントロールを完全に消化するまで、各イテランド値(次または前の要素の索引が代入された値)で再反復します。
例5-22 INDICES OF
繰返しコントロール
この例では、コレクション・ベクトルの[1, 3, 100]の索引を出力します。繰返しコントロール変数iのイテランド値は、ベクトルの最初の要素、次の要素、および最後の要素の索引です。
DECLARE
TYPE intvec_t IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
vec intvec_t := intvec_t(3 => 10, 1 => 11, 100 => 34);
FOR i IN INDICES OF vec LOOP
DBMS_OUTPUT.PUT_LINE(i);
END LOOP;
END;
/
1 3 100
PL/SQLへのPAIRS OF繰返しコントロールの拡張
コレクションは評価されてベクトルに代入されます。コレクションが空の場合は、繰返しコントロールが完全に消化されています。イテランドの次の索引値が決定されます(最初の要素の索引またはREVERSE
が指定されている場合は最後の要素の索引)次の値で索引付けされた次の要素の値がイテランドに代入されます。停止述語があれば評価されます。TRUE
に評価されない場合は、繰返しコントロールが完全に消化されています。スキップ述語があれば評価されます。TRUE
に評価されない場合は、次のステップにスキップします。ループ本体が評価されます。イテランドを次の索引値に進めます。この値は、ベクトル内の次の要素(REVERSE
の場合は前の要素)の索引です。繰返しコントロールが完全に消化されるまで各イテランド値で再反復します。
例5-23 PAIRS OF
繰返しコントロール
この例では、コレクション・ベクトルをコレクション結果に反転し、結果の索引値のペア(10 => 3、11 => 1、34 => 100)を出力します。
DECLARE
TYPE intvec_t IS TABLE OF PLS_INTEGER INDEX BY PLS_INTEGER;
vec intvec_t := intvec_t(3 => 10, 1 => 11, 100 => 34);
result intvec_t;
BEGIN
result := intvec_t(FOR i,j IN PAIRS OF vec INDEX j => i);
FOR i,j IN PAIRS OF result LOOP
DBMS_OUTPUT.PUT_LINE(i || '=>'|| j);
END LOOP;
END;
/
結果:10=>3 11=>1 34=>100
5.2.2.7 カーソルの繰返しコントロール
カーソルの繰返しコントロールは、明示カーソルまたは暗黙カーソルによって返されるレコードのシーケンスを生成します。
カーソル定義が制御式になります。カーソルの繰返しコントロールでは、REVERSE
は使用できません。
cursor_iteration__control ::= { cursor _object | sql_statement | cursor_variable | dynamic_sql }
cursor_objectは、明示的なPL/SQLカーソル・オブジェクトです。sql_statementは、繰返しコントロールで直接指定したSQL文に応じて作成される暗黙的なPL/SQLカーソル・オブジェクトです。cursor_variableは、PL/SQL REF CURSOR
オブジェクトです。カーソルの繰返しコントロールは、コレクションがカーソルであるときにVALUES OF
繰返しコントロールと同じになります。ループ本体でイテランドが変更されていても、繰返しコントロールによって生成される次の値には影響がありません。コレクションがカーソル変数の場合はオープンされている必要があります。そうしていないと、例外が発生します。繰返しコントロールが完全に消化されたときにもオープンしたままになります。ループ本体の実行中に、カーソル変数がイテランド以外からアクセスされると動作は不定になります。
PL/SQLへのカーソルの繰返しコントロールの拡張
カーソルは、イテランドのベクトルを作成するために評価されます。ベクトルが空の場合は、繰返しコントロールが完全に消化されています。イテランドの次の値を作成するために、ベクトルで値がフェッチされます。停止述語があれば評価されます。TRUE
に評価されない場合は、繰返しコントロールが完全に消化されています。スキップ述語があれば評価されます。TRUE
に評価されない場合は、次のステップにスキップします。ループ本体を評価します。繰返しコントロールが完全に消化されるまでフェッチした各イテランド値で同じことを再反復します。
例5-24 カーソルの繰返しコントロール
OPEN c FOR SELECT id, data FROM T; FOR r rec_t IN c LOOP result(r.id) := r.data; END LOOP; CLOSE c;
5.2.2.8 繰返しコントロールでの動的SQLの使用
... dynamic_sql ::= EXECUTE IMMEDIATE dynamic_sql_stmt [ using_clause ] using_clause ::= USING [ [ IN ] (bind_argument [,])+ ]
動的SQLは、カーソルまたはコレクションの繰返しコントロールで使用されることがあります。このような構成では、デフォルトのタイプを指定できません。最初の繰返しコントロールとして使用する場合は、イテランド(またはPairs Ofコントロールの値イテランド)に明示的なタイプを指定する必要があります。using_clauseは、使用可能な唯一の句です。INTO
句またはdynamic returning句は使用できません。指定したSQL文が行を返せない種類のものである場合は、通常のexecute immediate文でbulk collect into句またはinto句を指定したときと同じようなランタイム・エラーが報告されます。
例5-25 繰返しコントロールとしての動的SQLの使用
この例は、繰返しコントロールによる動的SQLからのすべてのレコードの生成を示しています。これにより、employee_idが103未満のすべての従業員のlast_nameおよびemployee_idを出力します。停止述語がTRUE
の場合にループ本体が実行されます。
DECLARE cursor_str VARCHAR2(500) := 'SELECT last_name, employee_id FROM hr.employees ORDER BY last_name'; TYPE rec_t IS RECORD (last_name VARCHAR2(25), employee_id NUMBER); BEGIN FOR r rec_t IN VALUES OF (EXECUTE IMMEDIATE cursor_str) WHEN r.employee_id < 103 LOOP DBMS_OUTPUT.PUT_LINE(r.last_name || ', ' || r.employee_id); END LOOP; END; /
結果:
De Haan, 102 King, 100 Kochhar, 101
例5-26 修飾式での繰返しコントロールとしての動的SQLの使用
v := vec_rec_t( FOR r rec_t IN (EXECUTE IMMEDIATE query_var) SEQUENCE => r);
5.2.2.9 停止述語句とスキップ述語句
停止述語句により、繰返しコントロールを完全に消化できます。その一方、スキップ述語句により、いくつかの値についてループ本体をスキップできます。
これらの述語句内の式は、制御式ではありません。
停止述語句により、繰返しコントロールを完全に消化できます。boolean_expressionは、ループの各反復の最初に評価されます。TRUE
に評価されない場合は、繰返しコントロールが完全に消化されています。
スキップ述語句により、いくつかの値についてループ本体をスキップできます。boolean_expressionが評価されます。TRUE
に評価されない場合、繰返しコントロールは次の値にスキップします。
pred_clause_seq ::= [stopping_pred] [skipping_pred] stopping_pred ::= WHILE boolean_expression skipping_pred ::= WHEN boolean_expression
例5-27 FOR LOOP停止述語句の使用
この例では、WHILE停止述語句による繰返しコントロールを示します。繰返しコントロールは停止述語がTRUE
に評価されなくなると完全に消化します。
BEGIN
FOR power IN 1, REPEAT power*2 WHILE power <= 64 LOOP
DBMS_OUTPUT.PUT_LINE(power);
END LOOP;
END;
/
結果:
1 2 4 8 16 32 64
例5-28 FOR LOOPスキップ述語句の使用
この例では、述語句をスキップするWHEN句を使用した繰返しコントロールを示します。スキップする述語がTRUE
に評価されない場合、繰返しコントロールは次の値にスキップします。
BEGIN
FOR power IN 2, REPEAT power*2 WHILE power <= 64 WHEN MOD(power, 32)= 0 LOOP
DBMS_OUTPUT.PUT_LINE(power);
END LOOP;
END;
/
結果:
2 32 64
5.2.3 WHILE LOOP文
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;
5.3 順次制御文
sequential control statementsであるGOTO
文とNULL文
は、PL/SQLプログラミングではIF
文やLOOP
文ほど重要ではありません。
指定した文に移動するGOTO
文が必要となることはほとんどありません。ただし、これを使用すると論理を単純化できる場合もあります。
何も実行しないNULL
文には、条件文の意味とアクションを明確にすることによって、コードをわかりやすくする効果があります。
ここでのトピック
5.3.1 GOTO文
GOTO
文は無条件に制御をラベルに移します。ラベルは有効範囲の中で他と重複しないもので、実行可能文かPL/SQLブロックの前に置かれている必要があります。GOTO
文が実行されると、ラベルが付けられた文またはブロックに制御が移ります。
GOTO
文の制限については、「GOTO文」を参照してください。
GOTO
文の使用は最小限にしてください(多用すると、コードの理解やメンテナンスが困難になります)。深いネスト構造から例外ハンドラに制御を移す場合、GOTO
文は使用しないでください。かわりに、例外を呼び出してください。PL/SQLの例外処理メカニズムの詳細は、「PL/SQLのエラー処理」を参照してください。
このGOTO
文は、参照されたラベルが置かれている最初の外側のブロックに制御を移しています。
5.3.2 NULL文
NULL
文は、後続の文に制御を移すのみです。一部の言語では、このような命令をno-op(何もしない)と呼びます。
NULL
文は、次のように使用できます。
-
GOTO
文のターゲットを提供する -
条件文の意味とアクションを明確にすることで、わかりやすくする
-
プレースホルダとスタブ・サブプログラムを作成する
-
考慮はするがアクションは不要なことを示す
注意:
NULL
文を使用する場合に警告が有効になっていると、unreachable
code
という警告が発生する可能性があります。警告の詳細は、「コンパイル時の警告」を参照してください。
例5-29 アクションを実行しないことを明示するNULL文
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;
/
例5-30 サブプログラム作成時のプレースホルダとしてのNULL文
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; /
例5-31 単純なCASE文のELSE句でのNULL文
NULL
文によって、A、B、C、D、F以外のgradeではアクションを実施しないことを示しています。
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