📐
概念 #データ設計 #正規化 #スキーマ設計 #PostgreSQL #データモデリング #DDIA 📚 データ志向アプリケーション設計(DDIA)

正規化理論(1NF〜BCNF)

なぜテーブルをそう設計するかの理論的根拠。1NF・2NF・3NF・BCNFの定義と、意図的な非正規化を選ぶ判断基準を理解する

定義

正規化(Normalization):データの冗長性を排除し、更新異常(Update Anomaly)を防ぐためにテーブルを分割する設計プロセス。E.F. Coddが1970年代に定義した理論に基づく。

更新異常とは何か

正規化の「なぜ」を理解する出発点。

非正規化テーブル(問題を含む):
┌──────────┬──────────┬──────────────┬─────────┬──────────────┐
│ order_id │ customer │ customer_addr│ product │ product_price│
├──────────┼──────────┼──────────────┼─────────┼──────────────┤
│    1     │  Alice   │ 東京都新宿区  │  Apple  │     100      │
│    2     │  Alice   │ 東京都新宿区  │ Banana  │      80      │
│    3     │  Bob     │ 大阪府大阪市  │  Apple  │     100      │
└──────────┴──────────┴──────────────┴─────────┴──────────────┘

挿入異常: Aliceが注文していない商品のデータを登録できない
削除異常: order_id=3を削除するとBobの情報も失われる
更新異常: Appleの価格を変えると全行を更新しなければならない
          → 更新漏れで価格の不一致が生じる

第1正規形(1NF)

ルール:各セルには単一の原子的な値しか持たない。繰り返しグループを排除する。

❌ 1NF違反(配列・繰り返しグループ):
┌──────────┬──────────┬─────────────────────────────┐
│ order_id │ customer │ products                    │
├──────────┼──────────┼─────────────────────────────┤
│    1     │  Alice   │ Apple(100), Banana(80)      │  ← 複数値
└──────────┴──────────┴─────────────────────────────┘

✅ 1NF準拠(繰り返しを行に展開):
┌──────────┬──────────┬─────────┬───────┐
│ order_id │ customer │ product │ price │
├──────────┼──────────┼─────────┼───────┤
│    1     │  Alice   │  Apple  │  100  │
│    1     │  Alice   │ Banana  │   80  │
└──────────┴──────────┴─────────┴───────┘

第2正規形(2NF)

前提:1NFを満たしていること。
ルール:主キーの一部に対する部分関数従属を排除する(複合主キーの場合に発生)。

❌ 2NF違反:
主キー = (order_id, product)
                ↓ 部分従属
customer → order_id だけで決まる(productは不要)
price    → product  だけで決まる(order_idは不要)

┌──────────┬─────────┬──────────┬───────┐
│ order_id │ product │ customer │ price │  ← customerとpriceが部分従属
└──────────┴─────────┴──────────┴───────┘

✅ 2NF準拠(部分従属を別テーブルへ):
orders:
┌──────────┬──────────┐
│ order_id │ customer │
└──────────┴──────────┘

products:
┌─────────┬───────┐
│ product │ price │
└─────────┴───────┘

order_items:
┌──────────┬─────────┐
│ order_id │ product │  ← 複合主キー(全体に従属する項目のみ)
└──────────┴─────────┘

第3正規形(3NF)

前提:2NFを満たしていること。
ルール:推移的関数従属を排除する(主キー → 非キー → 非キー という連鎖)。

❌ 3NF違反:
┌──────────┬──────────┬─────────────┬──────────┐
│ order_id │ customer │ customer_zip│ city     │
└──────────┴──────────┴─────────────┴──────────┘
  主キー → customer → customer_zip → city
                       ↑推移的従属:customer_zipからcityが決まる

✅ 3NF準拠(推移従属を別テーブルへ):
orders:
┌──────────┬──────────┬─────────────┐
│ order_id │ customer │ customer_zip│
└──────────┴──────────┴─────────────┘

zip_codes:
┌─────────────┬──────────┐
│ customer_zip│ city     │
└─────────────┴──────────┘

一般的なルール(Armstrong の公理の直感版)
「非キー属性は主キーだけに従属し、他の非キー属性には従属しない」

ボイス-コッド正規形(BCNF)

前提:3NFを満たしていること。
ルール:すべての非自明な関数従属において、決定項が候補キーであること。3NFより厳格。

❌ BCNF違反の例:
学生は複数の科目を受講し、科目ごとに担当教員が1人
1人の教員は1つの科目だけ担当する

(学生, 科目) → 教員  ← 候補キー
(学生, 教員) → 科目  ← 候補キー
教員 → 科目          ← 決定項が候補キーでない!(BCNF違反)

┌────────┬────────┬────────┐
│ student│ subject│ teacher│
├────────┼────────┼────────┤
│ Alice  │ Math   │ Yamada │
│ Alice  │ English│ Tanaka │
│ Bob    │ Math   │ Yamada │  ← Mathの教員はYamadaという情報が重複
└────────┴────────┴────────┘

✅ BCNF準拠:
enrollments:
┌────────┬────────┐
│ student│ teacher│
└────────┴────────┘

teacher_subjects:
┌────────┬────────┐
│ teacher│ subject│
└────────┴────────┘

3NF ⊃ BCNF(BCNFは3NFより強い条件)。ただしBCNFは分解後に元のFDが保存されないことがある。

正規形のまとめ

正規形排除する問題チェックポイント
1NF繰り返しグループ・複数値各セルに1つの値だけか
2NF部分関数従属非キー属性が主キー全体に従属しているか
3NF推移的関数従属非キー属性が別の非キー属性に従属していないか
BCNF3NFより厳格な従属すべての決定項が候補キーか

意図的な非正規化

正規化が「常に正しい」わけではない。パフォーマンスのために非正規化を選ぶ場面がある。

非正規化が有効なケース:

1. 読み取り専用の集計(DWH・ダッシュボード)
   正規化されたOLTPデータをDWHでスタースキーマ(非正規化)に変換

2. 頻繁に参照するカラムのコピー(キャッシュとして)
   orders.customer_name ← customersテーブルからコピー
   JOINなしで注文一覧を表示できる
   → ただし顧客名変更時に更新が必要

3. カウンターカラム
   posts.comment_count ← commentsの集計値を事前計算
   SELECT COUNT(*) のたびのフルスキャンを回避

非正規化のトレードオフ:
  書き込みの複雑さ増加 ↔ 読み取りの高速化
  更新異常のリスク ↔ JOINコストの削減

実務的な判断基準

正規化を優先する:
  - OLTP(書き込みが多い)
  - データ整合性が最重要
  - スキーマが頻繁に変わる

非正規化を選ぶ:
  - OLAP(読み取りが多い、書き込みは少ない)
  - クエリパフォーマンスが優先
  - 読み取りパターンが固定されている
  - マテリアライズドビューやdbtで管理できる

関連概念

出典・参考文献

  • E.F. Codd, “A Relational Model of Data for Large Shared Data Banks” (1970)
  • C.J. Date, An Introduction to Database Systems (2003)
  • Ramakrishnan & Gehrke, Database Management Systems (2002)
  1. 1. 🗄️データ志向アプリケーション設計:概要
  2. 2. 🧩データモデルとクエリ言語
  3. 3. 💾ストレージエンジンとインデックス
  4. 4. 🔁レプリケーション
  5. 5. 🍕パーティショニング(シャーディング)
  6. 6. 🔒トランザクションとACID
  7. 7. 分散システムの本質的な問題
  8. 8. 🤝一貫性と分散合意
  9. 9. 📦バッチ処理
  10. 10. 🌊ストリーム処理
  11. 11. 📋エンコーディングとスキーマ進化
  12. 12. 🔗Sagaパターンと分散トランザクション
  13. 13. 🏗️データシステムの統合設計
  14. 14. 📸MVCC(多版型同時実行制御)
  15. 15. 📊列指向ストレージとOLAP設計
  16. 16. 🕰️ベクタークロックと因果順序
  17. 17. 🔀CRDT(競合なし複製データ型)
  18. 18. 🔍クエリオプティマイザーと実行計画
  19. 19. キャッシュ戦略とRedis設計
  20. 20. 🔎全文検索と転置インデックス
  21. 21. 🌐NewSQL(分散ACIDデータベース)
  22. 22. 📝WALと論理レプリケーション
  23. 23. 🔌コネクションプーリング
  24. 24. 🚧ゼロダウンタイムマイグレーション
  25. 25. 🆔分散ID生成
  26. 26. 🔄N+1問題とDataLoaderパターン
  27. 27. 📈タイムシリーズDB
  28. 28. 🛡️Row Level Security(行レベルセキュリティ)
  29. 29. 📤Outboxパターン(トランザクショナルアウトボックス)
  30. 30. 💾DBバックアップとPITR
  31. 31. ⚠️データベース設計アンチパターン
  32. 32. 🕸️グラフDB深掘り
  33. 33. 🔋バックプレッシャーとサーキットブレーカー
  34. 34. 🔵コンシステントハッシング
  35. 35. 📋マテリアライズドビュー
  36. 36. 📡DBモニタリングとオブザーバビリティ
  37. 37. 🔐データプライバシーとCrypto-Shredding
  38. 38. ✂️垂直分割(Vertical Partitioning)
  39. 39. 🪟ウィンドウ関数
  40. 40. 🧲ベクトルDBとpgvector
  41. 41. 🔧dbtとデータ変換パイプライン
  42. 42. 📬ジョブキューの設計
  43. 43. 📐正規化理論(1NF〜BCNF)
  44. 44. ☁️クラウドDBサービスの設計思想と選択基準
  45. 45. 🗺️地理空間データとPostGIS
  46. 46. 🔑DBセキュリティと権限管理
  47. 47. 🏔️Lakehouse(Apache Iceberg / Delta Lake)
  48. 48. 📜データコントラクト
  49. 49. 🔭OpenTelemetryとDBトレーシング

出典: E.F. Codd, 'A Relational Model of Data for Large Shared Data Banks' (1970)