PostgreSQL | SQLite+MySQL経験者向け、30日で習得するPostgreSQL:設計とパフォーマンス - Day21 正規化と設計

SQL PostgreSQL
スポンサーリンク

Day21 後半のゴール

「“きれいな正規化”から“現場で回る設計”に落とし込む」

前半で、「正規化の原則」と「履歴・冗長性・インデックス」の話まで来ました。
後半では、もう一歩踏み込んで「実務でよく悩むポイント」を具体例で整理します。

どこまで正規化して、どこからは“割り切って持つか”。
JSON やタグのような“柔らかい情報”をどこに置くか。
将来の変更やセキュリティ・監査まで見据えて、どう設計するか。

ここを一緒に言語化していきます。


どこまで正規化するかの判断軸

「“更新頻度”と“意味の独立性”で考える」

実務で「この情報、別テーブルに分けるべき?」と迷ったとき、
判断軸として使いやすいのがこの2つです。

更新頻度
意味の独立性

更新頻度で考える

例えば、ユーザーの「名前」と「メールアドレス」と「プロフィール文」があるとします。

名前・メールアドレス
→ ログインや通知に使う。頻繁には変わらないが、重要度は高い。

プロフィール文
→ ユーザーが気分で変える。頻度は高いが、壊れても致命傷ではない。

この場合、全部 users テーブルに持ってもいいし、
プロフィールだけ別テーブル(user_profiles)に分ける選択もあります。

プロフィールを分けるメリットは、

プロフィールだけを更新する処理が多いなら、
users テーブルのロックや更新負荷を減らせる。

逆に、分けるデメリットは、

JOIN が増える。
アプリ側のコードが少し複雑になる。

「どちらが正解」ではなく、「どの更新がどのテーブルにどれだけ当たるか」をイメージして決める、という感覚が大事です。

意味の独立性で考える

例えば、「都道府県名」「国名」「通貨」などは、
ユーザーや注文とは別に“独立した意味”を持つことが多いです。

都道府県コード・名前を prefectures テーブルに分ける。
通貨コード・小数桁数を currencies テーブルに分ける。

こうしておくと、

マスタデータとして一元管理できる。
外部キー制約で「存在しない都道府県コードを入れない」ようにできる。

セキュリティ・監査の観点でも、「マスタの変更履歴を追う」「誰がいつ通貨設定を変えたか」を追いやすくなります。


“柔らかい情報”をどこに置くか

「正規カラム+JSONBのハイブリッド設計」

最近の実務でよく出てくるのが、「柔らかい情報」の扱いです。

ユーザーの任意属性(好み、設定、A/Bテストのバリアントなど)
外部APIレスポンスの一部
イベントログの追加情報

これらを全部カラムにすると、テーブル定義がすぐにパンパンになります。
かといって、全部 JSONB にすると、「何が必須で何が任意か」が分からなくなる。

そこでよく使うのが、「正規カラム+JSONB」のハイブリッドです。

例:ユーザー設定テーブル

CREATE TABLE user_settings (
  user_id   BIGINT PRIMARY KEY REFERENCES users(id),
  language  TEXT NOT NULL,
  timezone  TEXT NOT NULL,
  extras    JSONB NOT NULL DEFAULT '{}'::jsonb
);
SQL

language / timezone
→ アプリの動作に必須。カラムとして定義し、NOT NULL 制約をかける。

extras
→ 実験的なフラグや、特定の機能だけが使う追加設定を入れる。

こうしておくと、

「必須で守りたい情報」はスキーマで守れる。
「変わりやすい・増えやすい情報」は JSONB に逃がせる。

さらに、extras に対して GIN インデックスを張れば、
extras @> '{"beta_feature": true}' のような検索も現実的になります。

ここでのポイントは、「全部 JSON に逃げない」「全部カラムにもしない」というバランス感覚です。


将来の変更をどう見込むか

「“変わりにくい軸”と“変わりやすい軸”を分ける」

設計でよく効く考え方が、「変わりにくいもの」と「変わりやすいもの」を分けることです。

変わりにくいもの
→ ユーザーID、注文ID、商品ID、作成日時など。
→ これらは主キー・外部キー・インデックスの軸になりやすい。

変わりやすいもの
→ 表示名、説明文、タグ、フラグ類など。
→ UIやビジネスルールの変更で変わりやすい。

変わりにくいものは、テーブルの“骨格”としてしっかりカラムに持ち、
制約やインデックスをきちんと張る。

変わりやすいものは、

別テーブルに切り出す(例:product_descriptions)
JSONB に逃がす(例:extras)
履歴テーブルにする(例:product_price_history)

など、変更に耐えられる形にしておく。

セキュリティ的にも、「変わりにくいもの(ID・キー)」は厳しく守り、
「変わりやすいもの」は変更ログや監査の対象として扱う、という線引きがしやすくなります。


実務レベル設計の“チェックポイント”

「テーブルを作る前に、自分に問いかけてほしいこと」

新しいテーブルを設計するとき、最低限これだけは自分に聞いてほしい、という問いをまとめます。

このテーブルの“主語”は何か
→ users なのか、orders なのか、logs なのか。
→ 1テーブルに主語を増やしすぎていないか。

このテーブルの主キーは何か
→ 自然キーか、サロゲートキー(id)か。
→ 主キーで一意に決まらない情報を詰め込んでいないか。

どの情報が“現在の真実”で、どの情報が“過去の真実(履歴)”か
→ price と unit_price のように、意味を分けているか。

どのクエリが一番多く走るか
→ そのクエリの WHERE / JOIN に必要なインデックスは何か。

どの情報が“変わりにくい軸”で、どの情報が“変わりやすい装飾”か
→ 骨格と装飾を分けているか。

この問いに答えられるようになると、
「なんとなくテーブルを作る」から「意図を持って設計する」に一段上がれます。


セキュリティ・監査を意識した設計の一歩

「“変えていいもの”と“変えちゃいけないもの”をスキーマで分ける」

最後に、あなたがこれから実務に出たときに、
ちょっと差がつく視点を1つだけ。

設計の段階で、「変えていいもの」と「変えちゃいけないもの」を分けておくと、
セキュリティ・監査・障害対応が一気に楽になります。

変えちゃいけないものの例
→ 注文ID、注文日時、当時の価格(unit_price)、決済結果、監査ログ。

これらは、

NOT NULL
CHECK 制約
外部キー制約
UPDATE を基本禁止にして、INSERT だけで履歴を積む

といった形で、「スキーマで守る」ことができます。

変えていいものの例
→ 表示名、説明文、タグ、フラグ。

これらは、

別テーブルにして履歴を残す
JSONB にして柔軟に扱う

など、変更しやすさを優先して設計できます。

「どのカラムが“法律・契約・監査に絡むか”」を意識して設計できるようになると、
ただのDB設計者ではなく、「システム全体の信頼性を支えるエンジニア」に一歩近づきます。


Day21 後半のまとめ

実務レベル設計では、「正規化の原則」を土台にしつつも、更新頻度・意味の独立性・履歴要件・将来の変更を踏まえて、「どこまで分けるか」「どこから冗長に持つか」を決めていく必要があり、そのために「変わりにくい骨格(ID・キー・日時)」と「変わりやすい装飾(説明・タグ・フラグ)」を分ける発想が有効になる。
さらに、必須の情報はカラムと制約で守り、柔らかい情報は JSONB や別テーブルに逃がすハイブリッド設計を取りつつ、「どの値が現在の真実で、どの値が過去の真実か」「どのカラムは変えてよくて、どのカラムは変えちゃいけないか」をスキーマで表現しておくことが、正規化と実務をつなぐ Day21 後半の着地点になる。

タイトルとURLをコピーしました