【実務・中級編】 正規化の段階と設計原則 – PostgreSQL

「正規化」は教科書のためじゃない。PostgreSQLで設計の呼吸を整える話

現場で設計レビューをしていると、「正規化ってどこまでやればいいんですか?」という質問をよく受けます。

教科書的には第1正規形から第3正規形、さらにその先……と進むのが定石だけど、実際の開発現場では「パフォーマンス」と「整合性」のバランスを取るのが一番の腕の見せ所。今日は、PostgreSQLの機能をフル活用して、現場で使える「息の長いデータモデル」を作るためのヒントを書いてみます。

—

正規化の「勘所」:やりすぎず、やらなすぎない

正規化の目的は、一言で言えば「データの重複を削ぎ落とし、更新時の不整合を防ぐこと」です。

  • 第1正規形: 「一つのセルには一つの値」という基本。配列型(PostgreSQLなら`TEXT[]`など)は便利だけど、集計や検索で苦労するから、まずはテーブルを分けることを考えよう。
  • 第2正規形: 主キーの一部に依存する項目を切り出す。
  • 第3正規形: 主キー以外の項目に依存する項目(推移的関数従属性)を切り出す。

現場の先輩からのアドバイス:
「第3正規形までやれば十分」というのが今の定石です。BCNF(ボイス・コッド正規形)まで追い込むケースは稀。もしBCNFまでやりたくなるなら、それは設計が複雑になりすぎているサインかも。逆に、読み取り速度が致命的なら、あえて第2正規形で止めて「非正規化」する勇気も必要です。でも、それは「正しく正規化した後」に行う最適化であるべきだということは忘れないでね。

—

PostgreSQLの「制約(Constraints)」こそが最強の守護神

正規化で構造を整えたら、次はPostgreSQLの制約機能で「データの嘘」を防ぎます。アプリケーション側でバリデーションを書くのもいいけど、データベース自身がクリーンであることに勝る信頼性はありません。

1. CHECK制約:データの範囲を厳格にする

例えば、年齢やステータスコード。アプリケーションがバグっても、DB側で弾くのが鉄則。

CREATE TABLE users (
id SERIAL PRIMARY KEY,
age INT CHECK (age >= 0 AND age < 150), status VARCHAR(10) CHECK (status IN ('active', 'inactive', 'pending')) );

2. UNIQUE制約:重複を許さない

「メールアドレス」や「注文番号」など、ビジネスロジック的に重複が許されないものは必ずUNIQUE制約を。特に、`NULL`をどう扱うかはPostgreSQLの挙動(NULL同士は異なるとみなされる)をしっかり把握しておこう。

3. FOREIGN KEY:データの寿命を管理する

正規化でテーブルを分けると、必然的に外部キーが増えます。面倒くさがらずに`ON DELETE CASCADE`や`ON DELETE RESTRICT`を適切に設定して、データの整合性を担保しよう。

CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INT REFERENCES users(id) ON DELETE RESTRICT
);

※`RESTRICT`にしておくと、ユーザーを削除しようとしたときに「まだ注文が残ってるよ!」とDBが教えてくれる。これがバグ防止の第一歩。

—

「綺麗な設計」は、未来の自分へのプレゼント

ここまでやると、「設計が重いな」と感じるかもしれない。でも考えてみて。運用中に「あるはずのデータがない」「矛盾したレコードが混ざった」というトラブルシュートに追われる夜と、少し丁寧にDBを設計して、安心して夜眠れる夜。どっちがいいかな?

PostgreSQLは非常に強力な武器です。`CHECK`制約や`UNIQUE`制約を「お守り」のように使いこなせば、複雑なアプリケーションコードを書かなくても、データが勝手に綺麗な状態を保ってくれるようになります。

最後に一つだけ。
正規化や制約はあくまで「手段」です。一番大切なのは、今作っているデータが「どんなビジネスのルールを表現しているか」を理解すること。

設計に行き詰まったら、一度コードから離れて、ホワイトボードにテーブルの関係図を書いてみて。そこにある「自然な関係性」こそが、最高のデータベース設計への近道ですよ。

また次回の記事で、より深いインデックス設計の話でもしましょうか。現場からは以上です!

コメント

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