導入
第0章で「重複を避けるために表を分ける」ことを 正規化 と呼ぶ、と学びました。この章の中心テーマとして、正規化を**段階(第1〜第3正規形)**として、もう一歩踏み込んで理解します。難しく感じるかもしれませんが、根っこは「1つの事実は1か所にだけ書く」というシンプルな原則です。
説明
もし何も設計せず、注文を1枚の“ベタ表”で持ったらどうなるか。悪い例から始めます。
正規化前(ダメな1枚表)
| 注文 | 会員 | 都市 | 商品(複数) | 単価 |
|---|---|---|---|---|
| 5 | 田中 | 福岡 | ノートPC, USBケーブル | 128000, 800 |
この表には問題が詰まっています。「商品」欄に複数値が押し込まれ、会員名や都市は注文のたびに繰り返され、単価は商品の情報なのに注文表に混ざっています。これを3段階で整えます。
flowchart LR
R0["非正規形<br/>1マスに複数値・重複だらけ"] -->|"第1正規化<br/>1マス1値にする"| R1["第1正規形(1NF)<br/>繰り返しを行に分解"]
R1 -->|"第2正規化<br/>主キーの一部で決まる列を分離"| R2["第2正規形(2NF)"]
R2 -->|"第3正規化<br/>キー以外に依存する列を分離"| R3["第3正規形(3NF)<br/>重複のない設計"]
- 第1正規形(1NF)… 1マスに1つの値。「商品」欄に複数の商品を詰めるのをやめ、1商品=1行にばらします。これで
order_items(注文明細)が生まれます。 - 第2正規形(2NF)… 主キーの“一部”で決まる項目を追い出す。明細のキーは「注文+商品」の組ですが、「商品の単価」は商品だけで決まります。だから単価は
products側へ。商品マスタの独立です。 - 第3正規形(3NF)… キー以外の項目に“ぶら下がる”項目を追い出す。注文表に会員名や都市を持つと、それは「会員」で決まる情報。だから
users側へ。会員マスタの独立です。
こうして整えた結果が、まさにこのショップ DB の5テーブル構成です。「注文」「明細」「会員」「商品」「カテゴリ」に分かれ、どの事実も1か所にしか書かれていない。だから、会員が引っ越しても、商品の単価が変わっても、直すのは1行だけで済みます。
-- 正規化された5表から、あの“1枚表”をいつでも再現できる(JOIN で組み立て直す)
SELECT o.id AS 注文, u.name AS 会員, p.name AS 商品, oi.quantity AS 個数
FROM order_items oi
JOIN orders o ON oi.order_id = o.id
JOIN users u ON o.user_id = u.id
JOIN products p ON oi.product_id = p.id
ORDER BY o.id;
正規化は「表を分けて保存し、見たいときは JOIN で組み立てる」という分業です。保存は正規化、表示は結合――この呼吸が実務設計の基本になります。
ほどよさも大切です。集計を速くしたい等の理由で、あえて重複を許す「非正規化」を部分的に行うこともあります。まずは3NF を基本形として身につけ、崩すのはその後です。
やってみよう
初期表示のクエリを実行し、正規化された5つの表から「注文・会員・商品・個数」の“1枚表”が再現できることを確認しましょう。バラバラに保存しても、JOIN でいつでも元の見え方に戻せる――これが正規化の安心感です。
演習
同じ5表の JOIN を使って、注文ごと(o.id)の合計金額(SUM(oi.quantity * p.price))を取り出してください。
ヒント1を見る
GROUP BY o.id でまとめ、金額は SUM(oi.quantity * p.price)。
ヒント2を見る
SELECT o.id, SUM(oi.quantity * p.price) AS 合計 FROM order_items oi JOIN products p ON oi.product_id=p.id GROUP BY o.id;