第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_nameunit_priceproduct_id だけで決まり、order_id には依存していません。同じ商品が複数の注文に登場するたびに product_name が重複保存され、商品名を変えると更新が複数行に必要になります。

2NF 化の流れ

diagram (will load when visible)

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 つができることを確認しましょう。

  1. 2NF の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (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);

期待される出力

nametotal_amount
ノート2700
ペン1350
消しゴム300

ヒント

query.sql
query.sql
学習モード
コードの実行結果
データベースを初期化中...