はじめに
「とりあえず動くからこのテーブル構造でいいか」——そう作ったDBが、後から地獄を生むことがあります。データの重複、更新ミス、集計のしづらさ…。
この記事では、データベース設計の基本である「正規化」 を、具体的なダメな例とその改善を通して解説します。難しい用語より「なぜそうするのか」を重視しました。
なぜ設計が重要なのか
テーブル設計は家でいう基礎工事です。あとから変更するほどコストが跳ね上がります。最初に少し時間をかけるだけで、その後の開発・運用がずっと楽になります。
ダメな例:1つのテーブルに全部詰め込む
注文管理を例にします。最初はこう作りがちです。
orders テーブル(悪い例)
| order_id | customer_name | customer_email | product1 | product2 | product3 |
|---|---|---|---|---|---|
| 1 | 田中太郎 | tanaka@x.com | りんご | みかん | |
| 2 | 田中太郎 | tanaka@x.com | バナナ |
何が問題か
- 商品が3つまでしか登録できない(product4が必要になったら?)
- 顧客情報が重複している(田中さんがメール変更したら全行を直す必要がある)
- 「りんごが何件売れたか」を集計しにくい(3つの列を横断して数える羽目に)
第1正規化:繰り返しを別の行にする
「product1, product2, product3」のような繰り返し列をなくし、1行1商品にします。
order_items テーブル
| order_id | product_name |
|---|---|
| 1 | りんご |
| 1 | みかん |
| 2 | バナナ |
これで商品は何個でも登録できるようになり、「りんごの売上件数」も WHERE product_name = 'りんご' で簡単に集計できます。
第2正規化:重複する情報を別テーブルに分ける
顧客情報が注文ごとに重複しているのが問題でした。顧客は独立した存在なので、別テーブルに切り出します。
customers テーブル
| customer_id | name | |
|---|---|---|
| 1 | 田中太郎 | tanaka@x.com |
orders テーブル
| order_id | customer_id | order_date |
|---|---|---|
| 1 | 1 | 2025-04-01 |
| 2 | 1 | 2025-04-03 |
顧客情報は customers に1件だけ。メールアドレスを変更しても1箇所直すだけで済みます。orders は customer_id で顧客を参照します。
完成形:3つのテーブルに分割
最終的にこうなります。
customers(顧客)
├ customer_id(主キー)
├ name
└ email
orders(注文)
├ order_id(主キー)
├ customer_id(外部キー → customers)
└ order_date
order_items(注文明細)
├ item_id(主キー)
├ order_id(外部キー → orders)
├ product_name
├ quantity
└ price
テーブル作成SQL
CREATE TABLE customers (
customer_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE
);
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATE NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
CREATE TABLE order_items (
item_id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT NOT NULL,
product_name VARCHAR(100) NOT NULL,
quantity INT NOT NULL,
price INT NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(order_id)
);
分割したデータを結合して取得する
テーブルを分けても、JOINで元の形に戻せます。
SELECT
o.order_id,
c.name,
o.order_date,
oi.product_name,
oi.quantity,
oi.price
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
ORDER BY o.order_id;
設計のときに意識する3つのポイント
① 1つのテーブルには1種類のものだけ
顧客・注文・商品は別の概念です。「customers」「orders」のように、1テーブル=1つの概念にします。
② データの重複をなくす
同じ情報が複数の場所にあると、更新時にズレます。「1つの事実は1箇所に」が原則です。
③ 主キーと外部キーで関連を表現する
各テーブルに一意の主キー(customer_id など)を持たせ、別テーブルからは外部キーで参照します。これで「どの注文がどの顧客のものか」を正しく表現できます。
過剰な正規化に注意
一方で、なんでも分割すればいいわけではありません。
- JOINが増えすぎてクエリが複雑になる
- 参照のたびに複数テーブルをまたぐので速度が落ちる場合がある
集計が中心のシステム(分析・レポート用途)では、あえて非正規化して1テーブルにまとめることもあります。正規化は目的ではなく手段です。用途に応じて使い分けます。
まとめ
- テーブル設計は後から直すほど高コスト。最初が肝心
- 第1正規化:繰り返し列をなくして1行1データに
- 第2正規化:重複する情報を別テーブルに切り出す
- 主キー・外部キーで関連を表現する
- ただし過剰な正規化は逆効果。用途で使い分ける
「1つの事実は1箇所に」を意識するだけで、設計の質は大きく変わります。
データベース設計・最適化のご相談
「テーブル設計を最初から相談したい」「既存DBの構造を見直したい」「データ移行をお願いしたい」などのご相談を受け付けています。