5 PL/SQLの制御文

PL/SQLには、条件付き選択文、繰り返し文および順次制御文の3つのカテゴリの制御文があります。

PL/SQLの制御文のカテゴリは次のとおりです。

  • 条件付き選択文: データ値に応じて、異なる文を実行します。

    条件選択文は、IFおよびCASEです。

  • ループ文: 一連の異なるデータ値を使用して、同じ文を実行します。

    ループ文には、基本LOOPFOR 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変数は、TRUEFALSENULLのいずれかです。次のようには記述しないでください。

IF overdrawn = TRUE THEN
  RAISE insufficient_funds;
END IF;

かわりに、次のように記述してください。

IF overdrawn THEN
  RAISE insufficient_funds;
END IF;

例5-1 IF THEN文

この例では、salesの値がquota+200より大きい場合にのみ、THENEND 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より大きい場合にのみ、THENELSEの間にある文が実行されます。それ以外の場合は、ELSEEND 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_valueselectorと等しい最初のstatementsを実行します。残りの条件は評価されません。いずれのselector_valueselectorと等しくない場合、CASE文はelse_statements(存在する場合)を実行しますが、else_statementsが存在しない場合は、事前定義の例外CASE_NOT_FOUNDを呼び出します。

例5-6では、単純なCASE文を使用して、単一の値を多くの使用可能な値と比較しています。例5-6CASE文は、例5-5IF 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. イテランド(ループ変数と呼ぶこともあります): ループ・ヘッダーからループ本体に値を渡します
  2. 繰返しコントロール: ループの値を生成します
  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

EXITEXIT WHENCONTINUEおよびCONTINUE WHENは、ループの内側の任意の場所に配置できますが、ループの外側に配置することはできません。ループの外側の文に制御を移してループを終了したりループの現行の反復を終了したりできるGOTO文のかわりに、これらの文を使用することをお薦めします。

例外が呼び出された場合もループは終了します。

LOOP文にはラベルを付けることができ、LOOP文をネストすることもできます。わかりやすさを向上させるために、ネステッド・ループにはラベルを付けることをお薦めします。END LOOP文のラベルと同じループ文の先頭のラベルが一致していることを確認する必要があります(コンパイラはチェックしません)。

関連項目:

5.2.1 基本LOOP文

基本LOOP文の構造は、次のとおりです。

[ label ] LOOP
  statements
END LOOP [ label ];

ループが反復されるたびにstatementsが実行され、制御がループの先頭に戻ります。無限ループが発生しないように、文または例外の呼び出しによってループを終了する必要があります。

関連項目:

「基本LOOP文」

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文の範囲繰返しコントロール

この例では、iterandilower_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 ステップ範囲の繰返しコントロール

この例は、明示的にNUMBER(5,1)として宣言されたループ変数nを示しています。カウンタの増分は0.5です。
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 カーソルの繰返しコントロール

この例では、表tのidからデータへの連想配列マッピングを作成します。
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文」を参照してください。

EXITEXIT WHENCONTINUEまたはCONTINUE WHENstatements内に置くと、ループまたは現行のループの反復を途中で終了させることができます。

一部の言語は、条件をループの先頭ではなく最後でテストする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