Day7 後半のゴール
「“動けばOK”から、“ちゃんと移植できたと言える状態”へ」
前半では、MySQLのDDLをPostgreSQL向けに書き換える「設計レベルの移植」をやりました。
後半では、それを実際に「データ込みで移す」ときに何を意識するか、どこでハマりやすいかを整理していきます。
ここでのゴールはこうです。
スキーマ移植とデータ移行を、頭の中で別物として整理できる。
MySQL→PostgreSQLで、型・制約・NULL・文字コードあたりの“地雷ポイント”を説明できる。
「移植後に何を確認すれば“ちゃんと移植できた”と言えるか」をイメージできる。
スキーマ移植とデータ移行を分けて考える
「まず“器”を作り、そのあと“中身”を流し込む」
移植作業は、大きく分けると2段階です。
スキーマ移植(テーブル定義・制約・インデックスをPostgreSQLで再現する)
データ移行(MySQLに入っている行をPostgreSQLにコピーする)
前半でやったのは、ほぼスキーマ移植の部分でした。
後半では、「その器にどうやってデータを入れるか」「入れたあと何を見るか」を考えます。
イメージとしては、こうです。
MySQL側で SELECT ... してデータを取り出す(CSVやSQL INSERT文など)。
PostgreSQL側で、さきほど作ったテーブルに対して INSERT していく。
実務ではツールを使うことが多いですが、学習としては「何が起きているか」を理解しておくのが大事です。
型の差分がデータ移行にどう効いてくるか
BOOLEAN・TIMESTAMPTZ・TEXTまわりを具体的に見る
前半で型をこう変えました。
TINYINT(1) → BOOLEAN
DATETIME → TIMESTAMPTZ
VARCHAR(255) → TEXT or VARCHAR(n)
ここで、「MySQL側の値が、PostgreSQL側でどう解釈されるか」をイメージしておきます。
MySQLの is_active TINYINT(1) に 0/1 が入っている場合、
PostgreSQLの BOOLEAN に入れるときは、0→false、1→true という対応になります。
もしCSVで移すなら、MySQL側であらかじめこう変換しておくイメージです。
SELECT
email,
name,
CASE WHEN is_active = 1 THEN true ELSE false END AS is_active,
created_at
FROM users;
SQLDATETIME → TIMESTAMPTZ も同様で、「文字列としての ‘2024-01-01 10:00:00’」を、PostgreSQL側でTIMESTAMPTZとして解釈させます。
タイムゾーンをどう扱うか(それは日本時間なのか、UTCなのか)は、アプリ全体の設計の話になるので、ここでは「少なくとも型としてはTIMESTAMPTZに載せる」と押さえておけばOKです。
TEXT / VARCHAR まわりは、基本的にそのまま入りますが、
PostgreSQLでは文字コードがUTF-8前提なので、「MySQL側もUTF-8で動いているか」は一応意識しておきたいポイントです。
制約が“移行時のテスト”として働く
「おかしなデータがあれば、その場でバレる」
Day5でやったように、PostgreSQLの制約はかなり真面目に効きます。
これは、データ移行のときには「自動テスト」として働きます。
例えば、PostgreSQL側のusersテーブルをこう作っていたとします。
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name VARCHAR(50) NOT NULL,
is_active BOOLEAN NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
SQLここにMySQLからデータを流し込むとき、もしMySQL側に「emailがNULLの行」や「重複したemail」が紛れ込んでいたら、
PostgreSQLはINSERT時にエラーを返します。
NOT NULL制約違反
UNIQUE制約違反
これは一見「移行が止まってつらい」ように見えますが、実はかなりありがたい挙動です。
「今まで気づいていなかったデータの不整合」が、その場で浮き彫りになるからです。
移行時の心構えとしては、こうです。
エラーが出たら、「PostgreSQLが厳しすぎる」のではなく、「元データに問題がある可能性が高い」と疑う。
必要なら、MySQL側でデータをクレンジング(修正・削除)してから、もう一度移行する。
制約を「邪魔者」と見るのではなく、「移行時に一緒に品質チェックをしてくれる相棒」として見ると、気持ちが楽になります。
外部キーの厳密性が移行順序に影響する
「親テーブル→子テーブルの順で入れる」
外部キーがあると、「どの順番でデータを入れるか」が重要になります。
さきほどの例だと、posts.user_id は users.id を参照しています。
CREATE TABLE posts (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
title VARCHAR(200) NOT NULL,
body TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
SQLこの状態で、もし先にpostsのデータだけをINSERTしようとすると、
PostgreSQLは「そのuser_idのユーザーがまだいない」と言ってエラーにします。
つまり、移行の順番はこうなります。
先に users のデータを全部入れる。
そのあとで posts のデータを入れる。
さらに、「途中で外部キーを一時的に無効化して一気に入れて、あとで有効化する」というテクニックもありますが、
学習段階では「親→子の順で入れる」という基本だけ押さえておけば十分です。
ここでも、「外部キーは“移行時の整合性チェック”として働いてくれる」と捉えると、
エラーが出ても「ありがとう、どこがおかしいか教えてくれたんだな」と思えるようになります。
移植後に何を確認するか
「“同じクエリを投げて、同じ意味の結果が返るか”を見る」
移植が終わったあと、「ちゃんと移植できた」と言えるためには、何を見ればいいか。
完璧を目指すとキリがないので、まずはシンプルにこう考えます。
MySQLでよく使っていたクエリを、PostgreSQLに対しても投げてみる。
結果の件数・内容が、期待通りかをざっくり確認する。
例えば、こんなクエリたちです。
ユーザー数を数える:
SELECT COUNT(*) FROM users;
SQLアクティブユーザー数を数える(PostgreSQL版ではBOOLEANになっていることに注目):
SELECT COUNT(*) FROM users WHERE is_active;
SQL特定ユーザーの投稿件数:
SELECT COUNT(*) FROM posts WHERE user_id = 123;
SQL最近7日間の投稿件数:
SELECT COUNT(*) FROM posts
WHERE created_at >= NOW() - INTERVAL '7 days';
SQLこれらが「MySQL時代と同じ意味の結果になっているか」を見ることで、
型変換・制約・インデックスが大きくズレていないかをざっくりチェックできます。
本気の移行ではもっと細かい検証が必要ですが、
学習としては「よく使うクエリがちゃんと動くか」を見るだけでも、かなり良い練習になります。
「移植」は“ただの引っ越し”ではなく“設計のリファクタリング”
「PostgreSQLに来た意味を、ちゃんと回収する」
最後に、マインドセットの話を少しだけ。
MySQL → PostgreSQL の移植を、「方言の違いを直すだけ」と捉えると、
どうしても「動けばOK」で終わってしまいます。
でも、ここまでDay1〜Day6で見てきたように、PostgreSQLにはこういう特徴があります。
型が真面目(BOOLEAN・TIMESTAMPTZなど)
制約が真面目(CHECK・UNIQUE・外部キーがちゃんと効く)
インデックスとプランナが賢い
せっかくPostgreSQLに来るなら、「その真面目さを活かす」方向に設計を少し変えた方が、長い目で見て得です。
TINYINT(1) をそのままINTEGERにせず、BOOLEANにする。
DATETIMEをそのままTIMESTAMPにせず、タイムゾーンも含めて考える。
“なんとなくVARCHAR(255)”をやめて、「どこまでをDBに守らせるか」を考える。
Day7の復習課題は、「移植」という作業を通して、
自分のDB設計のクセを一段アップデートするチャンスだと思ってもらえると嬉しいです。
Day7 後半のまとめ
MySQL→PostgreSQLの移植は、「スキーマ移植(型・制約・インデックスの書き換え)」と「データ移行(実際の行を流し込む)」に分けて考え、TINYINT(1)→BOOLEAN、DATETIME→TIMESTAMPTZ、AUTO_INCREMENT→SERIAL/IDENTITYといった型の差分を意識しながら、制約や外部キーを“移行時の自動テスト”として活かすのがポイントになる。
外部キーがあることで「親テーブル→子テーブルの順でデータを入れる」必要が出てきたり、UNIQUEやNOT NULLに引っかかることで「元データの不整合」があぶり出されたりするが、それを「PostgreSQLが厳しすぎる」と見るのではなく、「このタイミングでデータ品質を上げるチャンス」と捉え、移行後にはMySQL時代によく使っていたクエリをPostgreSQLに投げて件数や結果を確認しつつ、「ただの引っ越し」ではなく「PostgreSQLの真面目さを取り込んだ設計リファクタリング」としてDay7を締める、というのが今回の着地点になる。
