OracleからPostgreSQLへの移行をClaude Codeで進める — ora2pgの変換を直す
ora2pgで変換したPL/SQLの残りをClaude Codeで直し、件数と合計の照合SQLで移行前後の一致を確かめる手順です。パッケージ、NULLと空文字、数値型の3点を扱います。
OracleのPL/SQLをPostgreSQLへ移すとき、ora2pgは変換の大半を自動で済ませます。ただし、出力されたPL/pgSQLがそのまま業務の結果を再現する保証はありません。残りの手直しをClaude Codeに任せ、直したあとの正しさは件数と合計の照合SQLで確かめる、という分担が現実的です。
この記事は、その分担の作り方を順に示します。Claudeに「直す」作業と「検算を書く」作業を渡し、一致の判定は人間が読める数字に寄せます。
ora2pgが自動で済ませる範囲と、手直しが残る範囲
ora2pgは、Oracle(とMySQL)に接続して構造やデータを取り出し、PostgreSQLへ読み込めるSQLスクリプトを生成するオープンソースの移行ツールです。移行評価レポート、スキーマ変換、PL/SQLからPL/pgSQLへの変換、データ移行(オフライン、停止を伴う)、データ検証が機能として挙げられています。
ただしOracle固有のPL/SQLについて、公式のドキュメントは次のように書いています。関数、プロシージャ、パッケージ、トリガーの生成コードは、PostgreSQLの構文に合わせて人間が見直す必要があります。PL/SQLの自動変換は発展途上で、手作業が出る前提です。
つまり手直しの対象は最初から決まっています。Claude Codeに渡すのは、その見直し作業です。
| 工程 | ora2pgの役割 | Claude Codeの役割 | 人間の役割 |
|---|---|---|---|
| 抽出と変換 | ora2pgの役割Oracleから取り出して生成 | Claude Codeの役割なし | 人間の役割設定ディレクティブの選択 |
| 構文エラーの解消 | ora2pgの役割なし | Claude Codeの役割psqlのエラーを読んで修正 | 人間の役割差分のレビュー |
| 検算SQLの作成 | ora2pgの役割TEST系の比較機能 | Claude Codeの役割両DB用の同一クエリを書く | 人間の役割合否の判定 |
作業ディレクトリと、Claudeに守らせる規約
ora2pgには、移行作業用のツリーを作るオプションがあります。
ora2pg --project_base /app/migration/ \
--init_project test_project生成されるツリーの要点は2つです。sources/にOracleの元コード、schema/にPostgreSQL向けに変換した結果が入ります。config/には設定ファイル、reports/には移行コストの評価レポートが置かれます。
この分け方は、Claudeへの指示にそのまま使えます。元コードは読むだけ、直すのは変換後だけ、という線引きです。プロジェクトのCLAUDE.mdに書いておくと、毎回の指示で言い直さずに済みます。
## Oracle→PostgreSQL移行のルール
- sources/はOracleの元コード。読むだけで、編集しない
- 直すのはschema/配下。1ファイル直すごとにpsql -fで流す
- 関数は、checks/のケース表と同じ入力でOracle側と同じ値が出るまで完了にしない
- NULLと空文字、数値の丸めに関わる変更は、必ず理由をコメントに書くClaude Codeの公式ドキュメントは、CLAUDE.mdに書く対象を「Claudeが同じ間違いを2回したとき」「毎回チャットで同じ訂正を打つとき」と説明しています。移行では、同じ型の変換ミスが何十ファイルにも現れます。1回目に直した時点で規約へ足す運用が向いています。
接続情報の扱いも先に決めます。ora2pgの設定ファイルには、Oracleへの接続先を定義します。Claudeに読ませる必要はないので、プロジェクトの.claude/settings.jsonに拒否ルールを書きます。
{
"permissions": {
"deny": ["Read(./config/ora2pg.conf)"]
}
}権限ルールは「拒否、確認、許可」の順に評価され、最初に一致したものが結果になります。拒否に入れた対象は、許可ルールを足しても通りません。
このルールが効く範囲には限りがあります。組み込みのファイルツールと、cat・head・sedのようにBashで認識される読み取りコマンドには及びます。一方、ファイル名を指定せずに読むコマンド(grep -rなど)や、PythonやNodeのスクリプトが自分でファイルを開く場合は対象外です。接続情報を確実に守りたいときは、Bashの実行を許可しないか、サンドボックスを有効にします。
変換結果で見直す4つの穴
手直しが残りやすいのは、パッケージ、NULLと空文字、数値型、暗黙の型変換の4つです。それぞれ、ora2pgの設定と生成物の両方を見ます。
パッケージは、スキーマにするか関数名にするか
Oracleのパッケージは、既定ではPostgreSQLのスキーマとして再現されます。PACKAGE_AS_SCHEMAを無効にすると、パッケージ名と関数名を連結した単独の関数になり、package_name.function_name()の呼び出しはpackage_name_function_name()に置き換えられます。
どちらを選ぶかで、アプリケーション側の呼び出しコードの書き換え方が変わります。Claudeに頼む前に、方針を一つに決めてください。決めないまま直させると、ファイルごとに別の命名が混ざります。
この置換が働くのは、ora2pgのPL/SQL変換が読み込むDDLとコードです。ora2pgが読まないアプリ側のコードは、自分で書き換えます。呼び出し元の洗い出しは、次のようにClaudeに頼めます。
アプリのコードからpkg_billingの関数呼び出しを全部探して、
schema/packages/pkg_billingの関数名と1対1で対応表にしてNULLと空文字は、挙動が変わる
Oracleは空文字をNULLと同じに扱います。PostgreSQLは両者を区別します。ora2pgにはNULL_EQUAL_EMPTYというディレクティブがあり、NULL判定をcoalesceを使った式に置き換えて、Oracleの挙動を真似られます。
NULL判定の書き換え(公式の説明に沿った形)
Oracle(元のPL/SQL)
IF p_note IS NULL THEN
...NULL_EQUAL_EMPTY有効時の変換
IF (coalesce(p_note::text, '') = '') THEN
...ここで引っかかるのは、公式がこの置換を最善とは言っていない点です。アプリケーションを自分で制御できるなら、空文字をNULLに変換するほうがよいと書かれています。
置換を有効にすれば、元の挙動に近づきます。空文字をNULLへ寄せる直し方は、PostgreSQLらしい書き方になります。どちらを採るかは移行の方針なので、Claudeに決めさせず、CLAUDE.mdに一行で書いておきます。
数値型は、丸めが入る
OracleのNUMBER(p,s)は、既定ではPostgreSQLのrealやfloatに近似変換されます。金額のように桁を保ちたい列では、ora2pgのPG_NUMERIC_TYPEを0にすると、numeric(p,s)のまま保てます(既定の1が近似変換です)。numericはrealやdoubleより遅いとも書かれています。
この丸めが、後で件数ではなく合計に出ます。次の節の検算が合計を見るのは、このためです。
暗黙の型変換は、エラーより値のずれで表れる
文字列と数値の比較や、日付と文字列の突き合わせのように、型の変換を言語任せにしていた箇所は要注意です。構文エラーなら変換直後のpsqlで見つかります。エラーにならず値だけが変わる場合は、同じ入力で両側を動かさないと分かりません。
暗黙の型変換の結果が一致するかは、変換後のコードを同じ入力で動かすまで分かりません。そのため検算ケースを用意します。
Claude Codeで直す順番
直す作業は、一度に全ファイルを渡さず、小さい単位に区切ります。Claude Codeの公式ドキュメントも、リファクタリングは「小さくテスト可能な増分」で行うよう勧めています。
1ファイルを直す流れ
- 1
エラーを出す
schema/functions/の1ファイルをpsqlで流し、エラー出力を保存します。 - 2
エラーだけを渡す
エラーとファイルをClaudeに渡し、そのファイルだけの修正を頼みます。
- 3
差分を読む
直した箇所を
git diffで人間が読みます。NULLと数値の変更は特に確認します。 - 4
検算ケースを流す
次の節の照合SQLで、Oracle側と同じ値かを確かめます。
エラーの解消だけなら、非対話モードで繰り返せます。claude -pは標準入力や引数のプロンプトを一度だけ処理して終了し、--allowedToolsで承認なしに使えるツールを指定できます。
for f in schema/functions/*.sql; do
psql -v ON_ERROR_STOP=1 -f "$f" 2> err.txt || \
claude -p "$f のエラー(err.txt)を直して。\
sources/は編集しない" \
--allowedTools "Read,Edit"
doneこのループは「流す、落ちたら直す」だけを自動化します。直した結果が正しいかは判定しません。判定は次の照合SQLの仕事です。
件数と合計の照合SQLで検算する
移行前後で同じ入力に同じ結果が返るかは、SQLの結果を並べれば確かめられます。ora2pgにも比較機能があり、TESTはOracleとPostgreSQLのオブジェクトの差、TEST_COUNTは表の行数の差、TEST_VIEWはビューの行数の差、TEST_DATAは両側の行のデータ検証を行います。--count_rowsを付けると、実際の行数を数えます。
TEST_DATAの既定の比較は先頭の10000行で、0を指定すると全行になります。データを変更した後は、行の並びが両側で変わります。主キーかユニーク索引で並べる必要があるため、そのどちらも持たない表は比較できません。
ora2pgの比較で足りない部分、つまり合計とNULLの内訳は、自分で書いた照合SQLで補います。Claudeに頼むのはここです。
表ごとの集計を、両側で同じ形にそろえる
SELECT 'orders' AS tbl,
COUNT(*) AS row_cnt,
SUM(amount) AS amount_sum,
SUM(CASE WHEN note IS NULL THEN 1 ELSE 0 END) AS note_null_cnt
FROM orders;このSQLはOracleでもPostgreSQLでもそのまま動く形にしてあります。FILTER句のような片方だけの構文を避けるのが、Claudeに頼むときの条件です。SQL*Plusとpsqlは既定の出力形式(見出し、桁揃え、数値の書式)が異なるため、両側の出力形式をそろえてから保存し、diffで比べます。形式がそろえば、一致の判定が機械的に済みます。
diff oracle_orders.out pg_orders.out \
&& echo "一致"行数が合って合計が違えば、数値型の丸めを疑います。行数が合ってnote_null_cntだけ違えば、NULLと空文字の扱いです。
関数は、固定の入力表で動かす
関数やプロシージャは、ケース表を作って同じ入力を流します。ケースは境界を含めます。
SELECT t.case_id,
pkg_billing.calc_tax(t.amount, t.tax_code) AS tax
FROM tax_cases t
ORDER BY t.case_id;入力にNULL、空文字、端数、桁あふれ近くの金額を含めてください。本番データから抜く場合は、個人情報を落とした抜き出しにします。ケース表の作成自体もClaudeに頼めます。ケースの狙いを人間が先に書くと、ずれにくくなります。
落とし穴
行数が一致しても、合計は一致しないことがある
行数は表の構造だけで決まり、合計は列の型と値で決まります。NUMBERが近似型に変換されると、行数は一致して合計だけが少しずれます。検算は件数だけで終えず、合計を必ず並べてください。
一致しないときの切り分け
合計か件数が合わないとき
- Q行数は両側で一致しているか?答えがいいえのとき答えがはい
ロード中の失敗やフィルタを疑い、ora2pgのロードログを見ます。
- Q合計だけが違うか?答えがいいえのとき答えがはい
金額列の型を確認し、
numeric(p,s)に保っているかを見ます。 - QNULLの件数が違うか?答えがいいえのとき答えがはい
NULL_EQUAL_EMPTYの設定と、空文字の入力経路を見ます。
関数単位で入力を絞る
ケース表の1行ずつで両側の結果を比べ、ずれた行から原因を特定します。
ora2pgだけで足りるか、別の道具が要るか
ora2pgは、スクリプトで動く移行エンジンとして位置づけられています。開発元のHexaClusterは、同じ作者の企業向け製品としてHexaRocketを案内しており、スキーマ変換の自動化と検証、オンライン移行とロールバックを特徴に挙げています。料金は問い合わせです。
どちらを採るかは、停止できる時間と検証の厳しさで変わります。ora2pgの移行はオフラインで、停止を伴います。この記事の手順は、そのオフライン移行を前提にしています。
移行先のPostgreSQLを触りながら調べるなら、PostgreSQL向けMCPサーバーの使い方が参考になります。元のOracleにClaudeから接続する選択肢は、Oracle MCPサーバーの解説にあります。ただし、移行作業の途中で本番のOracleへ直接つなぐ必要はありません。取り出したsources/を読ませるほうが安全です。
まとめ
ora2pgの出力は、完成品ではなく下書きです。パッケージの扱いとNULLの方針は人間が先に決め、構文エラーの解消はClaude Codeに任せます。合否は件数と合計の照合SQLで決めます。
先に決めておくと、手戻りが減ります。ディレクティブの選択を後回しにすると、ファイルごとに別の直し方が混ざります。移行の完了条件を「ケース表の全行が一致」に置くと、Claudeにも作業の終わりが伝わります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。