「正規化」は教科書のためじゃない。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`制約を「お守り」のように使いこなせば、複雑なアプリケーションコードを書かなくても、データが勝手に綺麗な状態を保ってくれるようになります。
最後に一つだけ。
正規化や制約はあくまで「手段」です。一番大切なのは、今作っているデータが「どんなビジネスのルールを表現しているか」を理解すること。
設計に行き詰まったら、一度コードから離れて、ホワイトボードにテーブルの関係図を書いてみて。そこにある「自然な関係性」こそが、最高のデータベース設計への近道ですよ。
また次回の記事で、より深いインデックス設計の話でもしましょうか。現場からは以上です!
コメント