Oracle → PostgreSQL 移行 学習ノート

DB更改プロジェクト着任前の事前学習資料

① 違いを知る
② 設計・テスト
③ 運用・監視
🗺️2つのDBの立ち位置
項目OraclePostgreSQL
種別商用RDBMSOSS(無償)
ライセンス非常に高額無償(PostgreSQL License)
シェアエンタープライズで最大手OSS DBで最大手
SQL標準準拠独自拡張が多いSQL標準に忠実
サポートOracle社による商用サポートコミュニティ+商用サポート選択
💡
「アプリの改修はない前提」でも、SQL文の非互換が潜んでいる場合があります。移行前の互換性調査が最重要です。
📖 用語メモ
エンタープライズ 大企業・基幹系向けという意味。IT文脈では「大規模・高信頼性・商用サポートあり」のシステムを指す。銀行・官公庁・大手製造業の基幹システムがイメージに近い。Oracleはこうした用途で長年使われてきた。
OSS Open Source Software の略。ソースコードが公開されていて誰でも無償で使える・改変できるソフトウェア。PostgreSQLはOSSのため、Oracleと違いライセンス費用がかからない。これがDB更改の主な動機のひとつ。
⚠️NULL と 空文字列
🚨
最も多いバグの原因。Oracleでは空文字('')はNULLと同一扱いだが、PostgreSQLでは別物。

Oracle

-- '' は NULL と同じ
WHERE col = ''
-- → 結果0件(NULLは=で比較不可)
WHERE col IS NULL
-- → 空文字も含めてヒット

PostgreSQL

-- '' は '' (空文字) のまま
WHERE col = ''
-- → 空文字のみヒット
WHERE col IS NULL
-- → NULLのみヒット(空文字は含まない)
📝よく使う関数・構文の対応表
OraclePostgreSQL用途
NVL(a, b)COALESCE(a, b) (コアレス)NULL置換
NVL2(a,b,c)CASE WHEN a IS NOT NULL THEN b ELSE c ENDNULL条件分岐
DECODE(col,v1,r1,...)CASE WHEN col=v1 THEN r1... END条件分岐
SYSDATENOW() / CURRENT_TIMESTAMP現在日時
TRUNC(date)DATE_TRUNC('day', ts)日付の切り捨て
TO_CHAR(d,'YYYYMMDD')TO_CHAR(d,'YYYYMMDD')日付フォーマット(同じ)
SUBSTR(s,1,5)SUBSTRING(s,1,5)部分文字列
INSTR(s,'x')POSITION('x' IN s)文字位置
MONTHS_BETWEEN(d1,d2)EXTRACT(MONTH FROM AGE(d1,d2)) (エクストラクト)月差
CONNECT BYWITH RECURSIVE (リカーシブ)階層クエリ
📄ページングとROWNUM

Oracle(ROWNUM)

SELECT * FROM (
  SELECT *, ROWNUM rn FROM t
  WHERE ROWNUM <= 20
) WHERE rn >= 11;

PostgreSQL(LIMIT/OFFSET)

SELECT * FROM t
LIMIT 10 OFFSET 10;
-- シンプル!
🔗外部結合・DUALテーブル・識別子
項目OraclePostgreSQL
外部結合 WHERE a.id = b.id(+)
Oracle独自構文
LEFT JOIN b ON a.id = b.id
ANSI標準
DUALテーブル SELECT 1 FROM DUAL SELECT 1
FROM句不要
識別子の大小文字 大文字に正規化
COL / col / Col → すべて COL
小文字に正規化
COL / col / Col → すべて col
大小文字を区別したいとき "Col"(ダブルクォート) "Col"(同様。ただし通常は避ける)
🗃️主なデータ型マッピング
Oracle型PostgreSQL型注意点
VARCHAR2(n)VARCHAR(n)ほぼ同じ
CHAR(n)CHAR(n)同じ
NUMBER(p,s)NUMERIC(p,s)ほぼ同じ
NUMBER(n)(整数)INTEGER / BIGINT整数ならINTEGERが推奨
DATETIMESTAMP⚠️ OracleのDATEは時刻を含む。PGのDATEは日付のみ
TIMESTAMPTIMESTAMP同じ
CLOBTEXTPGのTEXTは上限なし
BLOBBYTEAバイナリ型
なしBOOLEANPGはBOOLEAN型あり(OracleはNUMBER(1)で代用)
ROWIDなし代替:OID(非推奨)or シーケンスID
⚠️
DATE型に注意。OracleのDATE型は時刻まで格納できるが、PostgreSQLのDATE型は日付のみ。時刻が入っているカラムはTIMESTAMPに変換が必要。
🏗️スキーマとユーザーの概念
項目OraclePostgreSQL
スキーマユーザー = スキーマ
USER_A を作るとスキーマも自動生成
ユーザー ≠ スキーマ
スキーマは別途作成。デフォルトは public
接続設定listener.ora / tnsnames.orapg_hba.conf / postgresql.conf
インスタンス1インスタンス = 1DB1クラスタ内に複数DB可
ストアド言語PL/SQLPL/pgSQL(構文が似ているが差異あり)
バックアップRMANpg_dump / pg_basebackup
統計情報収集DBMS_STATS(手動 or 夜間自動)ANALYZE(AUTOVACUUM が自動実行)
📜PL/SQL vs PL/pgSQL
💡
構造はよく似ているが細かい差異が多い。ストアドを移植する場合は1本ずつ動作確認が必要。
項目PL/SQL(Oracle)PL/pgSQL(PostgreSQL)
標準出力DBMS_OUTPUT.PUT_LINE('msg')RAISE NOTICE 'msg'
カーソル定義CURSOR c IS SELECT ...CURSOR c FOR SELECT ...
カーソル終端チェックc%NOTFOUNDNOT FOUND
無名ブロックの実行BEGIN ... END; /DO $$ BEGIN ... END; $$;
型参照table.col%TYPEtable.col%TYPE(同じ)
例外処理EXCEPTION WHEN OTHERSEXCEPTION WHEN OTHERS(同じ)
💻 コード例
例1|変数宣言・出力

Oracle PL/SQL

DECLARE
  v_name VARCHAR2(100) := 'テスト';
BEGIN
  DBMS_OUTPUT.PUT_LINE(v_name);
END;
/

PostgreSQL PL/pgSQL

DO $$
DECLARE
  v_name VARCHAR(100) := 'テスト';
BEGIN
  RAISE NOTICE '%', v_name;
END;
$$;
例2|カーソルループ

Oracle PL/SQL

DECLARE
  CURSOR c1 IS
    SELECT name FROM users;
  v_name users.name%TYPE;
BEGIN
  OPEN c1;
  LOOP
    FETCH c1 INTO v_name;
    EXIT WHEN c1%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(v_name);
  END LOOP;
  CLOSE c1;
END;
/

PostgreSQL PL/pgSQL

DO $$
DECLARE
  c1 CURSOR FOR
    SELECT name FROM users;
  v_name users.name%TYPE;
BEGIN
  OPEN c1;
  LOOP
    FETCH c1 INTO v_name;
    EXIT WHEN NOT FOUND;
    RAISE NOTICE '%', v_name;
  END LOOP;
  CLOSE c1;
END;
$$;
例3|例外処理

Oracle PL/SQL

BEGIN
  SELECT name INTO v_name
  FROM users WHERE id = 1;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('なし');
  WHEN OTHERS THEN
    RAISE;
END;
/

PostgreSQL PL/pgSQL

BEGIN
  SELECT name INTO v_name
  FROM users WHERE id = 1;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RAISE NOTICE 'なし';
  WHEN OTHERS THEN
    RAISE;
END;
⚠️
PGの無名ブロックは DO $$ ... $$; で囲む。Oracle の BEGIN ... END; / とは書き方が異なる。移植時に書き忘れやすいポイント。
🔍SQL互換性チェックリスト
💡
「アプリ改修なし前提」でも、SQLの非互換を先に洗い出すことが移行成功の鍵。以下を全SQLで確認する。
  • 高リスク (+) 外部結合構文の使用 → LEFT/RIGHT JOINに書き換え必要
  • 高リスク ROWNUM の使用 → LIMIT/OFFSETに変換
  • 高リスク FROM DUAL の使用 → FROM句を削除
  • 高リスク 空文字('')とNULLの混在 → ロジックを精査し修正
  • 中リスク NVL / DECODE → COALESCE / CASE WHENに変換
  • 中リスク CONNECT BY 階層クエリ → WITH RECURSIVEに変換
  • 中リスク DATE型に時刻が入っているカラム → TIMESTAMPに変換
  • 中リスク PL/SQL ストアドプロシージャ → PL/pgSQLへ移植
  • 要確認 SYSDATENOW() / CURRENT_TIMESTAMP
  • 要確認 テーブル名・カラム名にPostgreSQLの予約語が使われていないか
  • 要確認 識別子が大文字前提で書かれていないか(PGは小文字に正規化)
  • 要確認 NUMBER型を整数として使っているカラムがないか
📐シーケンス・採番の設計
項目OraclePostgreSQL
自動採番 CREATE SEQUENCE
+ トリガーでNEXTVAL
SERIAL(簡易)
GENERATED AS IDENTITY(推奨)
現在値取得 seq.CURRVAL currval('seq_name')
次の値取得 seq.NEXTVAL nextval('seq_name')
⚠️
シーケンスの現在値(CURRVAL)はセッション内でNEXTVALを一度呼ばないと参照できない。Oracle同様の挙動を期待するコードは要注意。
🔤文字コード・照合順序(Collation)
  • DBの文字コードは UTF-8 を推奨。移行元がSJISの場合はデータ変換が必要。
  • 照合順序(Collation)はDB作成時に決まる。日本語環境では ja_JP.UTF-8 または C を選択。
  • ソート順が変わる可能性あり。アプリのソート結果を移行前後で比較テストすること。
  • 全角・半角の扱いはOracle関数(UPPER/LOWER等)とPGで結果が異なる場合あり。
インデックス設計
種類用途備考
B-tree通常の等値・範囲検索デフォルト。Oracleと同じ
GIN全文検索・配列・JSONBPG独自。LIKE検索にも使える
GiST地理データ・範囲型PG独自
BRIN大規模テーブルの範囲検索物理順序が整っている場合に有効
OracleのFUNCTION-BASED INDEXはPGでも使える(CREATE INDEX ON t (LOWER(col))等)。移行元にある場合は確認。
🧪テスト観点チェックリスト
  • SQLの実行結果比較:Oracle/PGで同じSQLを実行し、件数・値・順序が一致するか確認
  • NULL/空文字の境界テスト:NULLと空文字が混在するデータで結果が変わらないか確認
  • 日付境界テスト:DATE型→TIMESTAMP変換後に時刻部分が正しく扱われるか
  • ソート順の確認:ORDER BYを含むSQLの結果順序がOracleと一致するか
  • パフォーマンステスト:主要SQL・バッチの実行時間をOracle時と比較。EXPLAIN ANALYZEで実行計画を確認
  • ストアドの移植確認:PL/pgSQLに変換したストアドの動作確認(例外処理・カーソル等)
  • 採番の確認:シーケンスの初期値・インクリメント値がOracleと一致しているか
  • トランザクション境界テスト:コミット・ロールバックのタイミングが正しいか
  • 大量データテスト:本番相当のデータ量で性能劣化が起きないか
  • 同時接続テスト:複数ユーザーが同時操作したときのデッドロック・待機の発生確認
📊実行計画の確認方法

Oracle

EXPLAIN PLAN FOR
  SELECT * FROM t;
SELECT * FROM
  TABLE(DBMS_XPLAN.DISPLAY);

PostgreSQL

-- 実行計画のみ
EXPLAIN SELECT * FROM t;
-- 実際に実行して詳細表示
EXPLAIN ANALYZE SELECT * FROM t;
EXPLAIN ANALYZE は実際にSQLを実行する。本番環境では SELECT のみで使うこと(UPDATE/DELETEにはトランザクションでロールバックを)。
🧹VACUUM(バキューム)← 最重要
📖
そもそもなぜVACUUMが必要なのか?
PostgreSQLはMVCC(Multi-Version Concurrency Control:多版型同時実行制御)という仕組みでトランザクションを管理している。

MVCCの仕組み:UPDATEやDELETEをしても、古いバージョンのデータをすぐには物理削除しない。「他のトランザクションがまだ古いデータを参照しているかもしれない」ため、見えなくするだけで残しておく。この不要になった古いデータを dead tuple(デッドタプル) と呼ぶ。

放置すると何が起きるか:
・テーブルが肥大化してディスクを圧迫
・スキャン対象が増えてクエリが遅くなる
・トランザクションIDが枯渇(XIDラップアラウンド)→ DBが強制停止

Oracleとの違い:Oracleは「UNDO表領域」に古いデータを保存する方式のため、この問題が起きない。PostgreSQLはテーブル内に古いデータを残す方式なので、定期的に掃除(VACUUM)が必要。
🚨
Oracleにない概念。理解必須。VACUUMを怠るとテーブルが肥大化し、最悪トランザクションIDの周回(XIDラップアラウンド)でDBが停止する。
種類説明
AUTOVACUUMPostgreSQLが自動で実行。通常はこれに任せる。
VACUUM不要領域を回収(ディスク解放はしない)
VACUUM FULLテーブルを再構築してディスクを解放。テーブルロックが発生するので本番は要注意。
ANALYZE統計情報を更新。実行計画の精度が上がる。
⚠️
AUTOVACUUM が追いつかないほどの更新頻度の場合は手動チューニングが必要。pg_stat_user_tables で dead tupleの蓄積を監視する。
📈統計情報と実行計画の劣化
  • PostgreSQLはANALYZEで集めた統計情報をもとに実行計画を決定する。
  • 大量INSERT/UPDATE後は統計情報が古くなり、遅くなることがある → ANALYZE テーブル名 を実行。
  • AUTOVACUUMがANALYZEも兼ねているが、タイミングの遅れに注意。
💾バックアップ方法の比較
方法説明用途
pg_dump論理バックアップ(SQL形式)特定テーブルのバックアップ、DBマイグレーション
pg_dumpallクラスタ全体の論理バックアップ全DB・ロール含めた完全バックアップ
pg_basebackup物理バックアップディザスタリカバリ、レプリカ構築
WALアーカイブWALログを継続的に保存PITR(ポイントインタイムリカバリ)
本番環境では pg_basebackup + WALアーカイブ の組み合わせでPITRを構成するのが一般的。OracleのRMANに相当する構成。
🔭主要な監視ビュー
ビュー名内容Oracleの相当品
pg_stat_activity実行中のセッション・SQLV$SESSION
pg_stat_user_tablesテーブルのアクセス統計・dead tuple数DBA_TABLES
pg_stat_user_indexesインデックスの利用状況V$INDEX_STATS
pg_locksロック待ち状況V$LOCK
pg_stat_bgwriterバックグラウンドライターの統計(類似なし)
pg_stat_statementsSQLごとの実行統計(拡張機能)V$SQL / AWR
💡
pg_stat_statements はデフォルト無効。postgresql.confshared_preload_libraries = 'pg_stat_statements' を追加して有効化する。スロークエリ分析に非常に有用。
🐢スロークエリの検出
-- postgresql.conf に設定
log_min_duration_statement = 1000  -- 1秒以上のSQLをログ出力

-- pg_stat_statementsで確認(拡張機能有効化後)
SELECT
  mean_exec_time,
  calls,
  query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 20;
⚙️主要設定ファイル
ファイル役割Oracleの相当品
postgresql.confDB全体の設定(メモリ・ログ・接続数等)init.ora / spfile
pg_hba.conf接続認証の設定(IPアドレス・認証方式)sqlnet.ora
pg_ident.confOSユーザーとDBユーザーのマッピング(類似なし)
⚠️
pg_hba.conf の設定ミスは接続不能に直結する。変更後は必ず pg_reload_conf() または pg_ctl reload で反映し、疎通確認をすること。
🔌コネクションプーリング
💡
PostgreSQLは接続ごとにプロセスが起動するため、大量接続に弱い。本番環境では PgBouncer などのコネクションプーラーを前段に置くのが一般的。
項目OraclePostgreSQL
接続モデル共有サーバー or 専用サーバー1接続 = 1プロセス
最大接続数processes パラメータmax_connections(デフォルト100)
推奨構成(組み込み)PgBouncer / pgpool-II を前段に配置
📅PostgreSQLのバージョンサイクル
  • 毎年1回メジャーバージョンがリリースされる(例:16 → 17 → 18...)
  • サポート期間は リリースから5年(EOL = End of Life:サポート終了日 後はセキュリティパッチなし)
  • マイナーバージョンアップ(16.1 → 16.2等)は再起動だけで適用可能
  • メジャーバージョンアップは pg_upgrade またはダンプ&リストアが必要
  • EOLをカレンダーに登録して忘れないこと。Oracleと違い延長サポートは基本なし。
⚠️
Oracleは長期サポートが当たり前だったが、PostgreSQLは5年で EOL。更改後にどのバージョンを採用するか、次のバージョンアップ計画も着任時に確認しておくこと。