「PostgreSQL 9.6から15にアップグレードしても大丈夫?」 「MySQLからPostgreSQLに移行したいけど、どこまで互換性がある?」 「アプリケーションとの互換性は問題ない?」
こんな不安を抱えていませんか?
データベースの移行やアップグレードで最も重要なのが互換性の確認です。 互換性を正しく理解していれば、トラブルを未然に防ぎ、スムーズな移行が可能になります。
この記事では、PostgreSQLの互換性について、バージョン間の違いから他DBとの比較、実際の移行手順まで、実例たっぷりで解説します。 もう移行で悩むことはありません!
PostgreSQLバージョン間の互換性

バージョン番号の読み方
PostgreSQLのバージョン番号を理解することが、互換性判断の第一歩です。
バージョン10以降:
- メジャーバージョン:10, 11, 12, 13, 14, 15, 16
- マイナーバージョン:15.4の「.4」部分
バージョン9.6以前:
- メジャーバージョン:9.6, 9.5, 9.4
- マイナーバージョン:9.6.24の「.24」部分
互換性の基本ルール
-- バージョン確認
SELECT version();
-- PostgreSQL 15.4 on x86_64-pc-linux-gnu...
-- より詳細なバージョン情報
SELECT
current_setting('server_version') AS バージョン,
current_setting('server_version_num') AS バージョン番号,
current_setting('server_encoding') AS エンコーディング;
互換性の原則:
- マイナーバージョン:完全互換(9.6.1 → 9.6.24)
- メジャーバージョン:一部非互換の可能性あり
- アップグレード推奨:最新マイナーバージョンへ
主要バージョン間の互換性マトリックス
-- 機能別互換性チェック用クエリ
CREATE OR REPLACE FUNCTION check_version_features()
RETURNS TABLE (
機能 TEXT,
必要バージョン TEXT,
現在の状態 TEXT
) AS $$
BEGIN
RETURN QUERY
-- パーティショニング
SELECT
'ネイティブパーティショニング',
'10.0以上',
CASE
WHEN current_setting('server_version_num')::INTEGER >= 100000
THEN '利用可能'
ELSE '利用不可'
END
UNION ALL
-- GENERATED COLUMNS
SELECT
'GENERATED COLUMNS',
'12.0以上',
CASE
WHEN current_setting('server_version_num')::INTEGER >= 120000
THEN '利用可能'
ELSE '利用不可'
END
UNION ALL
-- SQL/JSON
SELECT
'SQL/JSON path',
'12.0以上',
CASE
WHEN current_setting('server_version_num')::INTEGER >= 120000
THEN '利用可能'
ELSE '利用不可'
END
UNION ALL
-- Parallel Vacuum
SELECT
'PARALLEL VACUUM',
'13.0以上',
CASE
WHEN current_setting('server_version_num')::INTEGER >= 130000
THEN '利用可能'
ELSE '利用不可'
END
UNION ALL
-- MERGE
SELECT
'MERGE文',
'15.0以上',
CASE
WHEN current_setting('server_version_num')::INTEGER >= 150000
THEN '利用可能'
ELSE '利用不可'
END;
END;
$$ LANGUAGE plpgsql;
-- 実行
SELECT * FROM check_version_features();
非互換な変更と対処法
PostgreSQL 14→15の主な変更点
-- 1. PUBLIC スキーマの権限変更(15以降)
-- 以前:誰でもPUBLICスキーマにオブジェクト作成可能
-- 現在:明示的な権限付与が必要
-- 対処法:従来の動作に戻す場合
GRANT CREATE ON SCHEMA public TO PUBLIC;
-- 2. 権限関連の関数名変更
-- 旧(14以前)
-- SELECT has_table_privilege('user', 'table', 'SELECT');
-- 新(15以降)推奨
-- SELECT pg_has_role('user', 'role', 'USAGE');
PostgreSQL 13→14の主な変更点
-- 1. 配列の添字が1始まりに統一
-- 影響を受けるコードの例
CREATE OR REPLACE FUNCTION check_array_behavior()
RETURNS void AS $$
DECLARE
arr INTEGER[] := ARRAY[10, 20, 30];
BEGIN
-- PostgreSQL 14以降は常に1始まり
RAISE NOTICE 'First element: %', arr[1]; -- 10
RAISE NOTICE 'Array lower bound: %', array_lower(arr, 1); -- 1
END;
$$ LANGUAGE plpgsql;
PostgreSQL 11→12の主な変更点
-- 1. recovery.confの廃止
-- 旧方式(11以前):recovery.confファイルを作成
-- 新方式(12以降):postgresql.confまたはpostgresql.auto.confに記載
-- 新しい設定方法
ALTER SYSTEM SET primary_conninfo = 'host=primary_server port=5432';
ALTER SYSTEM SET restore_command = 'cp /archive/%f %p';
-- 2. WITH OIDS のデフォルト無効化
-- 旧(11以前)
CREATE TABLE old_table (
id INTEGER
) WITH OIDS;
-- 新(12以降)- OIDSは非推奨
CREATE TABLE new_table (
id INTEGER,
custom_oid UUID DEFAULT gen_random_uuid() -- 代替案
);
他データベースとの互換性
MySQLからPostgreSQLへの移行
主な違いと変換方法:
-- ===== データ型の違い =====
-- MySQL
-- CREATE TABLE users (
-- id INT AUTO_INCREMENT PRIMARY KEY,
-- name VARCHAR(255),
-- created_at DATETIME,
-- is_active TINYINT(1)
-- );
-- PostgreSQL変換後
CREATE TABLE users (
id SERIAL PRIMARY KEY, -- AUTO_INCREMENT → SERIAL
name VARCHAR(255),
created_at TIMESTAMP, -- DATETIME → TIMESTAMP
is_active BOOLEAN -- TINYINT(1) → BOOLEAN
);
-- ===== 関数の違い =====
-- MySQL: IFNULL, ISNULL
-- SELECT IFNULL(column, 'default');
-- PostgreSQL: COALESCE
SELECT COALESCE(column, 'default');
-- MySQL: DATE_FORMAT
-- SELECT DATE_FORMAT(created_at, '%Y-%m-%d');
-- PostgreSQL: TO_CHAR
SELECT TO_CHAR(created_at, 'YYYY-MM-DD');
-- ===== 文字列結合 =====
-- MySQL
-- SELECT CONCAT('Hello', ' ', 'World');
-- PostgreSQL
SELECT 'Hello' || ' ' || 'World';
-- または
SELECT CONCAT('Hello', ' ', 'World'); -- 9.1以降
-- ===== LIMIT句の違い =====
-- MySQL
-- SELECT * FROM users LIMIT 5, 10; -- 6番目から10件
-- PostgreSQL
SELECT * FROM users LIMIT 10 OFFSET 5;
OracleからPostgreSQLへの移行
-- ===== シーケンスの違い =====
-- Oracle
-- SELECT seq_name.NEXTVAL FROM DUAL;
-- PostgreSQL
SELECT NEXTVAL('seq_name');
-- ===== ROWNUM の代替 =====
-- Oracle
-- SELECT * FROM users WHERE ROWNUM <= 10;
-- PostgreSQL
SELECT * FROM users LIMIT 10;
-- または ROW_NUMBER() を使用
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn
FROM users
) t WHERE rn <= 10;
-- ===== NVL関数の代替 =====
-- Oracle
-- SELECT NVL(column, 'default') FROM table;
-- PostgreSQL
SELECT COALESCE(column, 'default') FROM table;
-- ===== 階層クエリの変換 =====
-- Oracle: CONNECT BY
-- SELECT * FROM employees
-- START WITH manager_id IS NULL
-- CONNECT BY PRIOR employee_id = manager_id;
-- PostgreSQL: WITH RECURSIVE
WITH RECURSIVE emp_hierarchy AS (
-- アンカーメンバー
SELECT * FROM employees WHERE manager_id IS NULL
UNION ALL
-- 再帰メンバー
SELECT e.* FROM employees e
JOIN emp_hierarchy h ON e.manager_id = h.employee_id
)
SELECT * FROM emp_hierarchy;
SQL Serverからの移行
-- ===== TOP句の変換 =====
-- SQL Server
-- SELECT TOP 10 * FROM users;
-- PostgreSQL
SELECT * FROM users LIMIT 10;
-- ===== IDENTITY の変換 =====
-- SQL Server
-- CREATE TABLE users (
-- id INT IDENTITY(1,1) PRIMARY KEY
-- );
-- PostgreSQL
CREATE TABLE users (
id SERIAL PRIMARY KEY
);
-- または(PostgreSQL 10以降)
CREATE TABLE users (
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
);
-- ===== GETDATE() の変換 =====
-- SQL Server
-- SELECT GETDATE();
-- PostgreSQL
SELECT CURRENT_TIMESTAMP;
-- または
SELECT NOW();
アプリケーションとの互換性

JDBC/ODBCドライバーの互換性
// JDBCドライバーのバージョン対応表
/*
PostgreSQL Version | JDBC Driver Version | Java Version
-------------------|--------------------|--------------
16 | 42.6.0+ | 8+
15 | 42.5.0+ | 8+
14 | 42.3.0+ | 8+
13 | 42.2.19+ | 8+
12 | 42.2.11+ | 8+
*/
// 接続文字列の例
String url = "jdbc:postgresql://localhost:5432/mydb";
Properties props = new Properties();
props.setProperty("user", "postgres");
props.setProperty("password", "password");
props.setProperty("ssl", "false");
// バージョン互換性の設定
props.setProperty("preferQueryMode", "simple"); // 互換性重視
props.setProperty("assumeMinServerVersion", "12"); // 最小バージョン想定
言語別接続ライブラリの互換性
Python (psycopg2/psycopg3):
# psycopg2(安定版・互換性重視)
import psycopg2
conn = psycopg2.connect(
host="localhost",
database="mydb",
user="postgres",
password="password",
options="-c search_path=public"
)
# psycopg3(最新機能)
import psycopg
conn = psycopg.connect(
"postgresql://postgres:password@localhost/mydb",
row_factory=psycopg.rows.dict_row # 辞書形式で結果取得
)
Node.js (pg):
// node-postgres互換性設定
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
database: 'mydb',
user: 'postgres',
password: 'password',
port: 5432,
// 互換性設定
parseInputDatesAsUTC: true,
ssl: false,
statement_timeout: 30000,
query_timeout: 30000
});
移行前の互換性チェック
互換性診断スクリプト
-- 包括的な互換性チェック関数
CREATE OR REPLACE FUNCTION compatibility_check(
target_version TEXT DEFAULT '15'
) RETURNS TABLE (
カテゴリ TEXT,
項目 TEXT,
現状 TEXT,
対応必要 BOOLEAN
) AS $$
DECLARE
current_ver INTEGER;
target_ver INTEGER;
BEGIN
-- バージョン番号を取得
current_ver := current_setting('server_version_num')::INTEGER;
target_ver := (target_version || '0000')::INTEGER;
-- 非推奨機能のチェック
RETURN QUERY
SELECT
'非推奨機能',
'OID列の使用',
COUNT(*)::TEXT || '個のテーブル',
COUNT(*) > 0
FROM pg_class
WHERE relhasoids = true
AND relkind = 'r';
-- 削除された関数のチェック
RETURN QUERY
SELECT
'削除された関数',
'pg_stat_statements_reset()',
CASE WHEN EXISTS (
SELECT 1 FROM pg_proc WHERE proname = 'pg_stat_statements_reset'
) THEN '使用中' ELSE '未使用' END,
false;
-- エンコーディングのチェック
RETURN QUERY
SELECT
'エンコーディング',
'データベースエンコーディング',
current_setting('server_encoding'),
current_setting('server_encoding') NOT IN ('UTF8', 'EUC_JP');
-- 拡張機能のチェック
RETURN QUERY
SELECT
'拡張機能',
extname || ' ' || extversion,
CASE
WHEN extversion < default_version
THEN '要アップデート'
ELSE '最新'
END,
extversion < default_version
FROM pg_extension
JOIN pg_available_extensions ON extname = name
WHERE extname NOT IN ('plpgsql');
END;
$$ LANGUAGE plpgsql;
-- 実行
SELECT * FROM compatibility_check('15');
データ型の互換性確認
-- カスタムデータ型と非標準型の検出
SELECT
n.nspname AS スキーマ,
t.typname AS データ型,
t.typtype AS 種類,
CASE t.typtype
WHEN 'b' THEN '基本型'
WHEN 'c' THEN '複合型'
WHEN 'd' THEN 'ドメイン'
WHEN 'e' THEN '列挙型'
WHEN 'r' THEN 'レンジ型'
END AS 型の種類,
COUNT(a.attname) AS 使用カラム数
FROM pg_type t
JOIN pg_namespace n ON t.typnamespace = n.oid
LEFT JOIN pg_attribute a ON a.atttypid = t.oid
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
AND t.typtype != 'p' -- 疑似型を除外
GROUP BY n.nspname, t.typname, t.typtype
ORDER BY COUNT(a.attname) DESC;
アップグレード戦略
pg_upgradeを使った高速アップグレード
# 1. 新バージョンのインストール
sudo apt-get install postgresql-15
# 2. 新クラスタの初期化
/usr/lib/postgresql/15/bin/initdb -D /var/lib/postgresql/15/data
# 3. 互換性チェック
/usr/lib/postgresql/15/bin/pg_upgrade \
--old-datadir=/var/lib/postgresql/13/data \
--new-datadir=/var/lib/postgresql/15/data \
--old-bindir=/usr/lib/postgresql/13/bin \
--new-bindir=/usr/lib/postgresql/15/bin \
--check
# 4. アップグレード実行
/usr/lib/postgresql/15/bin/pg_upgrade \
--old-datadir=/var/lib/postgresql/13/data \
--new-datadir=/var/lib/postgresql/15/data \
--old-bindir=/usr/lib/postgresql/13/bin \
--new-bindir=/usr/lib/postgresql/15/bin \
--link # ハードリンクで高速化
論理レプリケーションを使った無停止アップグレード
-- 旧バージョン(パブリッシャー)側
-- 1. wal_levelを設定
ALTER SYSTEM SET wal_level = 'logical';
ALTER SYSTEM SET max_replication_slots = 10;
ALTER SYSTEM SET max_wal_senders = 10;
-- 2. 再起動後、パブリケーション作成
CREATE PUBLICATION upgrade_pub FOR ALL TABLES;
-- 新バージョン(サブスクライバー)側
-- 3. サブスクリプション作成
CREATE SUBSCRIPTION upgrade_sub
CONNECTION 'host=old_server dbname=mydb user=replicator'
PUBLICATION upgrade_pub;
-- 4. レプリケーション状態確認
SELECT * FROM pg_stat_subscription;
-- 5. 切り替え準備完了後
ALTER SUBSCRIPTION upgrade_sub DISABLE;
ALTER SUBSCRIPTION upgrade_sub SET (slot_name = NONE);
DROP SUBSCRIPTION upgrade_sub;
互換性テストの自動化

回帰テストスイート
-- テストケース管理テーブル
CREATE TABLE compatibility_tests (
id SERIAL PRIMARY KEY,
test_name VARCHAR(100),
test_sql TEXT,
expected_result TEXT,
min_version INTEGER,
max_version INTEGER
);
-- テスト実行関数
CREATE OR REPLACE FUNCTION run_compatibility_tests()
RETURNS TABLE (
test_name VARCHAR(100),
status TEXT,
message TEXT
) AS $$
DECLARE
test_record RECORD;
actual_result TEXT;
current_ver INTEGER;
BEGIN
current_ver := current_setting('server_version_num')::INTEGER;
FOR test_record IN
SELECT * FROM compatibility_tests
WHERE min_version <= current_ver
AND (max_version IS NULL OR max_version >= current_ver)
LOOP
BEGIN
EXECUTE test_record.test_sql INTO actual_result;
IF actual_result = test_record.expected_result THEN
RETURN QUERY SELECT
test_record.test_name,
'PASS'::TEXT,
'テスト成功'::TEXT;
ELSE
RETURN QUERY SELECT
test_record.test_name,
'FAIL'::TEXT,
format('期待値: %s, 実際: %s',
test_record.expected_result, actual_result);
END IF;
EXCEPTION WHEN OTHERS THEN
RETURN QUERY SELECT
test_record.test_name,
'ERROR'::TEXT,
SQLERRM;
END;
END LOOP;
END;
$$ LANGUAGE plpgsql;
-- テストケース登録例
INSERT INTO compatibility_tests (test_name, test_sql, expected_result, min_version)
VALUES
('JSONB演算子', 'SELECT ''{"a":1}''::jsonb ? ''a''', 'true', 90400),
('GENERATED列', 'SELECT 1 FROM pg_attribute WHERE attgenerated != ''''', '1', 120000),
('MERGE文', 'SELECT 1 WHERE EXISTS (SELECT 1 FROM pg_proc WHERE proname = ''merge'')', '1', 150000);
互換性問題の回避策
条件付きSQL実行
-- バージョンに応じた条件分岐
DO $$
DECLARE
ver INTEGER;
BEGIN
ver := current_setting('server_version_num')::INTEGER;
IF ver >= 120000 THEN
-- PostgreSQL 12以降の機能を使用
EXECUTE 'ALTER TABLE users ADD COLUMN full_name TEXT GENERATED ALWAYS AS (first_name || '' '' || last_name) STORED';
ELSE
-- 古いバージョンでの代替実装
EXECUTE 'ALTER TABLE users ADD COLUMN full_name TEXT';
EXECUTE 'CREATE TRIGGER update_full_name BEFORE INSERT OR UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_full_name_func()';
END IF;
END $$;
互換性レイヤーの実装
-- 他DBの関数をエミュレート
CREATE OR REPLACE FUNCTION IFNULL(anyelement, anyelement)
RETURNS anyelement AS $$
SELECT COALESCE($1, $2);
$$ LANGUAGE SQL IMMUTABLE;
CREATE OR REPLACE FUNCTION FROM_UNIXTIME(BIGINT)
RETURNS TIMESTAMP AS $$
SELECT TO_TIMESTAMP($1);
$$ LANGUAGE SQL IMMUTABLE;
-- MySQL互換のGROUP_CONCAT
CREATE OR REPLACE FUNCTION GROUP_CONCAT(TEXT)
RETURNS TEXT AS $$
SELECT STRING_AGG($1, ',');
$$ LANGUAGE SQL;
まとめ:互換性を制してスムーズな移行を
PostgreSQLの互換性について、重要ポイントをまとめます:
バージョン間互換性:
- 📊 マイナーバージョン – 完全互換で安心
- 🔄 メジャーバージョン – 事前チェック必須
- 📝 リリースノート – 必ず確認
他DB移行のポイント:
- データ型の違いを理解
- 関数名の変換規則を把握
- SQL文法の差異に注意
アップグレード戦略:
- pg_upgradeで高速移行
- 論理レプリケーションで無停止
- 必ず事前にテスト環境で検証
成功の秘訣:
- 互換性チェックツールの活用
- 段階的な移行計画
- ロールバック手順の準備
互換性を正しく理解し、適切な準備をすれば、PostgreSQLの移行やアップグレードは怖くありません。 この記事で紹介した方法を活用して、安全で確実な移行を実現してください!
コメント