【Oracle】DBMS_UTILITY完全ガイド|FORMAT_ERROR_BACKTRACE・COMPILE_SCHEMA・GET_TIME など実用関数を徹底解説

Oracle の DBMS_UTILITY パッケージには、PL/SQL 開発・運用で日常的に使う汎用ユーティリティ関数が多数収録されています。エラートレース・コールスタックの取得から、スキーマの一括再コンパイル、処理時間の計測、オブジェクト名の解決まで、幅広い用途をカバーします。

特に FORMAT_ERROR_BACKTRACEFORMAT_CALL_STACK は例外ハンドリングの品質を大幅に向上させる重要な関数です。例外がどの行で発生したかを正確に特定できるため、デバッグ効率が格段に上がります。

この記事でわかること

  • FORMAT_ERROR_BACKTRACE でエラー発生箇所を特定する方法(FORMAT_ERROR_STACK との違い)
  • FORMAT_CALL_STACK で現在の呼び出しスタックを取得する方法
  • COMPILE_SCHEMA でスキーマ内の無効オブジェクトを一括再コンパイルする方法
  • NAME_RESOLVE で PL/SQL オブジェクト名を解決する方法
  • GET_TIME で処理時間を計測する方法と注意点
  • COMMA_TO_TABLE / TABLE_TO_COMMA で文字列リストを変換する方法
  • EXPAND_SQL_TEXT でビュー定義を展開する方法
スポンサーリンク

FORMAT_ERROR_BACKTRACE と FORMAT_ERROR_STACK の違い

この 2 つは混同されやすいですが、役割が異なります。

関数 返す内容 主な用途
FORMAT_ERROR_STACK 現在の例外スタックのエラーコード+メッセージ(ORA-xxxxx) エラーの種類・メッセージを記録する
FORMAT_ERROR_BACKTRACE 例外が最初に発生した行番号とプログラム名 エラーの発生箇所(ファイル・行)を特定する
FORMAT_CALL_STACK 現在のコールスタック(どこから呼ばれたか) 処理の呼び出し経路を記録する
FORMAT_ERROR_BACKTRACE の注意点
FORMAT_ERROR_BACKTRACE は例外ハンドラ(EXCEPTION 節)内で呼ぶ必要があります。例外を RAISE で再送出すると、バックトレースは RAISE を書いた行に更新されます。RAISE;(引数なし)で再送出する場合も同様に行番号が書き換わるため、例外ハンドラ内の最初でバックトレースを記録してから再送出するのがベストプラクティスです。
FORMAT_ERROR_BACKTRACE と FORMAT_ERROR_STACK の使い方
-- エラーログテーブルへの記録パターン(実務でよく使う構成)
CREATE OR REPLACE PROCEDURE log_and_raise(p_proc_name VARCHAR2) AS
BEGIN
    -- エラーログテーブルに記録する(AUTONOMOUS_TRANSACTION で分離)
    INSERT INTO error_log(log_date, proc_name, error_stack, error_backtrace)
    VALUES(
        SYSTIMESTAMP,
        p_proc_name,
        DBMS_UTILITY.FORMAT_ERROR_STACK,       -- ORA-xxxxx: メッセージ
        DBMS_UTILITY.FORMAT_ERROR_BACKTRACE    -- エラーが発生した行番号・ファイル
    );
    -- RAISE で再送出すると BACKTRACE がこの行に更新されるため先にログを書く
    RAISE;
END log_and_raise;
/

-- FORMAT_ERROR_BACKTRACE の出力例:
-- ORA-06512: at "HR.PROCESS_ORDER", line 45
-- ORA-06512: at "HR.BATCH_PROCESS", line 12
-- 上記から: HR.PROCESS_ORDER の 45 行目で例外が発生したとわかる

-- FORMAT_ERROR_STACK の出力例:
-- ORA-01400: cannot insert NULL into ("HR"."ORDERS"."ORDER_ID")
-- ORA-06512: at "HR.PROCESS_ORDER", line 45

-- DBMS_UTILITY.FORMAT_CALL_STACK の使い方(例外ハンドラ外でも使える)
DECLARE
    v_stack VARCHAR2(2000);
BEGIN
    -- 現在の呼び出しスタックを取得する
    v_stack := DBMS_UTILITY.FORMAT_CALL_STACK;
    DBMS_OUTPUT.PUT_LINE(v_stack);
    -- 出力例:
    -- ----- PL/SQL Call Stack -----
    --   object      line  object
    --   handle    number  name
    -- 0x3a1234bc       5  anonymous block
END;
/

COMPILE_SCHEMA でスキーマ内の無効オブジェクトを一括再コンパイルする

パッケージ変更後や Oracle バージョンアップ後に、スキーマ内の多くのオブジェクトがINVALID 状態になることがあります。DBMS_UTILITY.COMPILE_SCHEMA を使うと、スキーマ内の全オブジェクトを一括で再コンパイルできます。

COMPILE_SCHEMA でスキーマを一括再コンパイルする
-- スキーマ内の無効オブジェクトを確認する
SELECT object_type, COUNT(*) AS cnt
FROM DBA_OBJECTS
WHERE owner = 'HR' AND status = 'INVALID'
GROUP BY object_type
ORDER BY object_type;

-- COMPILE_SCHEMA を実行する(SYS または DBA 権限が必要)
BEGIN
    DBMS_UTILITY.COMPILE_SCHEMA(
        schema   => 'HR',      -- 対象スキーマ(大文字)
        compile_all => FALSE    -- FALSE: INVALID なオブジェクトのみ / TRUE: 全オブジェクト
    );
END;
/

-- Oracle 10g 以降は第3引数で再コンパイルの順序を制御できる
BEGIN
    DBMS_UTILITY.COMPILE_SCHEMA(
        schema      => 'HR',
        compile_all => FALSE,
        reuse_settings => TRUE   -- TRUE: オブジェクトのコンパイル設定(PLSQL_CODE_TYPE等)を引き継ぐ
    );
END;
/

-- 再コンパイル後に INVALID が残っていないか確認する
SELECT object_name, object_type, last_ddl_time
FROM DBA_OBJECTS
WHERE owner = 'HR' AND status = 'INVALID'
ORDER BY object_type, object_name;
-- INVALID が残る場合は依存関係エラー(他オブジェクトの列名変更など)が原因
UTL_RECOMP による並列再コンパイル
大規模なスキーマで再コンパイルを高速化したい場合は UTL_RECOMP パッケージが有効です。UTL_RECOMP.RECOMP_PARALLEL(n) で n 並列で再コンパイルを実行できます。COMPILE_SCHEMA はシングルスレッドで動作するため、オブジェクト数が多い本番環境では UTL_RECOMP の方が速い場合があります。

NAME_RESOLVE でオブジェクト名を解決する

DBMS_UTILITY.NAME_RESOLVE は、PL/SQL のオブジェクト名(プロシージャ・ファンクション・パッケージなど)を解析して、所有者・オブジェクト名・オブジェクトID・サブプログラム名などを返します。動的 SQL でオブジェクト名を扱う際の妥当性チェックや依存関係確認に使います。

NAME_RESOLVE でオブジェクト名を解析する
DECLARE
    v_schema    VARCHAR2(30);
    v_part1     VARCHAR2(30);   -- パッケージ名またはオブジェクト名
    v_part2     VARCHAR2(30);   -- パッケージ内のプロシージャ名
    v_dblink    VARCHAR2(30);   -- データベースリンク名
    v_part1_type NUMBER;        -- オブジェクト種別
    v_object_id  NUMBER;        -- オブジェクトID
BEGIN
    -- プロシージャ名を解決する
    DBMS_UTILITY.NAME_RESOLVE(
        name          => 'HR.PROCESS_ORDER',   -- 解決するオブジェクト名
        context       => 1,                      -- 1: PL/SQL コンテキスト
        schema        => v_schema,
        part1         => v_part1,
        part2         => v_part2,
        dblink        => v_dblink,
        part1_type    => v_part1_type,
        object_number => v_object_id
    );

    DBMS_OUTPUT.PUT_LINE('スキーマ: ' || v_schema);
    DBMS_OUTPUT.PUT_LINE('オブジェクト: ' || v_part1);
    DBMS_OUTPUT.PUT_LINE('サブプログラム: ' || NVL(v_part2, '(なし)'));
    DBMS_OUTPUT.PUT_LINE('オブジェクトID: ' || v_object_id);
    -- part1_type の値: 7=プロシージャ, 8=ファンクション, 9=パッケージ, 12=トリガー など

EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('解決失敗: ' || SQLERRM);
END;
/

-- パッケージ内プロシージャを解決する例
-- NAME_RESOLVE('HR.BATCH_PKG.PROCESS_MONTHLY', 1, v_schema, v_part1, v_part2, ...);
-- → v_part1 = 'BATCH_PKG', v_part2 = 'PROCESS_MONTHLY'

GET_TIME で処理時間を計測する

DBMS_UTILITY.GET_TIME は 100 分の 1 秒単位の経過時間を返します。2 回の呼び出しの差を計算することで処理時間(センチ秒)を取得できます。簡易的なパフォーマンス計測に使います。

GET_TIME で処理時間を計測する
DECLARE
    v_start  PLS_INTEGER;
    v_end    PLS_INTEGER;
    v_elapsed_cs PLS_INTEGER;   -- センチ秒 (1/100秒)
BEGIN
    -- 開始時刻を記録する
    v_start := DBMS_UTILITY.GET_TIME;

    -- 計測したい処理を実行する
    FOR i IN 1..100000 LOOP
        NULL;
    END LOOP;

    -- 終了時刻を記録する
    v_end := DBMS_UTILITY.GET_TIME;

    -- 経過センチ秒を計算する
    -- GET_TIME の戻り値は BINARY_INTEGER で符号なし整数のオーバーフローに注意
    v_elapsed_cs := v_end - v_start;

    DBMS_OUTPUT.PUT_LINE(
        '経過時間: ' || v_elapsed_cs || ' センチ秒 (' ||
        ROUND(v_elapsed_cs / 100, 2) || ' 秒)'
    );
END;
/
GET_TIME の注意点とオーバーフロー対策
GET_TIME の戻り値は BINARY_INTEGER(符号付き 32 ビット整数)で、約 248 日(約 2,147,483,647 センチ秒)でオーバーフローしてリセットされます。長時間処理では SYSTIMESTAMP の差分計算の方が安全です。より詳細なプロファイリングが必要な場合は PL/SQL プロファイラ(DBMS_HPROF)を使ってください。

COMMA_TO_TABLE と TABLE_TO_COMMA で文字列リストを変換する

カンマ区切りの文字列を PL/SQL テーブルに変換したり、逆に PL/SQL テーブルをカンマ区切り文字列に変換したりするユーティリティです。簡単な CSV パースや文字列結合に使えます。

COMMA_TO_TABLE と TABLE_TO_COMMA の使い方
DECLARE
    v_list   VARCHAR2(500) := 'EMPLOYEES,DEPARTMENTS,ORDERS,CUSTOMERS';
    v_tablen BINARY_INTEGER;
    v_tab    DBMS_UTILITY.UNCL_ARRAY;   -- UNCL_ARRAY: 文字列配列型
    v_result VARCHAR2(500);
BEGIN
    -- カンマ区切り文字列 → PL/SQL テーブル
    DBMS_UTILITY.COMMA_TO_TABLE(
        list   => v_list,
        tablen => v_tablen,   -- 出力: 要素数
        tab    => v_tab       -- 出力: 要素の配列
    );

    DBMS_OUTPUT.PUT_LINE('要素数: ' || v_tablen);
    FOR i IN 1..v_tablen LOOP
        DBMS_OUTPUT.PUT_LINE(i || ': ' || v_tab(i));
    END LOOP;
    -- 出力:
    -- 要素数: 4
    -- 1: EMPLOYEES
    -- 2: DEPARTMENTS
    -- 3: ORDERS
    -- 4: CUSTOMERS

    -- PL/SQL テーブル → カンマ区切り文字列
    DBMS_UTILITY.TABLE_TO_COMMA(
        tab    => v_tab,
        tablen => v_tablen,
        list   => v_result    -- 出力: カンマ区切り文字列
    );
    DBMS_OUTPUT.PUT_LINE('結果: ' || v_result);
    -- 出力: 結果: EMPLOYEES,DEPARTMENTS,ORDERS,CUSTOMERS
END;
/
COMMA_TO_TABLE の制限
COMMA_TO_TABLE は Oracle の識別子ルール(英数字・アンダースコア・$・#)に準拠した要素のみ扱えます。スペースを含む文字列・日本語・特殊文字はエラーになります。また区切り文字はカンマ固定です。汎用的な CSV パースには正規表現(REGEXP_SUBSTR)や独自の分割関数を実装してください。

EXPAND_SQL_TEXT でビュー定義を展開する

DBMS_UTILITY.EXPAND_SQL_TEXT(Oracle 12c 以降)は、ビューや WITH 句(CTE)を含む SQL テキストを展開し、実際にアクセスするベーステーブルへのフル SQL テキストを返します。クエリの動作確認・最適化ヒントの調整に有用です。

EXPAND_SQL_TEXT でビューを展開する
-- Oracle 12c 以降で使用可能
DECLARE
    v_input  CLOB := 'SELECT * FROM emp_details_view WHERE department_id = 50';
    v_output CLOB;
BEGIN
    DBMS_UTILITY.EXPAND_SQL_TEXT(
        input_sql_text  => v_input,
        output_sql_text => v_output
    );
    -- ビューの定義が展開されたフル SQL が返される
    DBMS_OUTPUT.PUT_LINE(DBMS_LOB.SUBSTR(v_output, 4000, 1));
END;
/

-- WITH 句(CTE)も展開される
DECLARE
    v_input  CLOB := 'WITH dept_avg AS (SELECT department_id, AVG(salary) avg_sal FROM employees GROUP BY department_id) SELECT * FROM dept_avg WHERE avg_sal > 10000';
    v_output CLOB;
BEGIN
    DBMS_UTILITY.EXPAND_SQL_TEXT(v_input, v_output);
    DBMS_OUTPUT.PUT_LINE(DBMS_LOB.SUBSTR(v_output, 4000, 1));
END;
/

その他の便利な DBMS_UTILITY 関数

IS_CLUSTER_DATABASE / ACTIVE_INSTANCES / GET_DEPENDENCY
-- RAC 環境かどうかを確認する
DECLARE
    v_is_rac BOOLEAN;
BEGIN
    v_is_rac := DBMS_UTILITY.IS_CLUSTER_DATABASE;
    IF v_is_rac THEN
        DBMS_OUTPUT.PUT_LINE('RAC 環境です');
    ELSE
        DBMS_OUTPUT.PUT_LINE('シングルインスタンスです');
    END IF;
END;
/

-- オブジェクトの依存関係を確認する
-- GET_DEPENDENCY は DBA_DEPENDENCIES ビューに似た情報を返す
BEGIN
    DBMS_UTILITY.GET_DEPENDENCY(
        type   => 'PACKAGE',     -- オブジェクト種別
        schema => 'HR',          -- スキーマ
        name   => 'BATCH_PKG'   -- オブジェクト名
    );
    -- 結果は DBMS_OUTPUT に出力される
    -- 依存しているオブジェクト一覧が表示される
END;
/

-- DB_VERSION でバージョン情報を取得する
DECLARE
    v_version   VARCHAR2(100);
    v_compatible VARCHAR2(100);
BEGIN
    DBMS_UTILITY.DB_VERSION(v_version, v_compatible);
    DBMS_OUTPUT.PUT_LINE('バージョン: ' || v_version);
    DBMS_OUTPUT.PUT_LINE('互換性: ' || v_compatible);
    -- 出力例:
    -- バージョン: 19.0.0.0.0
    -- 互換性: 19.0.0
END;
/

まとめ

  • FORMAT_ERROR_BACKTRACE:例外ハンドラ内で呼ぶ。エラーが発生した行番号・プログラム名を返す。RAISE で再送出する前に記録する
  • FORMAT_ERROR_STACK:ORA-xxxxx エラーコードとメッセージを返す。FORMAT_ERROR_BACKTRACE と組み合わせてエラーログに記録するのが実務の定番
  • FORMAT_CALL_STACK:例外ハンドラ外でも使える。現在の呼び出し経路を返す
  • COMPILE_SCHEMA:スキーマ内の INVALID オブジェクトを一括再コンパイルする。compile_all=FALSE で INVALID のみ対象
  • NAME_RESOLVE:PL/SQL オブジェクト名を解析してスキーマ・オブジェクトID・サブプログラム名を返す
  • GET_TIME:センチ秒単位の簡易タイマー。長時間処理のオーバーフローに注意
  • COMMA_TO_TABLE:カンマ区切り文字列を配列に変換する。識別子ルールに準拠した文字列のみ対応
  • EXPAND_SQL_TEXT:Oracle 12c 以降でビュー・WITH 句を展開してベーステーブルへの SQL を確認できる

FORMAT_ERROR_BACKTRACE を活用した例外ハンドリングの詳細は 例外ハンドリング完全ガイドを参照してください。PRAGMA AUTONOMOUS_TRANSACTION と組み合わせたエラーログ記録パターンは AUTONOMOUS_TRANSACTION完全ガイドも参照してください。