基礎正規化

第3正規形 (3NF)

定義

第3正規形とは、第2正規形を満たし、かつ非キー属性が候補キー以外の非キー属性を経由して関数従属していない (推移関数従属を持たない) リレーションの状態をいう。

第3正規形のルール: 「別の列を経由して決まる列」を追い出す

第3正規形 (3NF) のルールは 2 つ。

  • まず 第2正規形 を満たしていること
  • 主キー以外の列が、別の「主キーでない列」を経由して決まっている 状態があってはいけない。

「経由して決まる」を専門用語で 推移関数従属 (transitive functional dependency) と呼ぶ。注文ID → 顧客ID → 顧客名 のように、真ん中の 顧客ID (主キーではない列) を挟んで 注文ID から顧客名が「間接的に」決まっているような状態。 こうなっていると同じ顧客の情報が注文の数だけ何度も繰り返され、更新時異常 の原因になる。

実践例: 2NF まで進めた受注テーブル

前ページ で 2NF に進めた結果、 受注データは 受注 / 商品 / 受注明細 の 3 テーブルに分かれた。 このうち 商品受注明細 はもう分析するべき従属関係は残っていない。 問題は 受注 テーブル — ここに顧客関連の列 (顧客名・顧客住所) がまだ残っており、 これが 3NF で扱う対象になる。

実データで見る冗長: 顧客ID が同じ行で顧客名・顧客住所が繰り返される

まず 2NF 受注テーブルを、列がどう決まっているかで色分けして眺める。 すると「同じ顧客ID の行で顧客名・顧客住所が全く同じ値を繰り返している」ことが目で見える。 これは 2NF まで進めても残っている冗長で、次に「なぜこうなるか」を関数従属で分析する。

2NF 受注テーブル (列を決定関数で色分け)
主キー (注文ID)
注文ID から直接決まる
顧客ID 経由 (推移) で決まる
受注 (2NF)
注文IDPK
注文日
顧客ID
顧客名
顧客住所
O0012026-06-01C01田中商事東京
O0022026-06-02C02山田工業大阪
O0032026-06-03C01田中商事東京

● の列 (顧客名・顧客住所) は、■ の 顧客ID が同じなら必ず同じ値になる (O001 と O003 は共に C01 なので田中商事/東京)。これは顧客の情報が「注文行ごとに複製されている」ことを意味する。

関数従属で分析すると推移経路が見つかる

上で見えた冗長がなぜ起きているかを関数従属で分析すると、注文ID → 顧客ID → 顧客名/顧客住所 という非キー属性を経由した 従属経路が見えてくる。

2NF 受注テーブルの関数従属分析
完全関数従属◆ 注文ID → 注文日, 顧客ID主キーから直接決まる
注文ID
注文日顧客ID
完全関数従属▲ 顧客ID → 顧客名, 顧客住所顧客ID は非キー属性 — この従属が推移の原因になる
顧客ID
顧客名顧客住所
推移関数従属● 注文ID → (顧客ID経由) → 顧客名, 顧客住所非キー属性 顧客ID を経由した推移関数従属 = 3NF 違反
注文ID
顧客名顧客住所

顧客ID は候補キーではない (受注テーブルの候補キーは 注文ID)。それなのに顧客ID から顧客名・顧客住所が決まっている。つまり注文ID → 顧客名・顧客住所は「注文ID → 顧客ID → 顧客名/顧客住所」という経由でしか成立しておらず、これが 3NF が排除したい推移関数従属。

3NF 化: 経由先を別テーブルに切り出す

経由の元 (顧客ID) と経由先 (顧客名・顧客住所) をまとめて 顧客 テーブルとして独立させる。 受注テーブル側には 顧客ID だけを残しておけば、 顧客の詳細が知りたい時は顧客テーブルを見に行けば分かる。

2NF → 3NF: 顧客関連を別テーブルに
2NF 受注 (顧客情報が入っている)
受注 (2NF)
注文IDPK注文日顧客ID顧客名顧客住所
O0012026-06-01C01田中商事東京
O0022026-06-02C02山田工業大阪
O0032026-06-03C01田中商事東京
3NF 化後 (受注 と 顧客 に分割 — 色は分割前と同じグループを維持)
受注
注文IDPK注文日顧客ID
O0012026-06-01C01
O0022026-06-02C02
O0032026-06-03C01
顧客
顧客IDPK顧客名顧客住所
C01田中商事東京
C02山田工業大阪
推移的に決まっていた顧客名・顧客住所を 顧客 テーブルに集約。 受注テーブルには 顧客ID だけが残る。 これで顧客の名前や住所を変更する時も 1 行だけ更新すれば、 その顧客が持つ全ての注文の表示に自動で反映される。

3NF 化 完了後のスキーマ全体像

1NF から出発して、2NF・3NF と分割してきた結果、 最終的に以下の 4 テーブル のスキーマになる。

  • 受注 (注文ID, 注文日, 顧客ID) — 注文そのものの事実
  • 顧客 (顧客ID, 顧客名, 顧客住所) — 顧客マスタ
  • 商品 (商品ID, 商品名, 単価) — 商品マスタ
  • 受注明細 (注文ID, 商品ID, 数量) — 注文と商品の関連

非1NF スプレッドシートに詰め込まれていたデータが、3 段階の正規化を経て、 「顧客の事実は顧客テーブルに 1 度」「商品の事実は商品テーブルに 1 度」「注文の事実は受注テーブルに 1 度」 という 一事実一箇所 の構造になった。

練習問題

別のデータで自分でも試してみる。答えを見る前にどこに推移があるか考えてみてほしい。

練習問題 — 社員テーブルを 3NF にする
以下は社員と所属部署を 1 つの表にまとめたもの (2NF は満たしている)。 主キーは 社員ID の単一主キー。 推移関数従属を見つけ、3NF に分割せよ。
社員 (2NF)
社員IDPK氏名部署ID部署名部署所在地
E001山田D01営業部東京
E002田中D01営業部東京
E003佐藤D02開発部大阪
E004鈴木D03人事部東京
答えを見る

関数従属の分析:

  • 社員ID → 氏名, 部署ID (直接)
  • 部署ID → 部署名, 部署所在地 (部署ID は非キー属性)
  • 社員ID → 部署名, 部署所在地 (推移 = 3NF 違反)

部署関連 (部署名・部署所在地) を 部署 テーブルに切り出し、 社員テーブルには 部署ID だけを外部キーとして残す。

社員
社員IDPK氏名部署ID
E001山田D01
E002田中D01
E003佐藤D02
E004鈴木D03
部署
部署IDPK部署名部署所在地
D01営業部東京
D02開発部大阪
D03人事部東京

3NF の先には BCNF もあるが…

3NF より少し厳密な ボイス・コッド正規形 (BCNF) というものもある。 3NF が排除しきれない特殊なケース (候補キーが複数あって、その候補キー同士に従属関係があるとき) をカバーする。

ただし現実の業務モデルではこの特殊ケースは滅多に問題にならないため、実務では 3NF まで進めば十分 とされることが多い。 BCNF の詳しい話は別トピックで扱う予定 (現状は未収録)。

3NF まで進めるとこんな良いことがある

  • 「同じ情報を複数の場所に書く」問題がほぼ解消され、更新時異常の心配がなくなる
  • 顧客・商品・部署のような「マスタ (基準情報)」が自然に独立したテーブルになる
  • その後の外部キー制約・インデックス設計・トランザクション設計といった議論が単純になる

ここまで進めた上で、性能上どうしても必要な箇所についてだけ、 意識的に冗長を戻す 非正規化を検討するのが実務の定番の流れ。

よくある疑問

Q.第3正規形と BCNF の違いは?
A.3NF は非キー属性の推移従属を排除しますが、候補キーが複数ある場合の候補キー間の従属は許容します。BCNF はさらにその候補キー間の従属も排除するので、より厳密です。
Q.なぜ実務では 3NF で止めることが多いのですか?
A.3NF は更新時異常の大半を排除しつつスキーマの複雑度が過大にならないバランス点であり、BCNF 以上は現実の業務モデルで問題になる頻度が下がるためです。
Q.3NF を満たしていることをどう判定しますか?
A.各非キー属性について「候補キー以外の別の非キー属性から関数従属していないか」を確認します。していれば推移従属なので、その従属関係を別テーブルに切り出します。

関連トピック

もっと学びたい方へ(おすすめ書籍)

本セクションはAmazonアソシエイトのリンクを含みます。

オンライン個別指導

もっと深くDBを学びたい方へ。

たいてっくが、SQL・データベース設計・パフォーマンスチューニング・ IPAデータベーススペシャリスト対策まで、1対1で学習をサポートします。まずは無料相談から。

無料相談を予約する →