【PL/SQL】ネストブロック完全ガイド|スコープ・Shadowing・例外伝播・ローカル関数・SAVEPOINT・実務5パターン

【PL/SQL】ネストされたブロックの書き方とスコープの考え方 PL/SQL

PL/SQLの強みはブロック構造を入れ子(ネスト)にできることです。BEGIN ... END;の中にさらにDECLARE ... BEGIN ... END;を書いて局所的な変数・例外・カーソルを切り分けられる、言わばC言語のブロックや関数のローカルスコープに相当する仕組みです。

入門記事の多くは「内側から外側の変数は見える、逆は見えない」という基本だけで終わりますが、実務では変数の隠蔽(Shadowing)をラベルで参照解決、例外が伝播する正確なルール、局所例外の再利用、ローカルサブプログラム、SAVEPOINT+ネストでの部分ロールバック、PRAGMA AUTONOMOUS_TRANSACTIONで独立トランザクション化まで押さえる必要があります。

この記事ではPL/SQLのネストブロックをスコープ理論・例外伝播・実務応用の3軸で徹底解説。ラベル付きブロック(<<name>>)、Shadowingされた外側変数へのアクセス、例外のネスト伝播ルール、ローカルサブプログラム(ブロック内定義)、ネストCursor、SAVEPOINT+ネスト構造、AUTONOMOUS_TRANSACTION、実務5パターン、アンチパターン7選まで網羅した決定版です。関連は【PL/SQL】基本構文完全ガイド【PL/SQL】変数・定数完全ガイド【PL/SQL】例外処理完全ガイドも併読推奨。

この記事で学べること

  • ネストブロックの基本構文と可視性ルール
  • ラベル付きブロック(<<name>>による外側変数の明示アクセス
  • Shadowing(変数隠蔽)の仕組みと解決方法
  • 例外の伝播ルール完全版(どこで捕まらないと親へ伝わるか)
  • 局所ユーザー定義例外のスコープ
  • ローカルサブプログラム(ブロック内のプロシージャ/ファンクション)
  • ネストしたCursorのスコープ管理
  • SAVEPOINT+ネストブロックで部分ロールバック
  • PRAGMA AUTONOMOUS_TRANSACTIONで独立トランザクション化
  • ネスト深度の実用上限とリファクタリング指針
  • 実務5パターン:try-catch風/リトライ/部分ロールバック/局所関数/例外の翻訳
  • アンチパターン7選(深すぎるネスト/Shadowing無自覚等)
スポンサーリンク

30秒クイックリファレンス:ネスト基本

ブロックの入れ子
&lt;&lt;outer&gt;&gt;                           -- 外側ラベル(省略可)
DECLARE
  v_msg VARCHAR2(50) := 'outer';
BEGIN
  DBMS_OUTPUT.PUT_LINE(v_msg);  -- outer

  &lt;&lt;inner&gt;&gt;                         -- 内側ラベル(省略可)
  DECLARE
    v_msg VARCHAR2(50) := 'inner';   -- 外側と同名(Shadowing)
  BEGIN
    DBMS_OUTPUT.PUT_LINE(v_msg);         -- inner(内側が優先)
    DBMS_OUTPUT.PUT_LINE(outer.v_msg);   -- outer(ラベルで外側を明示)
  EXCEPTION
    WHEN OTHERS THEN
      RAISE;    -- 外側に伝播
  END inner;

  DBMS_OUTPUT.PUT_LINE(v_msg);  -- outer(内側のv_msgはもう存在しない)
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('outer catch: ' || SQLERRM);
END outer;
/

3つの基本ルール:内側のブロックは外側の変数を参照可能、②外側からは内側の変数は見えない、③同名変数は内側が優先(Shadowing)、ラベル付きブロックならラベル名.変数名で外側を参照できる。

スコープの厳密なルール

PL/SQLの識別子のスコープは静的(字面上の)スコープで、「宣言された場所からブロック終了まで有効」が原則。動的スコープ(呼び出し階層)とは違う点に注意。

可視性マトリクス

宣言場所 外側から見える? 内側から見える? 同ブロック内から見える?
外側ブロック ◎(そのまま参照) ◎(Shadowingされない限り)
内側ブロック ✗(PLS-00201エラー) そのブロック内のみ◎
兄弟ブロック ✗(別スコープ)
宣言位置と有効範囲の例
DECLARE
  v_a NUMBER := 1;       -- スコープ: 外側全体
BEGIN
  -- v_a, v_b は参照可能
  DECLARE
    v_b NUMBER := 2;     -- スコープ: 内側のみ
  BEGIN
    DBMS_OUTPUT.PUT_LINE(v_a || ',' || v_b);  -- OK: 1,2
  END;

  -- ここで v_b は参照不可(PLS-00201)
  -- DBMS_OUTPUT.PUT_LINE(v_b);

  -- 兄弟ブロック:別の DECLARE で v_c を宣言しても兄弟間で見えない
  DECLARE
    v_c NUMBER := 3;
  BEGIN
    DBMS_OUTPUT.PUT_LINE(v_a || ',' || v_c);  -- OK: 1,3
  END;
END;

宣言の順序も重要

PL/SQLは前方参照不可。同じDECLARE内でも「下で宣言した変数を上の初期化で使う」のはエラー。互いに参照し合う変数は順序を意識して宣言するか、変数を2段階で宣言+代入(BEGIN内で代入)します。

ラベル付きブロック:Shadowing解決と明示脱出

Shadowingされた外側変数を参照

ラベル.変数 で外側を参照
&lt;&lt;main&gt;&gt;                           -- ラベル名は識別子と同じ規則
DECLARE
  v_total NUMBER := 0;
BEGIN
  DECLARE
    v_total NUMBER := 0;       -- 外側の v_total を隠蔽
  BEGIN
    FOR i IN 1..10 LOOP
      v_total := v_total + i;        -- 内側の v_total(合計 55)
      main.v_total := main.v_total + 1;  -- 外側の v_total(+1ずつ加算 = 10)
    END LOOP;
    DBMS_OUTPUT.PUT_LINE(v_total);         -- 55
  END;
  DBMS_OUTPUT.PUT_LINE(v_total);           -- 10
END main;

ラベル付きループでネスト脱出

EXIT ラベル名 で多重脱出
&lt;&lt;search&gt;&gt;
FOR i IN 1..10 LOOP
  FOR j IN 1..10 LOOP
    IF i * j &gt; 30 THEN
      EXIT search;    -- 外側ループまで一気に脱出
    END IF;
    IF j &gt; 5 THEN
      CONTINUE search; -- 外側ループの次イテレーションへ
    END IF;
    DBMS_OUTPUT.PUT_LINE(i || ',' || j);
  END LOOP;
END LOOP search;

ラベルの3大用途:①Shadowing解決(ラベル.変数)、②ネストループの明示脱出(EXIT/CONTINUE ラベル)、③END ラベルで対応明示(可読性UP)。ラベル名は処理の意味を表すouterではなくprocess_each_dept等)と保守時に意図が伝わりやすい。

変数の隠蔽(Shadowing)と対処

Shadowingの典型事故
-- ❌ Shadowingに気づかずバグ
DECLARE
  status VARCHAR2(20) := 'active';   -- 外側
BEGIN
  DECLARE
    status VARCHAR2(20);                -- 内側:NULL(初期化忘れ)
  BEGIN
    -- ここで status を使うと内側の NULL が優先される
    IF status IS NULL THEN
      DBMS_OUTPUT.PUT_LINE('status is NULL');  -- これが出力される
    END IF;
  END;
END;
対策:プレフィックスで衝突回避
-- ⭕ プレフィックスで回避
DECLARE
  v_outer_status VARCHAR2(20) := 'active';
BEGIN
  DECLARE
    v_inner_status VARCHAR2(20);
  BEGIN
    -- 名前が違うので混乱しない
    v_inner_status := v_outer_status;
  END;
END;

列名とのShadowing事故:変数名がテーブル列名と同じだと、SQL内で変数が優先されるという罠があります。UPDATE users SET name = nameのように書くと、ローカル変数nameを代入しているのか列同士の代入なのか曖昧。変数名にv_プレフィックスを付ければ、列名との衝突が物理的に起きません。

例外の伝播:どこで捕まえるか

伝播の基本ルール
BEGIN
  &lt;&lt;outer&gt;&gt;
  BEGIN
    &lt;&lt;inner&gt;&gt;
    BEGIN
      RAISE NO_DATA_FOUND;
    EXCEPTION
      WHEN TOO_MANY_ROWS THEN       -- NO_DATA_FOUND はマッチしない
        DBMS_OUTPUT.PUT_LINE('内側: TOO_MANY_ROWS');
    END inner;

    -- ここには到達しない(内側で未処理なので外側EXCEPTIONへ)
    DBMS_OUTPUT.PUT_LINE('到達せず');
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      DBMS_OUTPUT.PUT_LINE('外側でキャッチ: NO_DATA_FOUND');
  END outer;
END;
/
-- 出力:外側でキャッチ: NO_DATA_FOUND

伝播の5つのルール

厳密な伝播ルール

  • 例外発生時、同ブロックのEXCEPTIONで一致するWHENを探す
  • マッチするWHENが見つかればそこで処理→ブロックは正常終了として外側へ戻る
  • マッチしない場合は親ブロックのEXCEPTIONへ伝播(内側のBEGIN以降の文は実行されない)
  • 親でもマッチせねばさらに親へ(最終的にクライアントにエラーが返る)
  • EXCEPTION節内で新たな例外発生→その例外はさらに外側へ伝播(同ブロックで再キャッチしない)

例外処理のネストで部分的にキャッチ

「try-catch風」のネスト例
BEGIN
  -- 主要な処理
  process_main();

  -- 補助処理は失敗しても続行したい
  BEGIN
    send_notification();   -- ここが失敗しても
  EXCEPTION
    WHEN OTHERS THEN
      log_error(SQLCODE, SQLERRM);   -- ログだけ残して
      NULL;                -- 処理は続ける
  END;

  -- 補助処理失敗でも後続は実行される
  finalize_main();
EXCEPTION
  WHEN OTHERS THEN
    log_error(SQLCODE, SQLERRM);
    RAISE;
END;

ネスト例外の最大の価値:「致命的な主処理」と「補助的な処理」を分離できる点。Javaのtry-catchと同じ発想で、許容可能なエラーは内側ブロックで吸収、致命的なエラーは外側まで伝播、という設計が書けます。

局所ユーザー定義例外のスコープ

局所例外はブロック内でのみ有効
BEGIN
  &lt;&lt;outer&gt;&gt;
  DECLARE
    e_outer EXCEPTION;
  BEGIN
    &lt;&lt;inner&gt;&gt;
    DECLARE
      e_inner EXCEPTION;
    BEGIN
      RAISE e_inner;
    EXCEPTION
      WHEN e_inner THEN
        DBMS_OUTPUT.PUT_LINE('inner caught');
      -- e_outer はここでは見える(外側で宣言されている)
      WHEN e_outer THEN
        DBMS_OUTPUT.PUT_LINE('outer exception(ここでは発生しない)');
    END inner;
  EXCEPTION
    -- e_inner はここでは参照不可(内側の宣言)
    WHEN e_outer THEN
      DBMS_OUTPUT.PUT_LINE('outer caught');
  END outer;
END;
PRAGMA EXCEPTION_INIT の範囲
DECLARE
  e_locked EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_locked, -54);   -- ORA-00054 を名前付け
BEGIN
  UPDATE orders SET status = 'processing' WHERE id = 1 FOR UPDATE NOWAIT;
EXCEPTION
  WHEN e_locked THEN
    DBMS_OUTPUT.PUT_LINE('他が更新中です');
END;

例外名の再利用

外側ブロックでe_custom EXCEPTION;を宣言しておけば、内側ブロック複数からRAISEしても外側で一括キャッチできます。「共通例外」は外側で宣言、「そのブロック固有の例外」は内側で宣言、という使い分け。詳細は【PL/SQL】例外処理完全ガイド参照。

ローカルサブプログラム(ブロック内プロシージャ/ファンクション)

DECLARE内で定義する
DECLARE
  -- ローカル変数
  v_total NUMBER := 0;

  -- ローカルファンクション
  FUNCTION add_tax(p_amount IN NUMBER) RETURN NUMBER IS
  BEGIN
    RETURN ROUND(p_amount * 1.10);
  END;

  -- ローカルプロシージャ
  PROCEDURE log(p_msg IN VARCHAR2) IS
  BEGIN
    DBMS_OUTPUT.PUT_LINE(TO_CHAR(SYSDATE, 'HH24:MI:SS') || ' ' || p_msg);
  END;
BEGIN
  FOR rec IN (SELECT amount FROM orders WHERE date = TRUNC(SYSDATE)) LOOP
    v_total := v_total + add_tax(rec.amount);
  END LOOP;
  log('合計: ' || v_total);
END;
呼び出し位置の制約(前方参照不可)
DECLARE
  -- ❌ 下で定義される関数を上で使うとPLS-00201
  -- v_x NUMBER := add_one(10);   -- NG

  FUNCTION add_one(p IN NUMBER) RETURN NUMBER IS
  BEGIN
    RETURN p + 1;
  END;

  -- ここでは使える
  v_x NUMBER := add_one(10);
BEGIN
  NULL;
END;

-- 相互再帰が必要ならforward declarationを使う
DECLARE
  FUNCTION is_even(n IN NUMBER) RETURN BOOLEAN;   -- 前方宣言

  FUNCTION is_odd(n IN NUMBER) RETURN BOOLEAN IS
  BEGIN
    IF n = 0 THEN RETURN FALSE; END IF;
    RETURN is_even(n - 1);   -- is_evenを呼ぶ
  END;

  FUNCTION is_even(n IN NUMBER) RETURN BOOLEAN IS
  BEGIN
    IF n = 0 THEN RETURN TRUE; END IF;
    RETURN is_odd(n - 1);
  END;
BEGIN
  IF is_even(10) THEN DBMS_OUTPUT.PUT_LINE('even'); END IF;
END;

ローカルサブプログラム vs パッケージ

  • その匿名ブロックでしか使わない→ローカルサブプログラム
  • 同一SQL文内限定→WITH FUNCTION句(12c+)
  • 複数箇所で再利用→パッケージ化詳解
  • スタンドアロンな独立性が必要→単体プロシージャ/ファンクション詳解

ネストしたCursorのスコープ

内側でCursor宣言
DECLARE
  CURSOR cur_dept IS SELECT id, name FROM departments;
BEGIN
  FOR rec_d IN cur_dept LOOP
    -- 内側で子カーソルを宣言(部門ごと)
    DECLARE
      CURSOR cur_emp IS
        SELECT id, name FROM employees WHERE dept_id = rec_d.id;
    BEGIN
      FOR rec_e IN cur_emp LOOP
        DBMS_OUTPUT.PUT_LINE(rec_d.name || ' - ' || rec_e.name);
      END LOOP;
    END;
  END LOOP;
END;
パラメータ付きカーソルで外側変数参照
DECLARE
  CURSOR cur_dept IS SELECT id, name FROM departments;
  CURSOR cur_emp(p_dept_id NUMBER) IS
    SELECT id, name FROM employees WHERE dept_id = p_dept_id;
BEGIN
  FOR rec_d IN cur_dept LOOP
    FOR rec_e IN cur_emp(rec_d.id) LOOP
      DBMS_OUTPUT.PUT_LINE(rec_d.name || ' - ' || rec_e.name);
    END LOOP;
  END LOOP;
END;

パラメータ付きカーソルのメリット

パラメータ付きカーソルを外側で宣言しておくと、内側のブロックを追加せずに済み、ネスト階層が浅くなります。また複数の場所で同じクエリ形を使い回せるため、DRY原則にも合います。詳細は【PL/SQL】カーソル完全ガイド参照。

SAVEPOINT+ネストブロックで部分ロールバック

SAVEPOINTで途中地点保存
BEGIN
  INSERT INTO logs(msg) VALUES ('開始');

  SAVEPOINT sp_before_critical;

  BEGIN
    -- 危険な処理
    UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
    process_risky_operation();
  EXCEPTION
    WHEN OTHERS THEN
      ROLLBACK TO sp_before_critical;   -- 1行目のINSERTは残る、UPDATEはロールバック
      log_error(SQLCODE, SQLERRM);
  END;

  INSERT INTO logs(msg) VALUES ('完了');
  COMMIT;
END;
バッチ処理で部分エラー許容
DECLARE
  v_success NUMBER := 0;
  v_error   NUMBER := 0;
BEGIN
  FOR rec IN (SELECT id, payload FROM batch_queue) LOOP
    SAVEPOINT sp_row;
    BEGIN
      process_item(rec.id, rec.payload);
      v_success := v_success + 1;
    EXCEPTION
      WHEN OTHERS THEN
        ROLLBACK TO sp_row;
        v_error := v_error + 1;
        log_error(SQLCODE, SQLERRM, rec.id);
    END;
  END LOOP;
  COMMIT;
  DBMS_OUTPUT.PUT_LINE('OK: ' || v_success || ' / NG: ' || v_error);
END;

SAVEPOINTの詳細は

SAVEPOINTを使った部分ロールバックの全パターンは【PL/SQL】SAVEPOINTを使った部分ロールバックの実装方法で詳解。命名規則、ネスト制限、トリガーとの組み合わせなど実務的な注意点を扱っています。

PRAGMA AUTONOMOUS_TRANSACTIONで独立トランザクション

-- ネストしたブロックを自律トランザクション化する定番パターン
DECLARE
  PRAGMA AUTONOMOUS_TRANSACTION;   -- 宣言セクション先頭に書く(ブロック全体が独立)
BEGIN
  INSERT INTO audit_log(ts, msg) VALUES (SYSTIMESTAMP, 'checkpoint');
  COMMIT;   -- 呼び出し元のトランザクションとは独立にCOMMIT
END;
監査ログ用ローカルプロシージャ
DECLARE
  -- 自律トランザクション付きローカルプロシージャ
  PROCEDURE log_audit(p_msg IN VARCHAR2) IS
    PRAGMA AUTONOMOUS_TRANSACTION;
  BEGIN
    INSERT INTO audit_log(ts, msg) VALUES (SYSTIMESTAMP, p_msg);
    COMMIT;   -- メインがROLLBACKしてもこの記録は残る
  END;
BEGIN
  log_audit('処理開始');
  INSERT INTO orders(...) VALUES (...);
  log_audit('INSERT完了');
  -- 何か失敗してROLLBACKしてもログは残っている
  RAISE_APPLICATION_ERROR(-20001, 'テスト');
EXCEPTION
  WHEN OTHERS THEN
    log_audit('エラー発生: ' || SQLERRM);
    ROLLBACK;
END;

AUTONOMOUS_TRANSACTIONの重要ルール:①自律トランザクション内で必ずCOMMITまたはROLLBACKする(そうしないとORA-06519)、②独立したundo領域・ロックを持つのでデッドロック注意、③メインのSELECTでロックしている行に自律側でUPDATEすると自己デッドロック。詳細は【PL/SQL】AUTONOMOUS TRANSACTIONで独立した処理を行う方法

実務5パターン

①try-catch風:補助処理の失敗を吸収

BEGIN
  -- 本処理
  update_main_data();

  -- 通知はベストエフォート
  BEGIN
    send_email_notification();
  EXCEPTION
    WHEN OTHERS THEN
      log_error(SQLCODE, SQLERRM);
      -- 通知失敗でも本処理は完了扱い
  END;

  -- 統計更新もベストエフォート
  BEGIN
    update_stats();
  EXCEPTION
    WHEN OTHERS THEN
      log_error(SQLCODE, SQLERRM);
  END;

  COMMIT;
END;

②リトライ with 内側ブロック

DECLARE
  v_retry   NUMBER := 0;
  v_success BOOLEAN := FALSE;
BEGIN
  WHILE v_retry &lt; 3 AND NOT v_success LOOP
    BEGIN
      call_unstable_api();
      v_success := TRUE;
    EXCEPTION
      WHEN OTHERS THEN
        v_retry := v_retry + 1;
        DBMS_LOCK.SLEEP(2 ** v_retry);   -- 2,4,8秒
    END;
  END LOOP;

  IF NOT v_success THEN
    RAISE_APPLICATION_ERROR(-20001, 'リトライ上限');
  END IF;
END;

③部分ロールバック(SAVEPOINT+ネスト)

BEGIN
  FOR rec IN (SELECT id FROM batch_queue) LOOP
    SAVEPOINT sp_item;
    BEGIN
      process(rec.id);
    EXCEPTION
      WHEN OTHERS THEN
        ROLLBACK TO sp_item;
        log_error(SQLCODE, SQLERRM, rec.id);
    END;
  END LOOP;
  COMMIT;
END;

④局所ヘルパー関数でループ処理

DECLARE
  FUNCTION format_money(p_amt IN NUMBER) RETURN VARCHAR2 IS
  BEGIN
    RETURN '¥' || TO_CHAR(p_amt, 'FM999,999,999');
  END;

  FUNCTION calc_tax(p_amt IN NUMBER) RETURN NUMBER IS
  BEGIN
    RETURN ROUND(p_amt * 0.10);
  END;
BEGIN
  FOR rec IN (SELECT id, amount FROM orders) LOOP
    DBMS_OUTPUT.PUT_LINE(
      rec.id || ': ' || format_money(rec.amount) ||
      ' (tax=' || format_money(calc_tax(rec.amount)) || ')'
    );
  END LOOP;
END;

⑤例外の翻訳(下位例外→上位ビジネス例外)

DECLARE
  e_duplicate_email EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_duplicate_email, -1);   -- ORA-00001
BEGIN
  BEGIN
    INSERT INTO users(email) VALUES (p_email);
  EXCEPTION
    WHEN e_duplicate_email THEN
      RAISE_APPLICATION_ERROR(
        -20001,
        'このメールアドレスは既に登録されています: ' || p_email
      );
  END;

  send_welcome_email(p_email);
END;

アンチパターン7選

①深すぎるネスト(4階層以上)。可読性と保守性が著しく落ちる。早期returnやサブプログラム抽出でフラット化する。実務では2〜3階層までが限界。

②Shadowingを自覚せず同名変数。外側と内側で同じ名前を使うとバグの温床。v_outer_v_inner_のようにプレフィックスで意図的に区別する。

③内側ブロックでWHEN OTHERS THEN NULL;。Smothering Exception。親のビジネスロジックが壊れても気づけない。必ずRAISEまたはlog_errorRAISE

④内側の例外を新しい例外にすり替えWHEN OTHERS THEN RAISE e_my_error;でBACKTRACEが失われる。独自例外を投げるならRAISE_APPLICATION_ERROR+第3引数TRUEでスタック保持。

⑤AUTONOMOUS_TRANSACTION内でCOMMIT忘れORA-06519: active autonomous transaction detected。自律ブロック内は必ずCOMMITまたはROLLBACKで閉じる。

⑥SAVEPOINT名の衝突。ネストしたブロックで同名SAVEPOINTを使うと古い方が無効化される。sp_row_<処理名>のように区別する命名を。

⑦ローカルサブプログラムで同名関数乱立。内側ブロックで同名ローカル関数を再定義すると、外側の関数が隠蔽され意図しない関数が呼ばれる可能性。ブロック内関数はそのブロック固有の処理に限定。

よくある質問

Qネストの深さに制限はある?
A言語仕様上の制限は極めて大きい(255階層以上可能)ですが、実務では2〜3階層が限界。それ以上深くなったらサブプログラム化/早期returnでフラット化する。可読性を重視するなら最大3階層ルールをチーム規約に。
Q外側と同じ名前の変数を内側で宣言できる?
Aできます(Shadowing)が、推奨されません。内側で再宣言すると内側が優先され、外側はラベル経由(ラベル.変数)でしかアクセスできなくなります。プレフィックスv_outer_v_inner_)で意図的に区別するのがベスト。
Q内側のブロックで宣言した変数は外側で見える?
A見えません。内側の変数は内側のBEGIN〜END間でのみ有効です。値を外側に渡したい場合は①外側変数に代入する、②RETURNするファンクション化、③OUTパラメータ付きローカルプロシージャ、のいずれか。
Q内側で例外が発生したら外側の後続処理は実行される?
A実行されません。内側で例外処理されない(マッチするWHENが無い)と即座に外側のEXCEPTION句へ飛びます。内側BEGINの続きも、内側と外側の間の文も実行されずスキップ。内側でRAISEして外側キャッチしても同じ動作です。
Q内側ブロックでCOMMITしたら外側にも影響する?
A影響します。PL/SQLの通常ブロックは1つのトランザクションに属するため、内側のCOMMITは外側の変更も全てコミットします。独立させたいならPRAGMA AUTONOMOUS_TRANSACTIONを内側ブロックに付けます。
Qラベル名は何文字まで?
APL/SQL識別子と同じく30バイト(12cR1以前)/128バイト(12cR2+)。英数字+アンダースコアで先頭は英字。実務では処理の意味を表す短い名前outer_loopprocess_dept)が保守しやすい。予約語との衝突は避けられます。
Qラベル名と変数名は衝突する?
Aネームスペースが異なるため衝突しません。<<main>> DECLARE v_main NUMBER;は問題なく動作。ただし読み手が混乱するので、ラベル名と同じ名前の変数は避けるのが実務的。
Qローカルサブプログラムとパッケージ化、どちらを使うべき?
Aそのブロック限定の処理→ローカルサブプログラム、複数箇所で再利用→パッケージ化が原則。ローカル関数は匿名ブロック内で完結し依存が外に漏れないメリットがあります。詳細は【PL/SQL】プロシージャ・ファンクション完全ガイド
QSAVEPOINT名が内外で同じだとどうなる?
A新しい方が古い方を上書きします(Oracle 10g以降)。同名SAVEPOINTは「最新のSAVEPOINTマーカー位置」として扱われるため、ネストしたSAVEPOINTは名前で区別するのが鉄則。例:sp_outer_xxxsp_inner_yyyのようにプレフィックス。
Q無名ブロック内のEXCEPTION節でさらに例外が発生したら?
A新しい例外は外側のEXCEPTION節へ伝播します(現ブロックではもう捕まえられない)。EXCEPTION節内で安全に書くには、個別にネストブロック化する:EXCEPTION WHEN OTHERS THEN BEGIN log(SQLERRM); EXCEPTION WHEN OTHERS THEN NULL; END; RAISE;でログ失敗を吸収しつつ元例外を伝える。

関連記事

まとめ

  • PL/SQLはDECLARE/BEGIN/EXCEPTION/END;をネストできる
  • 基本可視性ルール:内側から外側は見える/外側から内側は見えない
  • 同名変数はShadowingで内側が優先、外側はラベル.変数で参照
  • 例外は同ブロックのWHENを順に探し、マッチしなければ親へ伝播
  • 局所例外/ユーザー定義例外は宣言ブロック内でのみ有効
  • ローカルサブプログラムでブロック限定のヘルパー関数を定義可能
  • Cursorのネストはパラメータ付きカーソルで階層を浅くできる
  • SAVEPOINT+ネストブロックで部分ロールバック(バッチの部分成功)
  • PRAGMA AUTONOMOUS_TRANSACTIONで独立トランザクション(監査ログ等)
  • 実務5パターン:try-catch風/リトライ/部分ロールバック/局所関数/例外翻訳
  • アンチパターン:深すぎるネスト/Shadowing無自覚/WHEN OTHERS握り潰し/RAISEで情報喪失/COMMIT忘れ/SAVEPOINT名衝突/同名局所関数

ネストブロックは変数・例外・トランザクションのスコープを局所化するPL/SQLの強力な設計ツールです。本記事のスコープルール・ラベル活用・例外伝播・SAVEPOINT+ネストパターンを押さえれば、保守性と堅牢性の高いPL/SQLコードが書けます。基本構文は基本構文完全ガイド、例外は例外処理完全ガイド、変数は変数・定数完全ガイド、SAVEPOINTはSAVEPOINT詳解、AUTONOMOUSはAUTONOMOUS TRANSACTIONと組み合わせて活用してください。