#03 データベースを設計する

正規化でデータの重複をなくす

エンティティを洗い出したら、次はデータの構造を整理する正規化の工程です。 正規化? データの重複をなくし、更新時の不整合を防ぐためにテーブルを分割する設計手法。第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_idcustomer_name がないとレコードを挿入できません。

第1正規形(1NF)

第1正規形(1NF)? 各セルに1つの値のみを持ち、繰り返しグループをなくした状態。「趣味1・趣味2・趣味3」のような列は1NFに違反している。 のルールは「1つのセルに複数の値を入れない」ことです。繰り返しグループ(コンマ区切りの値など)を排除します。

第1正規形(1NF)への変換 変換前(非正規形) 1セルに複数の値が入っている order_id customer product_ids prices 1 田中 101,102 2000,5000 2 鈴木 103,104 8000,3000 問題点 ・1セルに複数の値(コンマ区切り) ・「商品103の価格は?」が取れない ・SQLでの集計が困難 1NF適用 変換後(第1正規形) 1行1値に分解する order_id customer product_id price 1 田中 101 2000 1 田中 102 5000 2 鈴木 103 8000 2 鈴木 104 3000 PK PK 複合主キー: (order_id, product_id) まだ customer の重複あり → 2NF へ ※ 田中が2回出現している(次のステップで解消) 第1正規形のルール 1つのセルには1つの値のみ。繰り返しグループ(配列・コンマ区切り)を排除し、1行1値に分解する。 これにより、任意の値での検索・集計が可能になる
図1: 第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_namecustomer_emailorder_id だけで決まります(product_id は関係ない)。product_nameproduct_priceproduct_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_emailorder_id に依存していますが、customer_name は実は customer_email にも依存しています(同じメールアドレスなら同じ名前)。これが推移関数従属です。

第1正規形 → 第2正規形 → 第3正規形 第1正規形(1NF) order_items PK order_id PK product_id customer_name customer_email product_name product_price quantity 赤: order_id のみに依存(部分従属) 黄: product_id のみに依存(部分従属) 2NF 部分従属 を排除 第2正規形(2NF) orders PK order_id customer_name customer_email まだ推移従属が残っている email → name の依存 products PK product_id product_name product_price order_items PK order_id FK PK product_id FK quantity 3NF 推移従属 を排除 第3正規形(3NF) customers PK id name email UNIQUE orders PK id customer _id FK date 各正規形のルールまとめ 1NF: 1セル1値。繰り返しグループを排除する 2NF: 部分関数従属を排除する(主キーの全体に依存させる) 3NF: 推移関数従属を排除する(非キー属性間の依存をなくす) 通常の業務システムでは 3NF までを目標にする
図2: 第1→第2→第3正規形の段階
-- 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図で視覚的に表現する方法を学びます。