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

第3正規形

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

第 3 正規形

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

  • 推移関数従属とは何か
  • 3NF 違反が引き起こす冗長性とその影響
  • 3NF と BCNF の違い

第3正規形 とは

推移関数従属を排除し非キー属性の独立性を確保する。本レッスンでは、第3正規形 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

3NF の定義

第 3 正規形 (3NF) とは、2NF を満たしつつ、非キー列が他の非キー列に従属していない 状態です。「主キー以外の列同士で従属関係を持たない」と言い換えてもよいです。

主キー → 非キー列 → 別の非キー列、という連鎖を 推移関数従属 (Transitive Functional Dependency) と呼びます。これを排除するのが 3NF です。

違反例

社員テーブルを考えます。

emp_idnamedept_iddept_namedept_location
1Alice10EngineeringTokyo
2Bob10EngineeringTokyo
3Carol20SalesOsaka

関数従属は次のようになります。

  • emp_id -> name — 主キー → 非キー(OK)
  • emp_id -> dept_id — 主キー → 非キー(OK)
  • dept_id -> dept_name — 非キー → 非キー(推移従属)
  • dept_id -> dept_location — 非キー → 非キー(推移従属)

dept_nameemp_id から見て dept_id を経由して決まる 列です。これが推移従属で、3NF 違反です。

diagram (will load when visible)

何が起きるか

Engineering 部門の所在地が Tokyo から Yokohama に変わったとします。すべての Engineering 所属社員の行を更新する必要があります。更新漏れがあれば、Tokyo と Yokohama が混在し、データの真実がわからなくなります。

新しい部門 Marketing を作りたくても、まだ所属社員がいないと employees テーブルに NULL 行を作るしかなく、これは明らかに不自然です。

3NF に直す手順

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

SQL クエリ

-- 違反前 CREATE TABLE employees ( id INT PRIMARY KEY, name VARCHAR(50), dept_id INT, dept_name VARCHAR(50), dept_location VARCHAR(50) ); -- 違反後(3NF) CREATE TABLE departments ( id INT PRIMARY KEY, name VARCHAR(50), location VARCHAR(50) ); CREATE TABLE employees ( id INT PRIMARY KEY, name VARCHAR(50), dept_id INT, FOREIGN KEY (dept_id) REFERENCES departments(id) );

これで部門情報の更新は departments の 1 行で完結し、空の部門も独立して作れます。

BCNF との違い

BCNF (Boyce-Codd Normal Form) は 3NF をさらに厳密にしたものです。3NF は「非キー列同士の推移従属を禁止」だけですが、BCNF は「全ての関数従属の決定項は候補キーでなければならない」と要求します。

実務で BCNF まで気にすることは稀です。3NF を満たしていれば、ほとんどの異常は防げます。逆に BCNF にしようとすると、稀に 関数従属性を保存できない 分解になることがあります。3NF は無損失分解と依存性保存の両方を常に満たせますが、BCNF は依存性保存を犠牲にする場合があるため、現場では 3NF で止めるのが定石です。

DRY 原則との関係

3NF はソフトウェア工学の DRY (Don't Repeat Yourself) 原則と同じ精神を持ちます。「同じ事実を 2 箇所に書かない」「真実は 1 箇所だけ」を守ることで、変更コストと不整合リスクを下げます。

トレードオフ

3NF にすると JOIN が増えます。社員一覧と部門名を一緒に表示するクエリは次のようになります。

SQL クエリ

SELECT e.name, d.name AS dept, d.location FROM employees e JOIN departments d ON e.dept_id = d.id;

部門数 100、社員数 100 万、検索が 1 日 1 万回でも、インデックスを貼っておけば実用上問題ありません。「JOIN を恐れて非正規化する」のは早すぎる最適化です。

3NF まではほぼ無条件にやるべき。それ以上は 計測してから 判断すること。

設計レビューのチェックリスト

新しいテーブルを追加するとき、次の質問に答えられるか確認しましょう。

  1. 全カラムは原子値か(1NF)
  2. 複合主キーがあれば、各カラムは主キー全体に従属しているか(2NF)
  3. 主キー以外のカラム同士で従属関係はないか(3NF)
  4. 外部キーは正しく設定されているか
  5. 更新異常・挿入異常・削除異常は起きないか

やってみよう

  • employeeszip_codecity を追加した場合、3NF 違反か考える(答え:zip_code -> city なので違反)
  • 自分のテーブルから「非キー列同士で従属している」列を 3 組探す
  • それぞれを別テーブルに切り出した CREATE TABLE を書く

よくある質問

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

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

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

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

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

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

次のレッスン

次は 非正規化のトレードオフ で、推移関数従属を排除し非キー属性の独立性を確保する を学びます。

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

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

理解度チェック (30 秒)

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

この章のポイント

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

関連レッスン