📄
概念 📚 software-design-concepts

正規化(データベース正規化)

1NF〜BCNFの正規化形式の定義と、非正規化が有効なケース・実務でのトレードオフの判断基準

データベース正規化とは、リレーショナルデータベースの設計において、データの冗長性を排除し、更新の整合性を保つために、テーブルを一定のルールに従って分解・整理するプロセスである。正規化を適用することで、同じ情報が複数箇所に散在する状態を防ぎ、データの変更が局所的に完結するような構造を実現できる。

正規化の理論的基盤を築いたのは、1970年代にIBMで関係モデルを提唱したEdgar F. Coddである。Coddは「第1正規形(1NF)」「第2正規形(2NF)」「第3正規形(3NF)」を定義し、後にRaymond F. Boyce との共同研究でさらに厳密な「ボイスコッドの正規形(BCNF)」を提案した。これらの正規形は「関数従属性」という概念を軸に、テーブル内の列間の依存関係を整理する理論的な枠組みを提供する。

正規化によって取り除かれる問題は大きく3種類ある。「更新異常(Update Anomaly)」は、同じ情報が複数行に存在するために、片方を更新しても他方に反映されない状態を指す。「挿入異常(Insertion Anomaly)」は、他の情報がなければ本来保存したいデータを登録できない状態、「削除異常(Deletion Anomaly)」は特定のレコードを削除すると意図せず別の情報まで失ってしまう状態を指す。正規化はこれらの異常を構造的に防ぐ手段として機能する。

第1正規形(1NF)

1NFの要件は、テーブルの各セルが「原子値(atomic value)」を持つことである。つまり、1つのセルに複数の値を詰め込んだり、同一の概念を表す列を繰り返したりしてはならない。

違反例として、注文テーブル商品名 カラムが "りんご, みかん, バナナ" という形で格納されているケースが挙げられる。このような「繰り返しグループ」を持つ設計は1NFを満たさない。正規化するには、注文と商品を別レコードとして分離し、1行1商品の構造にする必要がある。

第2正規形(2NF)

2NFは1NFを満たした上で、「部分関数従属(Partial Functional Dependency)」を除去した状態である。部分関数従属とは、複合主キーの一部にのみ依存している非キー属性が存在することを指す。

例として、(注文ID, 商品ID) を複合主キーとするテーブルに 商品名 カラムがある場合、商品名商品ID だけで一意に決まるため、複合主キー全体には依存していない。これは部分関数従属であり、商品マスタ テーブルを分離して 商品ID → 商品名 の関係をそちらに移すことで2NFを達成できる。

第3正規形(3NF)

3NFは2NFを満たした上で、「推移的関数従属(Transitive Functional Dependency)」を除去した状態である。推移的関数従属とは、主キーでない列Aが別の非キー列Bを関数的に決定する(主キー → A → B という連鎖がある)状態を指す。

例として、社員テーブル(社員ID, 部署ID, 部署名) があるとき、部署名部署ID に依存しており、社員ID から直接ではなく 部署ID を経由して決まる。これを除去するには 部署テーブル を分離し、社員テーブル には 部署ID だけを残す。

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

BCNFは3NFをより厳密にした形式であり、「すべての関数従属 X → Y において、Xはスーパーキーでなければならない」という条件を課す。3NFでは非キー属性への従属が例外的に許容される場合があるが、BCNFはそれも許容しない。

実務的には3NFとBCNFの差異が現れるケースは限られるが、複数の候補キーが重複する属性を持つテーブル(例:授業スケジュールで (学生, 科目)(学生, 教師) が候補キーになる場合)などで違いが生じる。BCNFへの分解は、場合によって関数従属性の一部が失われる(依存性保持が保証されない)点に注意が必要である。

コードレビューで着目するポイント

  • 1カラムに複数値がカンマ区切りやJSON配列として詰め込まれていないか(1NF違反の兆候)
  • 複合主キーを持つテーブルで、主キーの一部にしか依存しない列が存在しないか(2NF違反の兆候)
  • 部署名カテゴリ名 などの名称系カラムが、マスタテーブルを介さず直接保持されていないか(3NF違反の兆候)
  • 外部キー制約が適切に設定されており、参照整合性が担保されているか
  • 同じ意味を持つ情報が複数テーブルに重複して存在していないか(冗長性の確認)
  • NULL値が多用されているカラムが、設計上の問題(異なる概念の混在など)を示していないか
  • テーブル名・カラム名が表現する概念の粒度が適切か(過度に汎用的な命名は設計の曖昧さを示唆する)

非正規化が有効なケース

  • OLAP(Online Analytical Processing)や集計・レポーティング用途では、結合コストを減らすためにあえて非正規化した幅広いテーブル(ワイドテーブル)を採用することが合理的である
  • OLTP(Online Transaction Processing)では正規化が基本方針だが、読み取りが極端に多い参照系のデータ(例:商品の合計レビュー件数)については、集計用カラムをキャッシュとして保持し、非同期に更新する設計が実用的な場合がある
  • マイクロサービスアーキテクチャでは、サービスをまたいだJOINができないため、各サービスが必要なデータを自前のDBに冗長に持つ設計(データの分散所有)が避けられないことがある
  • データウェアハウスやBIツール向けのスタースキーマ・スノーフレークスキーマでは、分析クエリの単純化を優先し、意図的に非正規化された構造を採用する

典型的なアンチパターン

EAV(Entity-Attribute-Value)アンチパターン

属性名と値をそれぞれカラムに持つ汎用テーブル(entity_id, attribute_name, attribute_value の3カラム構成)は、スキーマを柔軟に見せるが深刻な問題を招く。型安全性がなく、特定の属性に対するクエリが複雑になり、インデックスの効きが悪い。また、RDBのリレーショナルモデルの利点をほぼ失う。本来は属性の多様性にはJSONカラムの利用やドキュメントDBへの移行を検討すべきである。

複数値を1カラムに詰め込む

タグや権限などを "admin,editor,viewer" のように文字列として1カラムに格納するパターンは、検索・更新・整合性の観点からすべて問題がある。特定のタグを持つレコードを検索するには LIKE '%tag%' のような曖昧検索が必要になり、インデックスが使えない。多対多の関係は中間テーブルで表現するのが正しい設計である。

IDのみ異なる重複テーブル

sales_2023, sales_2024 のように年度ごとにテーブルを増やしたり、user_jp, user_us のように地域ごとにテーブルを分けるパターンは、テーブル数が増えるにつれてスキーマ管理・クエリ・マイグレーションがすべて複雑になる。区分を表すカラム(year, region など)を追加した単一テーブルで管理し、パーティショニングで性能を担保する設計が適切である。

参考リソース

  • E.F. Codd, “A Relational Model of Data for Large Shared Data Banks” (1970) — 関係モデルの原典論文。正規化の理論的出発点
  • Joe Celko, “Joe Celko’s SQL for Smarties: Advanced SQL Programming” — SQL設計の実践的なアンチパターンと正規化について詳しく解説
  • C.J. Date, “An Introduction to Database Systems” — 関係理論とBCNFを含む正規化形式の厳密な記述
  • Bill Karwin, “SQL Antipatterns: Avoiding the Pitfalls of Database Programming” — EAVを含む典型的なSQLアンチパターンを体系的に解説(日本語訳あり)
  • Martin Kleppmann, “Designing Data-Intensive Applications” — OLTP/OLAPの違いとデータ正規化・非正規化のトレードオフを実務観点から整理