第2正規形 (2NF)
第2正規形 とは
データベース設計の正規化、第二正規形(2NF)について、SQLでの具体例を通して実践的に理解を深めます。
2NF の役割
第2正規形 (Second Normal Form, 2NF) は、複合主キー を持つテーブルから 部分関数従属 を取り除く段階です。1NF を満たしているテーブルでも、主キーの一部だけで決まる列があると、その情報は重複・更新異常の温床になります。2NF は「主キー全体に依存しない属性は別テーブルへ追い出す」というルールで、特に注文明細や受講管理のような結合キーを持つテーブルで効きます。
2NF を一言でいうと「主キーの一部だけで決まる列は、別テーブルに引っ越しさせる」というルールです。複合主キーのテーブルでだけ問題になる、と覚えておきましょう。
関数従属とは
「ある列の値が決まると、別の列の値が一意に決まる」関係を 関数従属 (functional dependency) と呼びます。例えば product_id が決まれば product_name も一意に決まる、という関係です。表記は product_id → product_name のように矢印で書きます。複合主キー (order_id, product_id) のうち product_id だけで決まる列があれば、それは 部分関数従属 であり 2NF 違反です。
2NF 違反のテーブル
SQL クエリ
CREATE TABLE order_items_bad (
order_id INT,
product_id INT,
product_name VARCHAR(100),
unit_price INT,
quantity INT,
PRIMARY KEY (order_id, product_id)
);このテーブルは 1NF を満たしますが、product_name と unit_price は product_id だけで決まり、order_id には依存していません。同じ商品が複数の注文に登場するたびに product_name が重複保存され、商品名を変えると更新が複数行に必要になります。
2NF 化の流れ
2NF 化したスキーマ
商品名と単価を products に切り出し、order_items には product_id の外部キーと quantity だけを残します。
SQL クエリ
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)
);これで product_name の更新は products の 1 行だけで済み、価格改定も商品マスタの 1 行で完結します。
集計の書き方は変わる
2NF 化すると JOIN は増えますが、集計の正確性は格段に上がります。
SQL クエリ
SELECT p.name, SUM(oi.quantity) AS total_qty
FROM order_items oi
JOIN products p ON p.id = oi.product_id
GROUP BY p.id, p.name
ORDER BY total_qty DESC;2NF の鍵は「複合主キー」と「部分関数従属」です。
単一主キーのテーブルは自動的に 2NF を満たします。複合キーを持つテーブルに出会ったら、各非キー列がキーのどの部分に依存しているかを必ず確認しましょう。
良い例 商品の値段を変えたい時、
products.unit_priceを 1 行更新するだけで全注文に反映される、というのが 2NF が約束する世界です。
2NF の議論は「複合主キー」を持つテーブルでのみ発生します。単一主キーのテーブルは部分関数従属が原理的に存在しないため、1NF を満たした時点で 2NF も自動的に満たします。
2NF を満たさないと何が困るか
2NF 違反のテーブルでは、同じ商品名と単価が注文の数だけ複製されます。これは単なるストレージ無駄ではなく、事実の単一性を壊す問題です。商品名を「ノート」から「A4 ノート」に変えたいとき、order_items_bad の数百行を更新しなければなりません。更新漏れがあれば、過去の注文に「ノート」と「A4 ノート」が並存し、後続の集計や検索が破綻します。
単一主キーのテーブルとの違い
注意したいのは、2NF の議論は複合主キーのテーブルにしか登場しないことです。主キーが id 1 つの users テーブルでは、非キー列はそもそも主キーの一部に依存することができないため、自動的に 2NF を満たします。複合キー (例えば (user_id, role_id) のような関連テーブル) を見たら、必ず部分関数従属が紛れていないかをチェックします。
関連テーブルでよくあるパターン
ユーザとロールの中間テーブル、注文と商品の中間テーブルなど、複合主キーを使う「関連テーブル」は 2NF の落とし穴の典型です。下記は安全なパターンの例で、関連の事実 (誰がどの役割を持つか) 以外の属性をすべて外に追い出しています。
SQL クエリ
CREATE TABLE user_roles (
user_id INT,
role_id INT,
granted_at TIMESTAMP,
PRIMARY KEY (user_id, role_id)
);granted_at は (user_id, role_id) の組合せ全体で決まる属性なので部分従属ではありません。
まとめ
- 2NF は複合主キーの部分関数従属を排除する
- 主キーの一部だけで決まる列は別テーブルへ
- 1NF を満たした上で 2NF に進む順序を守る
- JOIN は増えるが、更新異常は防げる
次のレッスン
次は 第3正規形 (3NF) です。第3正規形について解説します。効率的なデータベース設計の基礎を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- 2NF の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. 2NF とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
テーブル構造
schema.sql
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)
);
INSERT INTO products VALUES
(10, 'ノート', 300),
(11, 'ペン', 150),
(12, '消しゴム', 100);
INSERT INTO order_items VALUES
(1001, 10, 2),
(1001, 11, 4),
(1002, 10, 1),
(1003, 12, 3),
(1004, 11, 5),
(1005, 10, 6);期待される出力
| name | total_amount |
|---|---|
| ノート | 2700 |
| ペン | 1350 |
| 消しゴム | 300 |