なぜ正規化が必要か
このレッスンで分かること
- 非正規化テーブルは更新異常・挿入異常・削除異常を引き起こします
- 正規化のゴールは「1 つの事実が 1 か所だけ」に存在する状態です
- テーブルを責務で分けて外部キーでつなぐのが基本方針
なぜ正規化が必要か とは
データベース設計における正規化の重要性と、その必要性について解説します。本レッスンでは、なぜ正規化が必要か の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。
導入
業務システムを長く運用していると、テーブル設計の良し悪しが運用コストとして跳ね返ってきます。同じ顧客名が何十行にも繰り返し書かれ、住所変更のたびに全行を更新しなければならない。表計算ソフトをそのままテーブルにしたような設計では、データの 更新異常 / 挿入異常 / 削除異常 が発生し、整合性が崩れていきます。正規化 はこうした異常を理論的に防ぐための手続きで、リレーショナルデータベース設計の出発点です。
非正規化テーブルで起きる異常
たとえば、注文と顧客と商品を 1 枚のテーブルにまとめた次のような構造を考えてみましょう。
SQL クエリ
CREATE TABLE orders_flat (
order_id INT,
customer_id INT,
customer_name VARCHAR(100),
customer_email VARCHAR(255),
product_id INT,
product_name VARCHAR(100),
unit_price INT,
quantity INT
);この 1 枚テーブルでは、同じ顧客の情報が注文行ごとに重複します。顧客のメールアドレスが変更された場合、関連する全行を漏れなく書き換える必要があり、1 行でも更新漏れがあれば矛盾が発生します。これが 更新異常 (update anomaly) です。
SQL クエリ
-- 更新漏れの例: 顧客 1 のメールを変えるつもりが、注文行 3 を更新し忘れた
UPDATE orders_flat SET customer_email = 'new@example.com'
WHERE customer_id = 1 AND order_id <> 3;さらに、まだ注文していない顧客を登録できない 挿入異常 (insertion anomaly)、最後の注文を消すと顧客情報まで消える 削除異常 (deletion anomaly) も起きます。
異常が起きる流れ
図の流れを箇条書きで整理すると、次の通りです。
- 1 枚テーブルに全てを格納する
- 同じ値が複数行で重複する
- 更新時に書き漏らしが発生する (更新異常)
- 親に紐付かないと子を登録できない (挿入異常)
- 子を消すと親も道連れに消える (削除異常)
正規化のゴール
正規化のゴールは、1 つの事実が 1 か所だけに存在する状態を作ることです。住所は customers に、商品名は products に、注文の数量は order_items にといったように、責務でテーブルを分け、外部キー でつなぎます。
SQL クエリ
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(255)
);
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
unit_price INT
);
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id)
);代表例
非正規化テーブルから「顧客ごとの注文金額合計」を出すと、商品単価が重複しているため集計値がブレるリスクがあります。
SQL クエリ
-- 非正規化テーブルでは unit_price が手入力で変わっていることがある
SELECT customer_id, SUM(unit_price * quantity) AS total
FROM orders_flat
GROUP BY customer_id;正規化されたテーブルなら、商品マスタが 1 か所のため、価格の整合性は壊れません。
SQL クエリ
SELECT o.customer_id, SUM(p.unit_price * oi.quantity) AS total
FROM order_items oi
JOIN products p ON p.id = oi.product_id
JOIN orders o ON o.id = oi.order_id
GROUP BY o.customer_id;正規化は「データ重複をゼロにする」のが本当の目的ではありません。重複を許すと事実の単一性が壊れ、整合性が保てなくなることが本質的な問題です。
最初から「全部 1 枚のテーブル」にする設計は、Excel をそのまま DB に持ち込んだような状態です。短期的には楽でも、データが増えるほど更新コストが指数関数的に膨らみます。
更新異常・挿入異常・削除異常という呼び方はリレーショナルデータベースの世界で 1970 年代から使われている用語で、現代の業務システム設計でも変わらず通用します。
3 つの異常を具体例で理解する
更新異常
同じ顧客のメールアドレスが 5 つの注文行に書かれているとします。アドレス変更時にどれか 1 行でも更新を漏らすと、過去の注文を辿るたびに「メールアドレスはどれが正しいのか」が分からなくなります。アプリ側で SELECT DISTINCT email FROM orders_flat WHERE customer_id = 1 を実行すると複数行が返ってきて、ユーザに送るアドレスを 1 つに絞れなくなります。
挿入異常
注文 1 つにつき 1 行という制約があるので、注文がない新規顧客は登録できません。仮に NULL で埋めて空の注文行を作れば、今度は集計対象としてカウントしてしまい、別の不具合になります。
削除異常
顧客 A の最後の注文を取り消すと、その顧客の名前とメールアドレスもテーブルから消滅します。注文と顧客は本来別の事実なのに、1 枚テーブルにまとめたせいで命運を共にしてしまいます。
正規化と SQL 設計
正規化はテーブル分割の話ですが、SQL 自体の書き方にも影響します。正規化されたスキーマでは集計のために JOIN が必要になりますが、その分マスタは 1 か所なのでクエリの意味が安定します。一方、非正規化テーブルでは SELECT は簡単に書けますが、結果がいつも「最新の事実」であるとは限らなくなります。
ここまでの要点 正規化 = 1 つの事実を 1 か所に。テーブルを責務で分けて FK でつなぐ。非正規化は更新・挿入・削除の 3 つの異常を生む。
まとめ
- 非正規化テーブルは更新異常 / 挿入異常 / 削除異常を起こす
- 正規化は「1 つの事実が 1 か所だけ」に存在する状態を目指す
- テーブルを責務で分けて外部キーで結ぶのが基本方針
- 集計は JOIN で組み立てれば不整合は起きない
次のレッスン
次は 第1正規形 (1NF) です。データベース設計の基礎、第一正規形(1NF)について、SQLでの具体例を通して実践的に理解を深めます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- 正規化の必要性 の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. 正規化の必要性 とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
テーブル構造
schema.sql
CREATE TABLE orders_flat (
order_id INT,
customer_id INT,
customer_name VARCHAR(100),
customer_email VARCHAR(255),
product_id INT,
product_name VARCHAR(100),
unit_price INT,
quantity INT
);
INSERT INTO orders_flat VALUES
(1001, 1, '田中', 'tanaka@example.com', 10, 'ノート', 300, 2),
(1001, 1, '田中', 'tanaka@example.com', 11, 'ペン', 150, 4),
(1002, 2, '佐藤', 'sato@example.com', 10, 'ノート', 300, 1),
(1003, 1, '田中', 'tanaka_NEW@example.com', 12, '消しゴム', 100, 3),
(1004, 3, '鈴木', 'suzuki@example.com', 11, 'ペン', 150, 5);期待される出力
| customer_id | distinct_email_count |
|---|---|
| 1 | 2 |