STEP 11:BCNFとその先

🎯 STEP 11: BCNFとその先

ボイスコッド正規形と高度な正規化を理解し、いつ非正規化すべきかを学ぼう

📋 このステップで学ぶこと
  • ボイスコッド正規形(BCNF)とは何か
  • 第3正規形とBCNFの違い
  • 第4正規形、第5正規形の概要
  • 実務で使われる正規化レベル
  • いつ非正規化すべきか(戦略的な非正規化)

学習時間の目安: 2時間 | 前提知識: STEP 10修了

🎯 1. ボイスコッド正規形(BCNF)とは?

STEP 10からの続き:正規化のさらなる深化

STEP 8〜10で第1正規形から第3正規形まで学びました。このSTEP 11では、第3正規形をさらに厳密にしたボイスコッド正規形(BCNF)と、さらに高度な正規形について学びます。また、実務で重要な戦略的な非正規化についても解説します。

BCNFの定義

📝 BCNFとは

ボイスコッド正規形(BCNF:Boyce-Codd Normal Form)=第3正規形をさらに厳密にした正規形

条件:すべての決定子が候補キーである

用語解説

決定子:他のカラムの値を一意に決定できるカラム(またはカラムの組み合わせ)

💡 簡単に言うと

BCNFとは、「キー以外のカラムが他のカラムを決定してはいけない」ということです。

つまり、「〇〇が決まれば△△が決まる」という関係があるなら、その〇〇は必ずキーでなければならないということです。

第3正規形とBCNFの違い

項目 第3正規形(3NF) BCNF
条件 推移的関数従属性がない すべての決定子が候補キー
厳しさ やや緩い より厳密
実務での使用 一般的に十分 特殊なケースのみ

BCNFが必要になるケース

ほとんどのテーブルでは、第3正規形を満たせば自動的にBCNFも満たします。しかし、複数の候補キーが存在し、それらが重なる場合に、第3正規形でもBCNFを満たさないケースが発生します。

🔍 2. BCNFが必要な実例

例:予約管理システム

❌ 第3正規形だがBCNFではないテーブル
court_reservations(テニスコート予約) ——————————————————- 日付 | 時間帯 | コート番号 | 会員ID 2025-01-15 | 10:00 | C001 | M001 2025-01-15 | 10:00 | C002 | M002 2025-01-15 | 14:00 | C001 | M003
ビジネスルール
  • 1つの時間帯に、1人の会員は1つのコートしか予約できない
  • 1つのコートは、1つの時間帯に1人しか予約できない
候補キー
  • 候補キー1:(日付, 時間帯, コート番号)
  • 候補キー2:(日付, 時間帯, 会員ID)
⚠️ 問題点
  • (日付, 時間帯, コート番号) → 会員ID
  • (日付, 時間帯, 会員ID) → コート番号
  • どちらの決定子も候補キーだが、互いに他方を含んでいるため、異常が発生する可能性がある

BCNFへの変換

✅ BCNFを満たす設計
【time_slots(時間枠)】 ————————————— 日付 | 時間帯 | 時間枠ID 2025-01-15 | 10:00 | TS001 2025-01-15 | 14:00 | TS002 【court_assignments(コート割当)】 ————————————— 時間枠ID | コート番号 | 会員ID TS001 | C001 | M001 TS001 | C002 | M002 TS002 | C001 | M003
✅ 改善点
  • 候補キーの重複が解消される
  • データの整合性がさらに向上
  • 時間枠を独立して管理できる
💡 実務での判断

このようなBCNFが必要なケースは実務ではまれです。ほとんどの場合、第3正規形で十分です。BCNFまで正規化すると設計が複雑になるため、本当に必要な場合のみ適用するのが賢明です。

📚 3. 第4正規形と第5正規形

第3正規形やBCNFを超えて、さらに高度な正規形も存在します。ただし、実務で必要になることは非常に稀です。

第4正規形(4NF)

📝 第4正規形とは

多値従属性(Multi-Valued Dependency)を排除した正規形です。

多値従属性:1つの値に対して、複数の値が独立して対応する関係

第4正規形が必要な例

❌ 多値従属性がある例
employee_skills_languages(従業員のスキルと言語) ——————————————————- 従業員ID | スキル | 言語 E001 | Java | 英語 E001 | Java | 日本語 E001 | Python | 英語 E001 | Python | 日本語
⚠️ 問題点
  • 従業員E001は「Java」と「Python」のスキルを持つ
  • 従業員E001は「英語」と「日本語」を話せる
  • しかし、スキルと言語は互いに独立している(Javaだから英語というわけではない)
  • この場合、スキル数 × 言語数 だけレコードが増えてしまう
✅ 第4正規形を満たす設計
【employee_skills(従業員スキル)】 ————————————— 従業員ID | スキル E001 | Java E001 | Python 【employee_languages(従業員言語)】 ————————————— 従業員ID | 言語 E001 | 英語 E001 | 日本語
✅ 改善点
  • 独立した関係を別テーブルに分離
  • データの冗長性を削減
  • スキルと言語を独立して管理できる

第5正規形(5NF)

📝 第5正規形とは

結合従属性(Join Dependency)を排除した正規形です。非常に理論的で、実務ではほぼ使われません。

💡 高度な正規形についてのまとめ

実務での使い分け:
第3正規形:ほとんどのケースで十分、必ずマスターすべき
⚠️ BCNF:特殊なケースのみ必要
第4正規形:非常に稀、知識として知っておく程度
第5正規形:ほぼ使わない、学術的な興味のみ

🎯 4. 実務で使われる正規化レベル

実際のシステム開発では、どのレベルまで正規化すべきなのでしょうか?

正規化レベルの選択基準

システムタイプ 推奨レベル 理由
業務システム
(受注、在庫管理など)
第3正規形 データの整合性が最重要。更新が頻繁に発生するため、正規化必須
Webアプリケーション
(SNS、ECサイトなど)
第3正規形 基本は第3正規形。ただし、一部のテーブル(例:キャッシュ)は非正規化も検討
データウェアハウス
(分析用DB)
非正規化あり 読み取り性能が重要。スタースキーマなど、意図的に非正規化する
レポートDB
(集計・分析専用)
非正規化あり 集計済みデータを保持。パフォーマンス優先で非正規化
キャッシュテーブル 非正規化 高速な読み取りが目的。元データは正規化されたテーブルに保持

正規化レベルの判断フロー

🔍 正規化レベルを決める手順
1
システムの目的を明確にする
OLTP(トランザクション処理)か、OLAP(分析処理)か?
2
基本は第3正規形を目指す
特別な理由がない限り、第3正規形を標準とする
3
パフォーマンス要件を確認
読み取り性能が極めて重要な場合は、非正規化を検討
4
テストと測定
実際にパフォーマンステストを行い、必要なら調整

⚡ 5. いつ非正規化すべきか?

戦略的な非正規化は、正規化を理解した上で、意図的に正規化のルールを崩すことです。

⚠️ 重要な前提

非正規化は「正規化を理解した上で、戦略的に行う」ものです。
正規化を知らずに非正規化するのは、ただの設計ミスです。

非正規化を検討すべきケース

✅ 非正規化が適切なケース
  1. 読み取り性能が極めて重要で、JOIN処理がボトルネックになっている
  2. 更新頻度が非常に低いデータ(例:マスタデータ)
  3. 集計済みデータをキャッシュとして保持する場合
  4. レポート・分析用のテーブル(データウェアハウス)
  5. 大量のデータを扱う場合で、パフォーマンスが問題になっている

非正規化の具体例1:注文テーブルに商品名を含める

正規化された設計
orders ————————————— 注文ID | 顧客ID | 商品ID | 数量 products ————————————— 商品ID | 商品名 | 価格 — 商品名を取得するにはJOINが必要 SELECT o.注文ID, p.商品名, o.数量 FROM orders o JOIN products p ON o.商品ID = p.商品ID;
非正規化された設計
orders ————————————— 注文ID | 顧客ID | 商品ID | 商品名 | 数量 | 単価 — JOINなしで取得可能 SELECT 注文ID, 商品名, 数量 FROM orders;
✅ 非正規化のメリット
  • JOIN不要で読み取りが高速
  • 注文時の商品名が保存される(商品名変更の影響を受けない)
  • 注文時の単価も記録できる(価格変更の影響を受けない)
⚠️ 注意点
  • データの重複が発生する
  • 商品マスタとの整合性チェックが必要

非正規化の具体例2:集計テーブル(サマリーテーブル)

正規化された設計
sales ————————————— 売上ID | 売上日 | 商品ID | 数量 | 金額 — 日次集計はその都度計算 SELECT 売上日, SUM(金額) AS 日次売上 FROM sales WHERE 売上日 BETWEEN ‘2025-01-01’ AND ‘2025-01-31’ GROUP BY 売上日;
非正規化された設計(集計テーブル追加)
sales ————————————— 売上ID | 売上日 | 商品ID | 数量 | 金額 daily_sales_summary(日次売上集計) ————————————— 売上日 | 日次売上 2025-01-01 | 1500000 2025-01-02 | 1800000 — 集計済みデータから高速取得 SELECT 売上日, 日次売上 FROM daily_sales_summary WHERE 売上日 BETWEEN ‘2025-01-01’ AND ‘2025-01-31’;
✅ メリット
  • 集計クエリが不要で超高速
  • レポート画面の表示が速い
⚠️ 注意点
  • 元のsalesテーブルと集計テーブルの整合性維持が必要
  • 日次バッチ処理などで集計データを更新する必要がある

非正規化の注意点

⚠️ 非正規化を行う場合の重要な注意事項
  1. 元の正規化されたテーブルは必ず残す(真実の情報源として)
  2. 非正規化したテーブルは、元テーブルから導出できることを明確にする
  3. 整合性維持の仕組みを設計する(トリガー、バッチ処理など)
  4. パフォーマンス測定を行う(本当に非正規化が必要か検証)
  5. ドキュメント化する(なぜ非正規化したのか、どう維持するのか)

📊 6. 正規化と非正規化の比較

項目 正規化 非正規化
データの整合性 ✅ 高い(重複がないため矛盾が発生しにくい) ⚠️ 低い(重複データの整合性維持が必要)
読み取り性能 ⚠️ 場合による(JOIN処理が必要) ✅ 高速(JOIN不要)
書き込み性能 ✅ 高速(1箇所だけ更新) ⚠️ 遅い(複数箇所の更新が必要)
ストレージ使用量 ✅ 少ない(重複データがない) ⚠️ 多い(重複データを保持)
保守性 ✅ 高い(構造が明確で変更しやすい) ⚠️ 低い(整合性維持が複雑)
適用場面 業務システム(OLTP)、トランザクション処理 レポートシステム(OLAP)、データウェアハウス

📝 7. 正規化のまとめ

正規形 主な目的 実務での重要度
第1正規形 繰り返し項目の排除 ⭐⭐⭐ 必須
第2正規形 部分関数従属性の排除 ⭐⭐⭐ 必須
第3正規形 推移的関数従属性の排除 ⭐⭐⭐ 必須(標準)
BCNF 決定子を候補キーに限定 ⭐ 特殊なケースのみ
第4正規形 多値従属性の排除 – 非常に稀
第5正規形 結合従属性の排除 – ほぼ使わない
💡 実務での正規化の基本方針
  1. 基本は第3正規形を目指す(これが標準)
  2. パフォーマンス問題が実際に発生したら、測定した上で非正規化を検討
  3. 非正規化は戦略的に(正規化を理解した上で、意図的に行う)
  4. ドキュメント化を徹底(設計判断の理由を記録)
  5. BCNFや第4正規形は、本当に必要な場合のみ適用

📝 練習問題

問題 1 基礎

BCNFと第3正規形の違いを説明してください。

【解答】

第3正規形:推移的関数従属性がない状態。主キー以外のカラムが主キー以外のカラムに依存していない。

BCNF(ボイスコッド正規形):第3正規形よりさらに厳密で、すべての決定子が候補キーである状態。

違い:ほとんどのテーブルでは第3正規形を満たせば自動的にBCNFも満たす。しかし、複数の候補キーが存在し、それらが重なる特殊なケースでは、第3正規形でもBCNFを満たさない場合がある。

問題 2 基礎

実務では、どのレベルまで正規化するのが一般的ですか?その理由も答えてください。

【解答】

実務では第3正規形まで正規化するのが一般的です。

理由:

  • 第3正規形で、データの整合性、保守性、拡張性が十分に確保できる
  • BCNFや第4正規形が必要になるケースは非常に稀
  • 第3正規形を超えると設計が複雑になり、開発・保守コストが増大する
  • パフォーマンス要件がある場合は、第3正規形から戦略的に非正規化する
問題 3 応用

非正規化が適切なケースを3つ挙げてください。

【解答例】
  1. 読み取り性能が極めて重要で、JOIN処理がボトルネックになっている場合(例:頻繁にアクセスされるレポート画面)
  2. 更新頻度が非常に低いデータ(例:マスタデータ、注文時の商品名や単価の記録)
  3. レポート・分析用のテーブル(データウェアハウス、集計済みデータのキャッシュテーブル)

重要:いずれの場合も、元の正規化されたテーブルは必ず残すことが前提です。

問題 4 応用

以下のテーブルは第3正規形ですが、BCNFではありません。理由を説明し、BCNFに変換してください。

lectures(講義割当) ——————————————————- 講師ID | 科目名 | 教室 T001 | データベース | R101 T002 | プログラミング | R102 T001 | ネットワーク | R103 ビジネスルール: – 1人の講師は複数の科目を担当できる – 各科目は1つの教室に固定(科目名 → 教室) – 候補キー: (講師ID, 科目名) または (講師ID, 教室)
【解答】

BCNFではない理由:

  • 決定子「科目名」は候補キーではないが、「教室」を決定している
  • 科目名 → 教室(科目名は候補キーではない)

BCNFへの変換:

subjects(科目テーブル) ————————————— 科目名 | 教室 データベース | R101 プログラミング | R102 ネットワーク | R103 lectures(講義割当テーブル) ————————————— 講師ID | 科目名 T001 | データベース T002 | プログラミング T001 | ネットワーク

改善点:
✅ 「科目名 → 教室」の関係を別テーブルに分離
✅ すべての決定子が候補キーになった
✅ BCNFを満たす

問題 5 発展

以下のテーブルには多値従属性があります。第4正規形に変換してください。

teacher_courses_textbooks ——————————————————- 教員ID | 科目名 | 教科書名 T001 | データベース | DB入門 T001 | データベース | SQL実践 T001 | プログラミング | DB入門 T001 | プログラミング | SQL実践 説明: – 教員T001は「データベース」と「プログラミング」を担当 – 教員T001は「DB入門」と「SQL実践」を使用 – 科目と教科書は独立している
【解答】

多値従属性の分析:

  • 教員ID →→ 科目名(1人の教員が複数の科目を担当)
  • 教員ID →→ 教科書名(1人の教員が複数の教科書を使用)
  • 科目名と教科書名は独立している(多値従属性)

第4正規形への変換:

teacher_courses(教員担当科目) ————————————— 教員ID | 科目名 T001 | データベース T001 | プログラミング teacher_textbooks(教員使用教科書) ————————————— 教員ID | 教科書名 T001 | DB入門 T001 | SQL実践

改善点:
✅ 独立した多値従属性を別テーブルに分離
✅ データの冗長性を削減(元は4レコード → 合計4レコード)
✅ 科目と教科書を独立して管理できる

問題 6 発展

以下の「正規化されたテーブル」に対して、戦略的な非正規化を提案してください。どのように非正規化し、なぜそれが適切か説明してください。

— 現在の設計(第3正規形) orders(注文テーブル) ————————————— 注文ID | 顧客ID | 注文日 customers(顧客テーブル) ————————————— 顧客ID | 顧客名 | メール order_details(注文詳細テーブル) ————————————— 注文ID | 商品ID | 数量 products(商品テーブル) ————————————— 商品ID | 商品名 | 価格 課題: – 注文一覧画面で「注文ID、注文日、顧客名、商品名、数量、金額」を表示する – この画面は頻繁にアクセスされる(1日1万回以上) – 現在、4テーブルのJOINが必要で、パフォーマンスが問題になっている
【解答】

非正規化の提案:

1. order_details テーブルに商品名と単価を追加

order_details(注文詳細テーブル) ————————————— 注文ID | 商品ID | 商品名 | 数量 | 単価 | 小計 1 | P001 | ノートPC | 1 | 80000 | 80000 1 | P002 | マウス | 2 | 2000 | 4000 — または、さらに進めて注文一覧専用のビューテーブルを作成 order_summary_view(注文サマリー) ————————————— 注文ID | 注文日 | 顧客ID | 顧客名 | 商品ID | 商品名 | 数量 | 単価 | 小計 1 | 2025-01-15 | C001 | 山田商店 | P001 | ノートPC | 1 | 80000 | 80000 1 | 2025-01-15 | C001 | 山田商店 | P002 | マウス | 2 | 2000 | 4000

なぜこの非正規化が適切か:

  • 読み取り頻度が高い(1日1万回以上)
  • 更新頻度は低い(注文確定後はほぼ更新しない)
  • 注文時の商品名・単価を保存することは業務的にも意味がある(価格変更の影響を受けない)
  • JOINが不要になり、パフォーマンスが大幅に向上

重要な注意事項:

  • ⚠️ 元の正規化されたテーブル(products)は必ず維持
  • ⚠️ 注文登録時に、商品マスタから商品名と価格をコピーする
  • ⚠️ 集計などの分析クエリは、元のテーブルを使用
  • ⚠️ この設計判断をドキュメント化する
問題 7 発展

以下のシナリオで、正規化と非正規化のどちらを選ぶべきか、理由とともに答えてください。

シナリオA:社内の経費精算システム。月に数百件の申請があり、承認フローで頻繁に更新される。
シナリオB:ECサイトの売上レポート画面。過去の売上データを集計して表示。更新は日次バッチで行う。
シナリオC:リアルタイムチャットアプリのメッセージDB。秒間数千のメッセージが記録され、読み書き両方が頻繁。

【解答】

シナリオA:経費精算システム → 正規化(第3正規形)

  • 理由:頻繁に更新されるため、データの整合性が最重要
  • 正規化することで、更新異常を防ぎ、データの一貫性を保証
  • 読み取り性能よりも、データの正確性が優先される

シナリオB:売上レポート → 非正規化(集計テーブル)

  • 理由:読み取り専用で、更新は日次バッチのみ
  • 集計済みデータを保持することで、レポート表示が超高速化
  • 元の正規化されたトランザクションデータは別途保持
  • 典型的なOLAP(分析処理)用途で、非正規化が適切

シナリオC:チャットアプリ → 正規化(第3正規形)+ 適度な非正規化

  • 理由:読み書き両方が頻繁なため、バランスが重要
  • 基本は正規化して、データの整合性を保証
  • ただし、頻繁に表示される情報(ユーザー名など)は、適度に非正規化してパフォーマンスを確保
  • 例:messages テーブルに user_id だけでなく username も保存(表示高速化)
  • キャッシュ層(Redis など)も併用を検討
問題 8 発展

【総合問題】以下のテーブルについて、(1)第3正規形か判定し、(2)BCNFか判定し、(3)必要なら適切な正規形に変換してください。

project_assignments ——————————————————- プロジェクトID | 従業員ID | 役割 | 部署名 P001 | E001 | リーダー | 開発部 P001 | E002 | メンバー | 開発部 P002 | E001 | リーダー | 開発部 ビジネスルール: – 1つのプロジェクトに複数の従業員が参加 – 1人の従業員は複数のプロジェクトに参加可能 – 従業員IDが決まれば部署名が決まる(従業員ID → 部署名)
【解答】

(1) 第3正規形か?

  • 第3正規形ではない
  • 主キー = (プロジェクトID, 従業員ID)
  • 推移的関数従属性がある:(プロジェクトID, 従業員ID) → 従業員ID → 部署名

(2) BCNFか?

  • BCNFでもない
  • 決定子「従業員ID」は候補キーではないが、「部署名」を決定している

(3) 第3正規形への変換:

employees(従業員テーブル) ————————————— 従業員ID | 部署名 E001 | 開発部 E002 | 開発部 projects(プロジェクトテーブル) ————————————— プロジェクトID | プロジェクト名 P001 | システムA開発 P002 | システムB開発 project_assignments(プロジェクト割当テーブル) ————————————— プロジェクトID | 従業員ID | 役割 P001 | E001 | リーダー P001 | E002 | メンバー P002 | E001 | リーダー

改善点:
✅ 推移的関数従属性を排除
✅ 従業員情報は employees テーブルで一元管理
✅ データの整合性が保証される
✅ 第3正規形を満たす(この場合、BCNFも満たす)

❓ よくある質問

Q1: 実務でBCNFまで正規化する必要はありますか?

ほとんどの場合、必要ありません。実務では第3正規形で十分です。BCNFが必要になるのは、複数の候補キーが存在し、それらが重なる非常に特殊なケースのみです。まずは第3正規形をしっかりマスターすることに集中しましょう。

Q2: 非正規化すると、データの整合性が失われませんか?

適切に管理すれば大丈夫です。非正規化する場合の重要なポイント:(1) 元の正規化されたテーブルは必ず残す、(2) 整合性維持の仕組みを設計する、(3) 非正規化したテーブルは元テーブルから導出できることを明確にする。つまり、非正規化したテーブルは「キャッシュ」や「ビュー」のようなものと考えます。

Q3: パフォーマンスが遅い場合、すぐに非正規化すべきですか?

いいえ、非正規化は最後の手段です。パフォーマンス改善の優先順位:(1) インデックスの追加・最適化、(2) クエリの最適化、(3) キャッシュの導入、(4) パーティショニング、(5) 非正規化。実測してから判断することが重要です。

Q4: データウェアハウスでは、なぜ非正規化するのですか?

データウェアハウスは「読み取り専用」に近いシステムだからです。主な用途は分析・レポート(読み取りメイン)で、データの更新は日次バッチなど定期的。複雑な集計クエリが頻繁に実行されるため、JOIN処理を減らし、読み取り性能を最大化するために非正規化が使われます。

Q5: 第4正規形や第5正規形を学ぶ必要はありますか?

基本的には不要です。実務で第4正規形や第5正規形が必要になるケースは非常に稀です。学習の優先順位は、(1) 第3正規形まで完璧にマスター、(2) 実務経験を積む、(3) 特殊なケースに遭遇したら学ぶ。第3正規形をしっかり理解していれば、実務の99%のケースに対応できます。

Q6: 正規化と非正規化、どちらが初心者向けですか?

正規化が初心者向けです。ルールが明確で機械的に適用でき、失敗が少なく、後から非正規化できます。非正規化は「正規化を理解した上で、戦略的に崩す」高度な技術です。「正規化を知らずに非正規化するのは、ただの設計ミス」ということを忘れずに!

📝

学習メモ

データベース設計・データモデリング - Step 11

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE