コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB

第2正規形

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

第 2 正規形

このレッスンで分かること

  • 部分関数従属とは何か
  • 複合主キーを持つテーブルが直面する 2NF の問題
  • 2NF に進む具体的なリファクタ手順

第2正規形 とは

部分関数従属を排除し複合キーの依存を整理する。本レッスンでは、第2正規形 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

2NF の定義

第 2 正規形 (2NF) とは、1NF を満たしつつ、主キーの全体に対して関数従属している 状態を指します。逆に言えば、主キーの 一部 だけで決まる列があれば 2NF 違反です。これを 部分関数従属 (Partial Functional Dependency) と呼びます。

2NF は 複合主キーを持つテーブル でのみ問題になります。単一カラムの主キーなら、すでに 2NF を満たしています。

違反例

注文明細テーブルを考えます。主キーは (order_id, product_id) の複合キーです。

order_idproduct_idquantityproduct_nameproduct_price
11002Apple500
12003Banana300
21001Apple500

ここで関数従属を書き出します。

  • (order_id, product_id) -> quantity — 主キー全体が決まれば数量が決まる(OK)
  • product_id -> product_name — 商品 ID だけで商品名が決まってしまう(部分従属)
  • product_id -> product_price — 商品 ID だけで価格が決まる(部分従属)

product_nameproduct_price主キーの一部 だけで決まっています。これが 2NF 違反です。

何が起きるか

  • 商品名を変更したい場合、その商品を含む全注文行を更新する必要がある
  • まだ注文されていない商品を登録できない(挿入異常)
  • ある商品の全注文を削除すると、商品マスタが消える(削除異常)

これは「ユーザー情報を注文行に書いていた」例と同じ構造の問題です。

2NF に直す手順

diagram (will load when visible)

部分従属している列を 別テーブル に切り出します。

SQL クエリ

-- 違反前 CREATE TABLE order_items ( order_id INT, product_id INT, quantity INT, product_name VARCHAR(50), product_price INT, PRIMARY KEY (order_id, product_id) ); -- 違反後(2NF) CREATE TABLE products ( id INT PRIMARY KEY, name VARCHAR(50), price INT ); CREATE TABLE order_items ( order_id INT, product_id INT, quantity INT, PRIMARY KEY (order_id, product_id), FOREIGN KEY (product_id) REFERENCES products(id) );

これで product_name の変更は products の 1 行更新で済み、まだ注文されていない商品の登録も可能になります。

よくある間違い

価格は商品マスタに置いていいのか

「注文時点の価格」と「現在の商品価格」は本来別物です。マスタの価格を直接参照すると、過去の注文額が変わってしまいます。order_items には unit_priceスナップショット として保持するのが定石です。

SQL クエリ

CREATE TABLE order_items ( order_id INT, product_id INT, quantity INT, unit_price INT, -- 注文時点の価格を記録 PRIMARY KEY (order_id, product_id) );

これは 2NF 違反ではありません。unit_price(order_id, product_id) の組に従属しているからです。「いつの値か」が違えば別の属性 という発想です。

単一主キーなら 2NF を気にしなくていい

id 一本の主キーなら部分従属は定義上ありえません。実務では代理キー id を使うことで、2NF を自動的にクリアできます。だからこそ複合主キーには注意が必要です。

「主キーの一部だけで決まる列がないか」を毎回チェックする習慣をつけよう。

関数従属図の書き方

設計レビューでは、矢印を使って従属関係を可視化します。

プレーンテキスト

(order_id, product_id) --> quantity --> unit_price product_id --------------> name --> price order_id ----------------> user_id --> created_at

主キーの一部から伸びる矢印があれば、そこが切り出し候補です。

トレードオフ

2NF に進むと JOIN が増えます。商品名を取得するには order_itemsproducts の結合が必要です。読み取りが多いシステムでは、ビューやマテリアライズドビューで対処します。

やってみよう

  • order_itemscategory カラムを足すと 2NF 違反か考える(答え:商品カテゴリは product_id で決まるので違反)
  • 自分のプロジェクトで複合主キーを持つテーブルを洗い出す
  • 各カラムが「主キー全体」「主キー一部」のどちらに従属しているか書き分ける

よくある質問

Q. なぜ正規化するのですか?

A. データの重複を排除し、更新時の不整合(更新異常・挿入異常・削除異常)を防ぐためです。第 3 正規形まで適用すると保守性が大きく上がります。一方で JOIN が増えるため、検索性能と保守性のバランスで非正規化することもあります。

Q. 1NF と 2NF の違いは?

A. 1NF は「各セルが単一値」、2NF は「主キーの一部だけに依存するカラムをテーブル分割」する形です。複合キーの片方だけに紐づく属性は別テーブルに切り出すと 2NF になります。1NF を満たさないと 2NF 以降の議論はできません。

Q. 全部正規化すれば最適ですか?

A. 理想ですが、JOIN が増えてパフォーマンスが落ちる場面では非正規化(カラム複製、計算結果のキャッシュ)も有効です。OLTP は 3NF、レポーティング用 DWH はスタースキーマ(非正規化)と、用途で使い分けるのが現代的なアプローチです。

次のレッスン

次は 第3正規形 で、部分関数従属を排除し複合キーの依存を整理する を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

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

理解度チェック (30 秒)

Q. 第2正規形 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン