【PL/SQL】DBMS_METADATA完全解説|DDL取得・SET_TRANSFORM_PARAM・スキーマ一括出力・エラー対処

【PL/SQL】DBMS_METADATA完全解説|DDL取得・SET_TRANSFORM_PARAM・スキーマ一括出力・エラー対処 PL/SQL

Oracle でテーブルやビュー、インデックス、シーケンスなどのオブジェクト定義(DDL)を SQL だけで取得したい場面は多くあります。移行スクリプト作成・バージョン管理・環境差分確認など、現場で頻繁に使われる DBMS_METADATA パッケージを、基本から実務パターンまで体系的に解説します。

この記事で解決できること

  • DBMS_METADATA.GET_DDL でテーブル・ビュー・インデックス等の DDL を取得する方法
  • SET_TRANSFORM_PARAM で STORAGE 句や TABLESPACE 句を除去してシンプルな DDL を得る方法
  • GET_DEPENDENT_DDL でテーブルに紐付く制約・インデックスをまとめて取得する方法
  • OPEN/FETCH_DDL/CLOSE でスキーマ全体の DDL を一括出力する方法
  • ORA-31600・ORA-39213 などのよくあるエラーの対処法
スポンサーリンク

DBMS_METADATA とは

DBMS_METADATA は Oracle が標準で提供するパッケージで、データベース内のオブジェクト定義(DDL)をプログラムから取得できます。SQL*Plus や SQL Developer の「DDL 表示」機能もこのパッケージを内部で使用しています。

主なサブプログラム 概要
GET_DDL 指定した 1 つのオブジェクトの DDL を CLOB で返す
GET_DEPENDENT_DDL 指定オブジェクトに依存するオブジェクトの DDL を返す(制約・インデックス等)
GET_GRANTED_DDL GRANT 文を取得する
OPEN / FETCH_DDL / CLOSE 複数オブジェクトをループで取得(スキーマ一括出力に使用)
SET_TRANSFORM_PARAM DDL 出力フォーマットを制御する(STORAGE 句除去など)

GET_DDL の基本構文

DBMS_METADATA.GET_DDL(
  object_type  IN VARCHAR2,   -- オブジェクト種別('TABLE', 'VIEW' など)
  name         IN VARCHAR2,   -- オブジェクト名
  schema       IN VARCHAR2 DEFAULT NULL,  -- スキーマ名(省略時は現在のスキーマ)
  version      IN VARCHAR2 DEFAULT 'COMPATIBLE',
  model        IN VARCHAR2 DEFAULT 'ORACLE',
  transform    IN VARCHAR2 DEFAULT 'DDL'
) RETURN CLOB;
SET LONG を必ず設定する
SQL*Plus / SQLcl では CLOB の出力上限がデフォルト 80 文字です。SET LONG 1000000SET PAGESIZE 0 を先に実行しないと DDL が途中で切れます。

テーブルの DDL を取得する(最もよく使う例)

-- SQL*Plus / SQLcl での前準備
SET LONG 1000000
SET PAGESIZE 0
SET LINESIZE 200

-- 自スキーマのテーブル DDL を取得
SELECT DBMS_METADATA.GET_DDL('TABLE', 'EMPLOYEES') FROM DUAL;

-- 他スキーマ(HR)のテーブル DDL を取得(DBA 権限が必要)
SELECT DBMS_METADATA.GET_DDL('TABLE', 'EMPLOYEES', 'HR') FROM DUAL;

object_type の主な種別一覧

object_type 対象 備考
TABLE テーブル 最もよく使う
VIEW ビュー ビュー定義の SQL が取得される
INDEX インデックス 主キー制約由来は別途 CONSTRAINT
SEQUENCE シーケンス START WITH は現在値を反映(移行時は要確認)
PROCEDURE ストアドプロシージャ CREATE OR REPLACE で出力
FUNCTION ファンクション 同上
PACKAGE パッケージ仕様部 本体は PACKAGE_BODY
PACKAGE_BODY パッケージ本体 仕様部と分けて取得
TRIGGER トリガー ENABLE/DISABLE 状態も出力
SYNONYM シノニム PUBLIC シノニムは PUBLIC_SYNONYM
TYPE オブジェクト型仕様部 本体は TYPE_BODY
TYPE_BODY オブジェクト型本体
DATABASE_LINK DB リンク パスワードは取得不可
MATERIALIZED_VIEW マテリアライズドビュー リフレッシュ設定も含む
JOB DBMS_SCHEDULER ジョブ Oracle 10g 以降

SET_TRANSFORM_PARAM で出力を整形する

デフォルトの GET_DDL 出力には STORAGE 句・TABLESPACE 句・PCTFREE などの物理属性が含まれ、別環境に流用しにくい場合があります。SET_TRANSFORM_PARAM でこれらを除去できます。

よく使う変換パラメータ一覧

パラメータ名 デフォルト 用途
STORAGE TRUE FALSE にすると STORAGE 句を除去
SEGMENT_ATTRIBUTES TRUE FALSE にすると PCTFREE/PCTUSED/INITRANS 等を除去
TABLESPACE TRUE FALSE にすると TABLESPACE 句を除去
CONSTRAINTS TRUE FALSE にするとインライン制約(PK/UK/FK)を除去
REF_CONSTRAINTS TRUE FALSE にすると外部キーのみ除去
CONSTRAINTS_AS_ALTER FALSE TRUE にすると制約を ALTER TABLE で出力
PRETTY TRUE FALSE にすると改行・インデントを除去
SQLTERMINATOR FALSE TRUE にすると末尾に「;」を付加

物理属性をすべて除去してシンプルな DDL を取得

-- セッション全体に適用(以降の GET_DDL 呼び出しに有効)
BEGIN
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'STORAGE', FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'SEGMENT_ATTRIBUTES', FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'TABLESPACE', FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'SQLTERMINATOR', TRUE);
END;
/

SELECT DBMS_METADATA.GET_DDL('TABLE', 'EMPLOYEES') FROM DUAL;
SESSION_TRANSFORM はセッション終了まで有効
SESSION_TRANSFORM に設定した値はセッション中ずっと有効です。元に戻すには各パラメータを TRUE(デフォルト値)に再設定してください。一時的にだけ適用したい場合は、後述の OPEN/ADD_TRANSFORM/SET_TRANSFORM_PARAM でハンドルごとに設定する方法を使います。

GET_DEPENDENT_DDL で関連オブジェクトを取得

テーブルに紐付くインデックスや制約をまとめて取得するには GET_DEPENDENT_DDL を使います。

-- テーブルに付属するインデックスを取得
SELECT DBMS_METADATA.GET_DEPENDENT_DDL('INDEX', 'EMPLOYEES', 'HR') FROM DUAL;

-- テーブルに付属する制約を取得(PK/UK/FK/CHECK)
SELECT DBMS_METADATA.GET_DEPENDENT_DDL('CONSTRAINT', 'EMPLOYEES', 'HR') FROM DUAL;

-- 参照制約(外部キー)のみ取得
SELECT DBMS_METADATA.GET_DEPENDENT_DDL('REF_CONSTRAINT', 'EMPLOYEES', 'HR') FROM DUAL;
GET_DEPENDENT_DDL が返すもの
第 1 引数はあくまで「ベーステーブルに依存するオブジェクト種別」を指定します。'INDEX' を指定すると、そのテーブルに作成されたすべてのインデックス(主キー由来を含む)の CREATE INDEX 文が返されます。

GET_GRANTED_DDL で権限(GRANT 文)を取得

オブジェクト権限やシステム権限の GRANT 文を取得するには GET_GRANTED_DDL を使います。スキーマ移行時に権限設定を再現する際に便利です。

-- EMPLOYEES テーブルのオブジェクト権限(GRANT 文)を取得
SELECT DBMS_METADATA.GET_GRANTED_DDL('OBJECT_GRANT', 'EMPLOYEES', 'HR') FROM DUAL;

-- ユーザー HR のシステム権限(GRANT 文)を取得
SELECT DBMS_METADATA.GET_GRANTED_DDL('SYSTEM_GRANT', 'HR') FROM DUAL;

-- ユーザー HR のロール付与(GRANT 文)を取得
SELECT DBMS_METADATA.GET_GRANTED_DDL('ROLE_GRANT', 'HR') FROM DUAL;
GET_GRANTED_DDL は対象オブジェクトに権限がない場合 ORA-31608 が発生
権限が 1 件もない場合は ORA-31608: specified object of type OBJECT_GRANT not found が発生します。EXCEPTION ブロックでハンドリングするか、dba_tab_privs で事前に件数を確認してから呼び出してください。

複数オブジェクトの DDL を一括取得

USER_OBJECTS と組み合わせて同種オブジェクトを一括出力

-- 自スキーマのすべてのテーブル DDL を取得
SET LONG 1000000
SET PAGESIZE 0

SELECT DBMS_METADATA.GET_DDL('TABLE', table_name)
FROM   user_tables
ORDER  BY table_name;

-- 自スキーマのすべてのビュー DDL を取得
SELECT DBMS_METADATA.GET_DDL('VIEW', view_name)
FROM   user_views
ORDER  BY view_name;

OPEN / FETCH_DDL / CLOSE でスキーマ全体を一括出力

より細かく制御する場合は、ハンドルベースの API を使います。フィルタや変換パラメータをハンドル単位で指定できるため、セッション設定を汚さずに済みます。

DECLARE
  h    NUMBER;
  th   NUMBER;
  obj  SYS.KU$_DDLS;
BEGIN
  -- ハンドルをオープン(種別: TABLE)
  h  := DBMS_METADATA.OPEN('TABLE');

  -- フィルタ: スキーマを HR に限定
  DBMS_METADATA.SET_FILTER(h, 'SCHEMA', 'HR');

  -- 変換ハンドルで物理属性を除去
  th := DBMS_METADATA.ADD_TRANSFORM(h, 'DDL');
  DBMS_METADATA.SET_TRANSFORM_PARAM(th, 'STORAGE',            FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(th, 'SEGMENT_ATTRIBUTES', FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(th, 'SQLTERMINATOR',      TRUE);

  -- FETCH ループで全テーブルを出力
  LOOP
    obj := DBMS_METADATA.FETCH_DDL(h);
    EXIT WHEN obj IS NULL;
    FOR i IN 1..obj.COUNT LOOP
      DBMS_OUTPUT.PUT_LINE(obj(i).ddltext);
    END LOOP;
  END LOOP;

  DBMS_METADATA.CLOSE(h);
END;
/
FETCH_DDL vs FETCH_CLOB
FETCH_CLOB は 1 オブジェクトを CLOB として返します。FETCH_DDLSYS.KU$_DDLS(DDL の配列型)を返し、1 回のフェッチで複数の DDL を含む場合があります(制約や依存オブジェクトを含む場合)。確実にすべてを取得するには FETCH_DDL の使用を推奨します。

実務パターン集

パターン 1:移行スクリプト自動生成(スキーマ全体)

-- すべての種別を含む移行スクリプトをコンソール出力
BEGIN
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'STORAGE',            FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'SEGMENT_ATTRIBUTES', FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'SQLTERMINATOR',      TRUE);
END;
/

-- テーブル → インデックス → ビュー → シーケンス → プロシージャ の順で出力
SELECT DBMS_METADATA.GET_DDL(object_type, object_name, owner)
FROM (
  SELECT object_type, object_name, owner,
    CASE object_type
      WHEN 'TABLE'     THEN 1
      WHEN 'INDEX'     THEN 2
      WHEN 'VIEW'      THEN 3
      WHEN 'SEQUENCE'  THEN 4
      WHEN 'PROCEDURE' THEN 5
      WHEN 'FUNCTION'  THEN 6
      WHEN 'PACKAGE'      THEN 7
      WHEN 'PACKAGE_BODY'  THEN 8
      WHEN 'TRIGGER'       THEN 9
      ELSE 10
    END AS sort_order
  FROM dba_objects
  WHERE owner = 'HR'
    AND object_type IN ('TABLE','INDEX','VIEW','SEQUENCE','PROCEDURE','FUNCTION','PACKAGE','PACKAGE_BODY','TRIGGER')
    AND status = 'VALID'
)
ORDER BY sort_order, object_name;

パターン 2:特定テーブルの完全定義を一括出力(テーブル+制約+インデックス)

DECLARE
  PROCEDURE print_clob(p_clob CLOB) IS
    v_offset  INTEGER := 1;
    v_amount  INTEGER := 32767;
    v_buffer  VARCHAR2(32767);
  BEGIN
    LOOP
      EXIT WHEN v_offset > DBMS_LOB.GETLENGTH(p_clob);
      DBMS_LOB.READ(p_clob, v_amount, v_offset, v_buffer);
      DBMS_OUTPUT.PUT_LINE(v_buffer);
      v_offset := v_offset + v_amount;
    END LOOP;
  END;
BEGIN
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'STORAGE',            FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'SEGMENT_ATTRIBUTES', FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'SQLTERMINATOR',      TRUE);

  -- テーブル本体
  print_clob(DBMS_METADATA.GET_DDL('TABLE', 'EMPLOYEES', 'HR'));
  -- インデックス
  print_clob(DBMS_METADATA.GET_DEPENDENT_DDL('INDEX',         'EMPLOYEES', 'HR'));
  -- 制約
  print_clob(DBMS_METADATA.GET_DEPENDENT_DDL('CONSTRAINT',    'EMPLOYEES', 'HR'));
  -- 外部キー
  print_clob(DBMS_METADATA.GET_DEPENDENT_DDL('REF_CONSTRAINT','EMPLOYEES', 'HR'));
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('ERROR: ' || SQLERRM);
END;
/

パターン 3:バージョン管理用に CONSTRAINTS_AS_ALTER で出力

-- 制約を ALTER TABLE 形式で分離出力(Git 管理に向く)
BEGIN
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'STORAGE',              FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'SEGMENT_ATTRIBUTES',   FALSE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'CONSTRAINTS',          TRUE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'CONSTRAINTS_AS_ALTER', TRUE);
  DBMS_METADATA.SET_TRANSFORM_PARAM(
    DBMS_METADATA.SESSION_TRANSFORM, 'SQLTERMINATOR',        TRUE);
END;
/

SELECT DBMS_METADATA.GET_DDL('TABLE', 'EMPLOYEES', 'HR') FROM DUAL;

よくあるエラーと対処法

エラー 原因 対処法
ORA-31600 SET_FILTER / SET_TRANSFORM_PARAM に無効な値を指定 パラメータ名・値のスペルを確認
ORA-39213 XDB(XML DB)スキーマが無効または不完全 @?/rdbms/admin/catmeta.sql で再作成、または DBA に連絡
ORA-31603 指定したオブジェクトが存在しない オブジェクト名・スキーマ名・大文字小文字を確認
ORA-06512 + ORA-01031 対象スキーマへの権限不足 SELECT_CATALOG_ROLE または DBA ロールが必要
DDL が途中で切れる SET LONG の値が小さすぎる SET LONG 1000000 を事前に実行
オブジェクト名が見つからない 小文字で指定している Oracle は識別子をデフォルト大文字で管理。大文字で指定する

必要な権限

操作対象 必要な権限
自スキーマのオブジェクト 特別な権限不要(ログインユーザーの所有オブジェクトは取得可)
他スキーマのオブジェクト SELECT ANY DICTIONARY または SELECT_CATALOG_ROLE
FULL スキーマ一括取得 EXP_FULL_DATABASE または DBA ロール

よくある質問(FAQ)

Q GET_DDL と expdp(Data Pump エクスポート)の DDL モードはどう違う?
A

GET_DDL は SQL で即座に 1 オブジェクトの DDL を取得できますが、あくまでテキスト出力です。expdp の CONTENT=METADATA_ONLY は dmp ファイルとして出力され、impdp で再インポートできます。

スキーマ移行には expdp/impdp、手動スクリプト管理や即時確認には DBMS_METADATA という使い分けが実務ではよく行われます。

Q SEQUENCE の START WITH が現在値になってしまう。初期値で取得したい
A

DBMS_METADATA はシーケンスの現在値(NEXTVAL – INCREMENT_BY)を START WITH に出力するため、取得した DDL をそのまま別環境で実行すると大きな値から始まります。

環境移行では取得した DDL の START WITH 値を手動で 1 に書き換えるか、user_sequences ビューから LAST_NUMBER を確認した上で調整してください。

Q PACKAGE_BODY を取得したら文字化けする
A

SQL*Plus の NLS_LANG 設定がデータベースの文字セットと一致していない場合に発生します。クライアント側の NLS_LANG を JAPANESE_JAPAN.AL32UTF8(DB が UTF-8 の場合)に合わせてから実行してください。

Windows では set NLS_LANG=JAPANESE_JAPAN.AL32UTF8、Linux では export NLS_LANG=JAPANESE_JAPAN.AL32UTF8 を SQL*Plus 起動前に設定します。

Q GET_DEPENDENT_DDL で ORA-31608 が発生する
A

ORA-31608: specified object of type INDEX not found は依存オブジェクトが 0 件のときに発生します。対象テーブルにインデックスが存在しない場合や、GET_DEPENDENT_DDL の第 1 引数に間違った種別を指定した場合に起きます。

EXCEPTION ブロックで WHEN OTHERS THEN NULL; を入れてスキップするか、user_indexes WHERE table_name = '...' で事前に件数を確認してから呼び出してください。

Q VIEW の DDL を取得したら「FORCE」が付く。なぜ?
A

DBMS_METADATA はビューの DDL を CREATE OR REPLACE FORCE VIEW ... として出力します。FORCE は参照先のテーブルが存在しなくても作成できるオプションです。

移行スクリプトとして使う場合はテーブル DDL より後にビュー DDL を実行する順序にすれば問題ありません。FORCE は削除せずそのまま使うのが安全です。

まとめ

用途 推奨の方法
1 つのオブジェクト DDL を即時確認 GET_DDL(object_type, name, schema)
テーブルに紐付く制約・インデックスを取得 GET_DEPENDENT_DDL('CONSTRAINT'/'INDEX', table, schema)
STORAGE/TABLESPACE 句を除去してシンプルに SET_TRANSFORM_PARAM(SESSION_TRANSFORM, 'STORAGE', FALSE)
制約を ALTER TABLE 形式で出力(Git 管理向き) SET_TRANSFORM_PARAM(SESSION_TRANSFORM, 'CONSTRAINTS_AS_ALTER', TRUE)
スキーマ全体を一括出力(ループ処理) OPEN / FETCH_DDL / CLOSE + SET_FILTER
移行スクリプト全自動生成 dba_objects を JOIN して object_type ごとに順序制御

DBMS_METADATA を使いこなすことで、GUI ツールなしに SQL だけでスキーマ全体の DDL を抽出・整形できます。SET_TRANSFORM_PARAM で出力をシンプルに制御し、ループ処理で一括取得するパターンを覚えれば、データ移行・バージョン管理・環境差分確認のほぼすべてに対応できます。