正規化でデータの重複をなくす
エンティティを洗い出したら、次はデータの構造を整理する正規化の工程です。 正規化? データの重複をなくし、更新時の不整合を防ぐためにテーブルを分割する設計手法。第1〜第3正規形などの段階がある。整理されたDBはメンテナンスしやすくなる。 は「データの重複をなくし、更新・削除・挿入に伴う不整合を防ぐ」ための設計技法です。
正規化が必要な理由
まず、正規化されていないテーブルがどのような問題を引き起こすか見てみましょう。
以下は「受注管理」を1テーブルで管理した例です。
-- 非正規形の受注テーブル
order_id | customer_name | customer_email | product_ids | product_names | product_prices | qty
---------|---------------|---------------------|--------------|----------------------|----------------|----
1 | 田中 太郎 | tanaka@example.com | 101,102 | Tシャツ,ジーンズ | 2000,5000 | 1,2
2 | 田中 太郎 | tanaka@example.com | 103 | スニーカー | 8000 | 1
3 | 鈴木 花子 | suzuki@example.com | 101 | Tシャツ | 2000 | 3
このテーブルには3種類の「異常」が潜んでいます。
更新異常(Update Anomaly)
田中さんのメールアドレスが変わった場合、すべての行を更新しなければなりません。1行でも漏れると、同じ人物なのにメールアドレスが異なるレコードが生まれます。
削除異常(Delete Anomaly)
注文ID=2のレコードを削除すると、田中さんが「スニーカー(8000円)」という商品情報も消えてしまいます。
挿入異常(Insert Anomaly)
まだ注文が入っていない新商品を登録したい場合、order_id や customer_name がないとレコードを挿入できません。
第1正規形(1NF)
第1正規形(1NF)? 各セルに1つの値のみを持ち、繰り返しグループをなくした状態。「趣味1・趣味2・趣味3」のような列は1NFに違反している。 のルールは「1つのセルに複数の値を入れない」ことです。繰り返しグループ(コンマ区切りの値など)を排除します。
先ほどの非正規形テーブルを1NFに変換します。
-- 1NF: 繰り返しグループを除去
order_id | customer_name | customer_email | product_id | product_name | product_price | qty
---------|---------------|---------------------|------------|--------------|---------------|----
1 | 田中 太郎 | tanaka@example.com | 101 | Tシャツ | 2000 | 1
1 | 田中 太郎 | tanaka@example.com | 102 | ジーンズ | 5000 | 2
2 | 田中 太郎 | tanaka@example.com | 103 | スニーカー | 8000 | 1
3 | 鈴木 花子 | suzuki@example.com | 101 | Tシャツ | 2000 | 3
主キーは (order_id, product_id) の複合キーになります。
第2正規形(2NF)
第2正規形(2NF)? 第1正規形を満たした上で、複合主キーの一部にだけ依存する「部分関数従属」をなくした状態。完全関数従属のみになるようテーブルを分割する。 のルールは「主キーの一部だけに依存する属性(部分関数従属)を排除する」ことです。
先ほどのテーブルを分析すると、customer_name と customer_email は order_id だけで決まります(product_id は関係ない)。product_name と product_price は product_id だけで決まります。これが部分関数従属です。
-- 2NF: 部分関数従属を除去してテーブルを分割
-- 注文テーブル(order_id -> customer情報)
orders
order_id | customer_name | customer_email
---------|---------------|--------------------
1 | 田中 太郎 | tanaka@example.com
2 | 田中 太郎 | tanaka@example.com
3 | 鈴木 花子 | suzuki@example.com
-- 商品テーブル(product_id -> product情報)
products
product_id | product_name | product_price
-----------|--------------|---------------
101 | Tシャツ | 2000
102 | ジーンズ | 5000
103 | スニーカー | 8000
-- 注文明細テーブル(order_id + product_id -> qty)
order_items
order_id | product_id | qty
---------|------------|----
1 | 101 | 1
1 | 102 | 2
2 | 103 | 1
3 | 101 | 3
第3正規形(3NF)
第3正規形(3NF)? 第2正規形を満たした上で、主キー以外の列が主キーに推移的に依存する「推移関数従属」をなくした状態。実用上、3NFを目指すことが多い。 のルールは「主キー以外の属性を経由した依存関係(推移関数従属)を排除する」ことです。
2NFの orders テーブルをよく見ると、customer_email は order_id に依存していますが、customer_name は実は customer_email にも依存しています(同じメールアドレスなら同じ名前)。これが推移関数従属です。
-- 3NF: 推移関数従属を除去
-- 顧客テーブル
customers
customer_id | customer_name | customer_email
------------|---------------|--------------------
1 | 田中 太郎 | tanaka@example.com
2 | 鈴木 花子 | suzuki@example.com
-- 注文テーブル(customer_emailの代わりにcustomer_idを参照)
orders
order_id | customer_id | order_date
---------|-------------|------------
1 | 1 | 2026-04-01
2 | 1 | 2026-04-05
3 | 2 | 2026-04-08
正規化のメリットをまとめる
| 正規形 | 除去するもの | 解決される問題 |
|---|---|---|
| 1NF | 繰り返しグループ | セル内の複数値 |
| 2NF | 部分関数従属 | 主キーの一部への依存 |
| 3NF | 推移関数従属 | 非キー属性間の依存 |
正規化の落とし穴
正規化は強力な技法ですが、やりすぎに注意が必要です。
JOINが増えるとクエリが重くなる
テーブルを細かく分割するほど、データを取得するときの JOIN が増えます。例えば注文一覧を表示するのに10テーブルを結合するクエリは、パフォーマンスの問題を起こしかねません。
実用的な落としどころ
業務システムでは通常第3正規形(3NF) までを目標にします。それ以上の正規化(ボイスコッド正規形、第4正規形など)は理論上は正しくても、実用上はオーバースペックになることが多いです。
また、第7回で詳しく扱いますが、パフォーマンス要件によっては意図的に非正規化( 非正規化? パフォーマンス向上のために意図的にデータの重複を許す設計。JOINが多くなって遅い場合に、あえてデータをまとめて検索を速くする。正規化とのトレードオフで判断する。 )を行うこともあります。正規化は目的ではなく、「データの整合性を保つための手段」であることを忘れないようにしましょう。
まとめ
- 1NF:1セル1値、繰り返しグループの除去
- 2NF:部分関数従属の除去(複合主キーが前提)
- 3NF:推移関数従属の除去
正規化を理解することで、「なぜテーブルをこう分けるのか」が論理的に説明できるようになります。次回は、正規化されたテーブル群をER図で視覚的に表現する方法を学びます。