正規化 (1NF〜3NF)
正規化は「理論暗記」ではなく、更新不整合を防ぐための実務技法です。 このページでは、受注データを例に非正規 → 1NF → 2NF → 3NF の順で段階的に分解します。
このページは概念理解を優先して、フィールド名を日本語で記載しています。 実装時に英語名へ置き換えても、正規化の考え方は同じです。
正規化の目的
- 同じ情報の重複保存を減らす
- 更新・削除時の矛盾(更新不整合)を防ぐ
- テーブルごとの責務を明確にする
非正規の問題
まずは、全部入りの 1 テーブルを考えます。
受注まとめ(
受注ID, 受注日,
顧客ID, 顧客名,
商品ID, 商品名, 単価,
数量
)
| 受注ID | 顧客名 | 商品名 | 単価 | 数量 |
|---|---|---|---|---|
| 1001 | 山田太郎 | USBメモリ 32GB | 1200 | 2 |
| 1001 | 山田太郎 | キーボード | 3800 | 1 |
| 1002 | 鈴木花子 | USBメモリ 32GB | 1200 | 1 |
この構造だと、商品名変更時に複数行更新が必要です。1行でも更新漏れがあると同じ商品IDなのに商品名が食い違います。
月別12カラム問題(代表的アンチパターン)
正規化の観点で特に多いのが、1レコードに月別値を横持ちする設計です。
商品別売上(
商品ID,
1月売上, 2月売上, 3月売上, ... , 12月売上
)
この設計は次の問題を起こします。
- 年月が列名に埋め込まれ、13か月目や翌年対応がしづらい
- 集計SQLが複雑化し、保守時にバグを生みやすい
- 可変軸(年月)を列で持つため、正規化の考え方に反する
改善例(縦持ち):
月次売上(
商品ID,
対象年月,
売上金額,
PRIMARY KEY (商品ID, 対象年月)
)
「増える可能性がある軸(年月、週、タグ)」は列ではなく行で持つのが基本です。 正規化の実務的なコツとして覚えると、設計レビューで強くなります。
第1正規形 (1NF)
1NF の要点は「繰り返し項目を列内に詰め込まない」ことです。
例えば 商品ID一覧='P01,P02' のような格納を避け、1明細1行に分けます。
| 悪い例 | 良い例 |
|---|---|
商品ID一覧='P01,P02' |
受注明細テーブルを作って 1 商品 1 行で保存 |
第2正規形 (2NF)
明細テーブルの主キーを (受注ID, 明細番号) とすると、
顧客名 は 受注ID だけで決まります。
これは主キーの一部にしか依存しないため、受注明細に置くべきではありません。
2NF の分離例:
受注(受注ID, 受注日, 顧客ID)
受注明細(受注ID, 明細番号, 商品ID, 数量)
第3正規形 (3NF)
例えば 受注 に 顧客名 を持つと、
受注ID -> 顧客ID -> 顧客名 の推移依存が発生します。
顧客名は顧客テーブルへ分離するのが 3NF の考え方です。
3NF の到達形:
顧客(顧客ID, 顧客名)
商品(商品ID, 商品名, 単価)
受注(受注ID, 受注日, 顧客ID)
受注明細(受注ID, 明細番号, 商品ID, 数量)
ER図で見る差
非正規(責務が混在)
3NF(責務分離後)
実務のまとめ
- まず 3NF まで素直に設計する
- 性能課題が出てから、根拠を持って限定的に非正規化する
- 正規化の説明を「更新不整合をどう防ぐか」で語れるようにする
レビューで「この列はどのキーに従属するか」を説明できれば、正規化の品質が一段上がります。