4.2 LOOP文
ループ文は、一連の異なる値を使用して、同じ文を実行します。ループ文には次のものがあります。
-
基本
LOOP -
FORLOOP -
カーソル
FORLOOP -
WHILELOOP
ループを終了する文には次のものがあります。
-
EXIT -
EXITWHEN
現行のループの反復を終了する文には次のものがあります。
-
CONTINUE -
CONTINUEWHEN
EXIT、EXIT WHEN、CONTINUEおよびCONTINUE WHENは、ループ内の任意の場所に置くことができますが、ループの外に置くことはできません。ループの外の文に制御を移してループまたは現行のループの反復を終了することができるGOTO文のかわりに、これらの文を使用することをお薦めします。(例外が呼び出された場合もループは終了します。例外の詳細は、「例外処理の概要」を参照してください。)
LOOP文にはラベルを付けることができ、LOOP文をネストすることもできます。わかりやすさを向上させるために、ネステッド・ループにはラベルを付けることをお薦めします。END LOOP文のラベルと同じループ文の先頭のラベルが一致していることを確認する必要があります(コンパイラはチェックしません)。
ここでのトピック
カーソルFOR LOOPの詳細は、「カーソルFOR LOOP文による問合せ結果セットの処理」を参照してください。
4.2.1 基本LOOP文
基本LOOP文の構造は、次のとおりです。
[ label ] LOOP statements END LOOP [ label ];
ループが反復されるたびにstatementsが実行され、制御がループの先頭に戻ります。無限ループが発生しないように、文または例外の呼び出しによってループを終了する必要があります。
関連項目:
4.2.2 EXIT文
EXIT文は、現行のループの反復を無条件で終了し、カレント・ループまたはラベルが付けられている外側のループのいずれかの最後に制御を移します。
例4-9では、基本LOOP文内のEXIT文が無条件でカレント・ループの最後に制御を移します。
関連項目:
例4-9 EXIT文が含まれている基本LOOP文
DECLARE x NUMBER := 0; BEGIN LOOP DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR(x)); x := x + 1; IF x > 3 THEN EXIT; END IF; END LOOP; -- After EXIT, control resumes here DBMS_OUTPUT.PUT_LINE(' After loop: x = ' || TO_CHAR(x)); END; /
結果:
Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop: x = 3 After loop: x = 4
4.2.3 EXIT WHEN文
EXIT WHEN文は、WHEN句内の条件がTRUEの場合、現行のループの反復を終了し、カレント・ループまたはラベルが付けられている外側のループのいずれかの最後に制御を移します。
制御がEXIT WHEN文に達するたびに、WHEN句内の条件が評価されます。条件がTRUEでない場合、EXIT WHEN文は何も実行しません。例4-10に示すように、無限ループが発生しないように、ループ内の文で条件がTRUEになるようにする必要があります。
例4-10では、xが3より大きい場合、基本LOOP文内のEXIT WHEN文がカレント・ループの最後に制御を移します。例4-10は例4-9と論理的に等価です。
関連項目:
例4-11では、基本LOOP文が別の基本LOOP文にネストされていて、両方にラベルが付けられています。内部ループにはEXIT WHEN文が2つあり、1つは内部ループを終了し、もう1つは外部ループを終了します。
内部ループ内のEXIT WHEN文から外部ループに制御を移すことができるのは、外部ループにラベルが付けられている場合のみです。
例4-12では、外部ループにはラベルが付いていないため、内部ループは制御を外部ループに移すことができません。
例4-10 EXIT WHEN文が含まれている基本LOOP文
DECLARE x NUMBER := 0; BEGIN LOOP DBMS_OUTPUT.PUT_LINE('Inside loop: x = ' || TO_CHAR(x)); x := x + 1; -- prevents infinite loop EXIT WHEN x > 3; END LOOP; -- After EXIT statement, control resumes here DBMS_OUTPUT.PUT_LINE('After loop: x = ' || TO_CHAR(x)); END; /
結果:
Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop: x = 3 After loop: x = 4
例4-11 EXIT WHEN文が含まれているネストされたラベル付き基本LOOP文
DECLARE s PLS_INTEGER := 0; i PLS_INTEGER := 0; j PLS_INTEGER; BEGIN <<outer_loop>> LOOP i := i + 1; j := 0; <<inner_loop>> LOOP j := j + 1; s := s + i * j; -- Sum several products EXIT inner_loop WHEN (j > 5); EXIT outer_loop WHEN ((i * j) > 15); END LOOP inner_loop; END LOOP outer_loop; DBMS_OUTPUT.PUT_LINE ('The sum of products equals: ' || TO_CHAR(s)); END; /
結果:
The sum of products equals: 166
例4-12 EXIT WHEN文が含まれているネストされたラベルなしの基本LOOP文
DECLARE
i PLS_INTEGER := 0;
j PLS_INTEGER := 0;
BEGIN
LOOP
i := i + 1;
DBMS_OUTPUT.PUT_LINE ('i = ' || i);
LOOP
j := j + 1;
DBMS_OUTPUT.PUT_LINE ('j = ' || j);
EXIT WHEN (j > 3);
END LOOP;
DBMS_OUTPUT.PUT_LINE ('Exited inner loop');
EXIT WHEN (i > 2);
END LOOP;
DBMS_OUTPUT.PUT_LINE ('Exited outer loop');
END;
/
結果:
i = 1 j = 1 j = 2 j = 3 j = 4 Exited inner loop i = 2 j = 5 Exited inner loop i = 3 j = 6 Exited inner loop Exited outer loop PL/SQL procedure successfully completed.
4.2.4 CONTINUE文
CONTINUE文は、現行のループの反復を無条件で終了し、カレント・ループまたはラベルが付けられている外側のループのいずれかの次の反復に制御を移します。
例4-13では、基本LOOP文内のCONTINUE文が無条件でカレント・ループの次の反復に制御を移します。
関連項目:
例4-13 基本LOOP文内のCONTINUE文
DECLARE x NUMBER := 0; BEGIN LOOP -- After CONTINUE statement, control resumes here DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR(x)); x := x + 1; IF x < 3 THEN CONTINUE; END IF; DBMS_OUTPUT.PUT_LINE ('Inside loop, after CONTINUE: x = ' || TO_CHAR(x)); EXIT WHEN x = 5; END LOOP; DBMS_OUTPUT.PUT_LINE (' After loop: x = ' || TO_CHAR(x)); END; /
結果:
Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop, after CONTINUE: x = 3 Inside loop: x = 3 Inside loop, after CONTINUE: x = 4 Inside loop: x = 4 Inside loop, after CONTINUE: x = 5 After loop: x = 5
4.2.5 CONTINUE WHEN文
CONTINUE WHEN文は、WHEN句内の条件がTRUEの場合、現行のループの反復を終了し、カレント・ループまたはラベルが付けられている外側のループのいずれかの次の反復に制御を移します。
制御がCONTINUE WHEN文に達するたびに、WHEN句内の条件が評価されます。条件がTRUEでない場合、CONTINUE WHEN文は何も実行しません。
例4-14では、xが3より小さい場合、基本LOOP文内のCONTINUE WHEN文がカレント・ループの次の反復に制御を移します。例4-14は例4-13と論理的に等価です。
関連項目:
例4-14 基本LOOP文内のCONTINUE WHEN文
DECLARE x NUMBER := 0; BEGIN LOOP -- After CONTINUE statement, control resumes here DBMS_OUTPUT.PUT_LINE ('Inside loop: x = ' || TO_CHAR(x)); x := x + 1; CONTINUE WHEN x < 3; DBMS_OUTPUT.PUT_LINE ('Inside loop, after CONTINUE: x = ' || TO_CHAR(x)); EXIT WHEN x = 5; END LOOP; DBMS_OUTPUT.PUT_LINE (' After loop: x = ' || TO_CHAR(x)); END; /
結果:
Inside loop: x = 0 Inside loop: x = 1 Inside loop: x = 2 Inside loop, after CONTINUE: x = 3 Inside loop: x = 3 Inside loop, after CONTINUE: x = 4 Inside loop: x = 4 Inside loop, after CONTINUE: x = 5 After loop: x = 5
4.2.6 FOR LOOP文
FOR LOOP文は、指定された範囲にループ索引がある場合に1つ以上の文を実行します。この文の構造は、次のとおりです。
[ label ] FOR index IN [ REVERSE ] lower_bound..upper_bound LOOP statements END LOOP [ label ];
REVERSEが指定されていない場合、indexの値はlower_boundから始まり、upper_boundに達するまで、ループを反復するたびに1ずつ増加します。lower_boundがupper_boundよりも大きい場合、statementsは実行されません。
REVERSEが指定されている場合、indexの値はupper_boundから始まり、lower_boundに達するまで、ループを反復するたびに1ずつ減少します。upper_boundがlower_boundよりも小さい場合、statementsは実行されません。
EXIT、EXIT WHEN、CONTINUEまたはCONTINUE WHENをstatements内に置くと、ループまたは現行のループの反復を途中で終了させることができます。
ヒント:
問合せの結果セットの行を処理するには、整数の範囲のかわりに問合せが含まれているカーソルFOR LOOPを使用してください。詳細は、「 カーソルFOR LOOP文による問合せ結果セットの処理」を参照してください。
関連項目:
例4-15では、indexはi、lower_boundは1、upper_boundは3です。このループは1から3の数値を出力します。
例4-16のFOR LOOP文は、例4-15を反転したものであり、3から1の数値を出力します。
一部の言語のFOR LOOPには、ループ索引の増分値を1以外に指定することが可能なSTEP句があります。PL/SQLでSTEP句をシミュレートするには、ループ索引の参照のたびに、必要な増分値を乗じます。
例4-17では、FOR LOOPの索引が実質的に5ずつ増加します。
ここでのトピック
例4-15 FOR LOOP文
BEGIN
DBMS_OUTPUT.PUT_LINE ('lower_bound < upper_bound');
FOR i IN 1..3 LOOP
DBMS_OUTPUT.PUT_LINE (i);
END LOOP;
DBMS_OUTPUT.PUT_LINE ('lower_bound = upper_bound');
FOR i IN 2..2 LOOP
DBMS_OUTPUT.PUT_LINE (i);
END LOOP;
DBMS_OUTPUT.PUT_LINE ('lower_bound > upper_bound');
FOR i IN 3..1 LOOP
DBMS_OUTPUT.PUT_LINE (i);
END LOOP;
END;
/
結果:
lower_bound < upper_bound 1 2 3 lower_bound = upper_bound 2 lower_bound > upper_bound
例4-16 反転FOR LOOP文
BEGIN
DBMS_OUTPUT.PUT_LINE ('upper_bound > lower_bound');
FOR i IN REVERSE 1..3 LOOP
DBMS_OUTPUT.PUT_LINE (i);
END LOOP;
DBMS_OUTPUT.PUT_LINE ('upper_bound = lower_bound');
FOR i IN REVERSE 2..2 LOOP
DBMS_OUTPUT.PUT_LINE (i);
END LOOP;
DBMS_OUTPUT.PUT_LINE ('upper_bound < lower_bound');
FOR i IN REVERSE 3..1 LOOP
DBMS_OUTPUT.PUT_LINE (i);
END LOOP;
END;
/
結果:
upper_bound > lower_bound 3 2 1 upper_bound = lower_bound 2 upper_bound < lower_bound
例4-17 FOR LOOP文でのSTEP句のシミュレート
DECLARE step PLS_INTEGER := 5; BEGIN FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE (i*step); END LOOP; END; /
結果:
5 10 15
4.2.6.1 FOR LOOP索引
FOR LOOP文の索引は、ループに対してローカルであるPLS_INTEGER型の変数として暗黙的に宣言されます。ループ内の文は索引の値を参照できますが、変更することはできません。ループの外側の文は、索引を参照できません。FOR LOOP文の実行後、索引は未定義となります。(ループ索引はループ・カウンタと呼ばれる場合があります。)
例4-18では、FOR LOOP文が索引の値を変更しようとしているため、エラーが発生します。
例4-19では、FOR LOOP文の外側の文がループ索引を参照しようとしているため、エラーが発生します。
例4-20に示すように、FOR LOOP文の索引の名前が、外側のブロックで宣言された変数の名前と同じ場合、ローカルの暗黙的な宣言によって他の宣言が隠されます。
例4-21は、Example 4-20を変更して、外側のブロックで宣言された変数をループ内の文で参照できるようにする方法を示しています。
例4-22では、ネストしたFOR LOOP文の索引で同じ名前を使用しています。内部ループでは、外部ループのラベルで参照を修飾して、外部ループの索引を参照しています。また、内部ループでは、意味を明確にするために、それ自体のラベルでそれ自体の索引への参照も修飾しています。
例4-18 索引値を変更しようとするFOR LOOP文
BEGIN FOR i IN 1..3 LOOP IF i < 3 THEN DBMS_OUTPUT.PUT_LINE (TO_CHAR(i)); ELSE i := 2; END IF; END LOOP; END; /
結果:
i := 2; * ERROR at line 6: ORA-06550: line 6, column 8: PLS-00363: expression 'I' cannot be used as an assignment target ORA-06550: line 6, column 8: PL/SQL: Statement ignored
例4-19 外側の文によるFOR LOOP文の索引の参照
BEGIN FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE ('Inside loop, i is ' || TO_CHAR(i)); END LOOP; DBMS_OUTPUT.PUT_LINE ('Outside loop, i is ' || TO_CHAR(i)); END; /
結果:
DBMS_OUTPUT.PUT_LINE ('Outside loop, i is ' || TO_CHAR(i));
*
ERROR at line 6:
ORA-06550: line 6, column 58:
PLS-00201: identifier 'I' must be declared
ORA-06550: line 6, column 3:
PL/SQL: Statement ignored
例4-20 変数と同じ名前を持つFOR LOOP文の索引
DECLARE i NUMBER := 5; BEGIN FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE ('Inside loop, i is ' || TO_CHAR(i)); END LOOP; DBMS_OUTPUT.PUT_LINE ('Outside loop, i is ' || TO_CHAR(i)); END; /
結果:
Inside loop, i is 1 Inside loop, i is 2 Inside loop, i is 3 Outside loop, i is 5
例4-21 FOR LOOP文による索引と同じ名前を持つ変数の参照
<<main>> -- Label block. DECLARE i NUMBER := 5; BEGIN FOR i IN 1..3 LOOP DBMS_OUTPUT.PUT_LINE ( 'local: ' || TO_CHAR(i) || ', global: ' || TO_CHAR(main.i) -- Qualify reference with block label. ); END LOOP; END main; /
結果:
local: 1, global: 5 local: 2, global: 5 local: 3, global: 5
例4-22 同じ索引名のネストしたFOR LOOP文
BEGIN <<outer_loop>> FOR i IN 1..3 LOOP <<inner_loop>> FOR i IN 1..3 LOOP IF outer_loop.i = 2 THEN DBMS_OUTPUT.PUT_LINE ('outer: ' || TO_CHAR(outer_loop.i) || ' inner: ' || TO_CHAR(inner_loop.i)); END IF; END LOOP inner_loop; END LOOP outer_loop; END; /
結果:
outer: 2 inner: 1 outer: 2 inner: 2 outer: 2 inner: 3
4.2.6.2 下限と上限
FOR LOOP文の下限と上限には、数値リテラル、数値変数または数式を使用できます。境界の数値が指定されていない場合、PL/SQLは事前定義の例外VALUE_ERRORを呼び出します。
例4-24では、FOR LOOP文の上限が変数になっており、値は実行時に決定されます。
例4-23 FOR LOOP文の境界
DECLARE first INTEGER := 1; last INTEGER := 10; high INTEGER := 100; low INTEGER := 12; BEGIN -- Bounds are numeric literals: FOR j IN -5..5 LOOP NULL; END LOOP; -- Bounds are numeric variables: FOR k IN REVERSE first..last LOOP NULL; END LOOP; -- Lower bound is numeric literal, -- Upper bound is numeric expression: FOR step IN 0..(TRUNC(high/low) * 2) LOOP NULL; END LOOP; END; /
例4-24 実行時に境界が指定されるFOR LOOP文
DROP TABLE temp; CREATE TABLE temp ( emp_no NUMBER, email_addr VARCHAR2(50) ); DECLARE emp_count NUMBER; BEGIN SELECT COUNT(employee_id) INTO emp_count FROM employees; FOR i IN 1..emp_count LOOP INSERT INTO temp (emp_no, email_addr) VALUES(i, 'to be added later'); END LOOP; END; /
4.2.6.3 FOR LOOP文内のEXIT WHENまたはCONTINUE WHEN文
特定の条件が発生した場合はただちにFOR LOOP文を終了する必要があるとします。FOR LOOP文内のEXIT WHEN文に、条件を指定できます。
例4-25では、FOR LOOP文が10回実行されますが、中のFETCH文から行が戻されなくなった場合は、ただちにループが終了します。
次は、途中で終了する必要のあるFOR LOOP文が、別のFOR LOOPの中にネストされているとします。内側のループが途中で終了した場合に外側のループも終了する必要があるなら、例4-26に示すように、外側のループにラベルを付け、EXIT WHEN文にラベル名を指定します。
内側のループが途中で終了しても外側のループの現行の反復を完了する必要があるなら、例4-27に示すように、外側のループにラベルを付け、CONTINUE WHEN文にラベル名を指定します。
関連項目:
例外の詳細は、「例外処理の概要」を参照してください。特定の条件が発生した場合は、例外を使用してただちにループを終了させることもできます
例4-25 FOR LOOP文内のEXIT WHEN文
DECLARE
v_employees employees%ROWTYPE;
CURSOR c1 is SELECT * FROM employees;
BEGIN
OPEN c1;
-- Fetch entire row into v_employees record:
FOR i IN 1..10 LOOP
FETCH c1 INTO v_employees;
EXIT WHEN c1%NOTFOUND;
-- Process data here
END LOOP;
CLOSE c1;
END;
/
例4-26 内側のFOR LOOP文内のEXIT WHEN文
DECLARE v_employees employees%ROWTYPE; CURSOR c1 is SELECT * FROM employees; BEGIN OPEN c1; -- Fetch entire row into v_employees record: <<outer_loop>> FOR i IN 1..10 LOOP -- Process data here FOR j IN 1..10 LOOP FETCH c1 INTO v_employees; EXIT outer_loop WHEN c1%NOTFOUND; -- Process data here END LOOP; END LOOP outer_loop; CLOSE c1; END; /
例4-27 内側のFOR LOOP文内のCONTINUE WHEN文
DECLARE v_employees employees%ROWTYPE; CURSOR c1 is SELECT * FROM employees; BEGIN OPEN c1; -- Fetch entire row into v_employees record: <<outer_loop>> FOR i IN 1..10 LOOP -- Process data here FOR j IN 1..10 LOOP FETCH c1 INTO v_employees; CONTINUE outer_loop WHEN c1%NOTFOUND; -- Process data here END LOOP; END LOOP outer_loop; CLOSE c1; END; /
4.2.7 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;
例4-28では、1番目のWHILE LOOP文内の文は実行されず、2番目のWHILE LOOP文内の文が1回実行されます。
例4-28 WHILE LOOP文
DECLARE
done BOOLEAN := FALSE;
BEGIN
WHILE done LOOP
DBMS_OUTPUT.PUT_LINE ('This line does not print.');
done := TRUE; -- This assignment is not made.
END LOOP;
WHILE NOT done LOOP
DBMS_OUTPUT.PUT_LINE ('Hello, world!');
done := TRUE;
END LOOP;
END;
/
結果:
Hello, world!