
データベース正規化 入門 - 第1〜第3正規形とBCNFを実例で理解する
正規化と設計の定番。実務で最初の一冊。
やりがちな設計の失敗と回避策を網羅。
SQLの考え方を根本から鍛える一冊。
当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。
「同じ会社名が何百行にも書いてあって、社名が変わったとき一部だけ直し忘れた」——こうしたデータの矛盾を、設計の段階で防ぐ手法が正規化(normalization)です。正規化は、テーブルを「1つの事実は1か所にだけ持つ」形へ整理し、更新・挿入・削除の異常を構造的に起こさないようにするための理論です。この記事では、関数従属や候補キーといった前提用語から、第1〜第3正規形とBCNFまでを、悪い設計テーブルを正規化していく流れで具体的に解説します。
正規化とは何か
正規化は、関係モデル(リレーショナルモデル)を提唱した Edgar F. Codd が、その一部として整理した設計理論です。1NF は1970年、2NF と 3NF は1971年に Codd が示し、BCNF は1974年に Raymond F. Boyce と Codd によって定義されました(BCNF は Ian Heath が1971年に近い概念を先に述べていた、という指摘もあります)。
ねらいは一貫していて、「1つの事実を1か所にだけ格納し、重複と矛盾の入り込む余地をなくす」ことです。同じ情報が複数の場所にコピーされていると、片方だけ更新される・整合しない値が混ざる、といった事故が起きます。正規化は、この重複をテーブル設計そのもので防ぎます。
正規形(normal form)は入れ子の関係になっていて、上位を満たすには下位も満たしている必要があります。
- 1NF ⊂ 2NF ⊂ 3NF ⊂ BCNF(右にいくほど厳しい)
- つまり BCNF を満たすテーブルは、自動的に 3NF・2NF・1NF も満たしている
なぜ正規化が必要か(3つのデータ異常)
正規化の価値は、正規化されていないと何が困るかを見ると腹落ちします。次のような「1つのテーブルに何でも詰め込んだ」設計を考えます。受注(注文)と、その明細、顧客情報、商品情報がすべて1行に同居しています。
| order_id | customer_id | customer_name | product_id | product_name | unit_price | qty |
|---|---|---|---|---|---|---|
| 1001 | C01 | 田中商店 | P10 | ノート | 200 | 3 |
| 1001 | C01 | 田中商店 | P20 | ペン | 100 | 5 |
| 1002 | C02 | 佐藤書房 | P10 | ノート | 200 | 1 |
このテーブルには、次の3つの異常(anomaly)が潜んでいます。
- 更新異常(update anomaly): 「ノート」の商品名や単価を変えるとき、
P10が出てくるすべての行を漏れなく直す必要がある。1行でも直し忘れると、同じ商品なのに名前や単価が食い違う。 - 挿入異常(insertion anomaly): まだ1件も注文されていない新商品を登録したいだけなのに、注文行が存在しないと商品を記録できない(
order_idを無理にダミーで埋めることになる)。 - 削除異常(deletion anomaly): ある注文をすべて削除したら、そこにしか載っていなかった顧客や商品の情報まで一緒に消えてしまう。
正規化は、これらの異常を「テーブルを適切に分割し、事実を1か所に集約する」ことで根本から取り除きます。
前提用語(キーと関数従属)
正規形の定義は、いくつかの用語の上に成り立っています。ここを押さえておくと、以降の話がすべて同じ枠組みで理解できます。
- 候補キー(candidate key): 行を一意に識別できる最小の属性の組。「これがあれば行が1つに決まる」かつ「どれか1列でも欠けると一意にならない」もの。
- 主キー(primary key): 候補キーの中から代表として選んだ1つ。
- スーパーキー(superkey): 行を一意に識別できる属性の組(最小でなくてもよい)。候補キーはスーパーキーのうち最小のもの。
- 非キー属性(non-key attribute): どの候補キーにも含まれない属性。
- 関数従属(functional dependency, FD): ある属性の値が決まれば別の属性の値が一意に決まる関係。「X が決まれば Y が決まる」を X → Y と書く。例:
product_id → product_name(商品IDが決まれば商品名は1つに決まる)。
さらに、正規化で除去したい2種類の「よくない従属」があります。
- 部分従属(partial dependency): 複合キー(複数列からなるキー)の一部にだけ非キー属性が従属している状態。2NF で除去する。
- 推移従属(transitive dependency): 非キー属性が、別の非キー属性を経由してキーに従属している状態(X → Y かつ Y → Z で、Z が Y 経由でキーに従う)。3NF で除去する。
NOTE
正規化は「テーブルを分割する作業」に見えますが、本質は「どの属性がどの属性に関数従属しているか(FD)を洗い出し、その従属関係にキーを合わせる」ことです。FD が見えれば、どこを分割すべきかは自然に決まります。
第1正規形(1NF)
定義: すべての列が単一の値(スカラー値)を持ち、繰り返しグループや複数値・入れ子構造を含まないこと。1つのセルには1つの値だけを入れます。
次の設計は 1NF を満たしていません。1つのセルに複数の商品をカンマ区切りで詰め込んでいます。
| order_id | customer_name | products |
|---|---|---|
| 1001 | 田中商店 | ノート:3, ペン:5 |
| 1002 | 佐藤書房 | ノート:1 |
このままでは、「ペンを含む注文を検索する」「商品ごとの数量を集計する」といった処理が SQL で素直に書けません。値がセルの中に埋もれているためです。1NF では、1行1商品になるよう行に展開します。
-- 悪い例:products 列に "ノート:3, ペン:5" のような複数値
-- 良い例:明細を1行1商品に分解する
CREATE TABLE order_lines (
order_id INT NOT NULL,
customer_name VARCHAR(100) NOT NULL,
product_name VARCHAR(100) NOT NULL,
qty INT NOT NULL,
PRIMARY KEY (order_id, product_name)
);| order_id | customer_name | product_name | qty |
|---|---|---|---|
| 1001 | 田中商店 | ノート | 3 |
| 1001 | 田中商店 | ペン | 5 |
| 1002 | 佐藤書房 | ノート | 1 |
何が嬉しいか: 1つの値が1つのセルに入ることで、WHERE product_name = 'ペン' のような検索、SUM(qty) のような集計、JOIN がすべて自然に書けるようになります。RDB の SQL は「1セル1値」を前提に設計されているため、ここがすべての土台です。
第2正規形(2NF)
定義: 1NF を満たし、かつすべての非キー属性が候補キーの全体に完全に関数従属していること。言い換えると、複合キーの一部だけに従属する部分従属を除去した状態です。
先ほどの order_lines は主キーが (order_id, product_name) の複合キーでした。ここで、各列がキーのどこに従属しているかを見てみます。
customer_nameはorder_idだけで決まる(誰の注文かは注文IDで決まる) -> キーの一部への部分従属qtyは(order_id, product_name)の両方が揃って初めて決まる -> 完全従属(これは問題なし)
customer_name が order_id だけに従属しているのが問題です。この状態だと、同じ注文の複数明細行に顧客名が繰り返し格納され、更新異常が残ります。そこで従属の単位ごとにテーブルを分割します。
-- 注文(order_id だけで決まる情報)
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id CHAR(3) NOT NULL
);
-- 注文明細(order_id と product_id の両方で決まる情報)
CREATE TABLE order_items (
order_id INT NOT NULL,
product_id CHAR(3) NOT NULL,
qty INT NOT NULL,
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id) REFERENCES orders(order_id)
);これで「顧客は注文ごとに1回だけ」記録され、数量は明細ごとに記録されます。
何が嬉しいか: 顧客名や単価のように「キーの一部だけで決まる情報」を1か所に集約できるため、注文明細を何行追加しても、その付随情報が重複しなくなります。2NF は複合キーを持つテーブルで特に重要で、単一列の主キーしかないテーブルでは部分従属が起きようがないため、1NF を満たせば自動的に 2NF になります。
第3正規形(3NF)
定義: 2NF を満たし、かつ非キー属性が別の非キー属性に従属していないこと。すなわち推移従属を除去した状態です。非キー属性は「キーに直接」従属していなければなりません。
次の products テーブルを見てください。主キーは product_id です。
| product_id | product_name | category_id | category_name |
|---|---|---|---|
| P10 | ノート | CT1 | 文具 |
| P20 | ペン | CT1 | 文具 |
| P30 | 電卓 | CT2 | 事務機器 |
ここには次の従属があります。
product_id → category_id(商品が決まればカテゴリIDが決まる)category_id → category_name(カテゴリIDが決まればカテゴリ名が決まる)
すると product_id → category_id → category_name となり、category_name は非キー属性 category_id を経由して主キーに従属しています。これが推移従属です。この状態だと「文具」というカテゴリ名が複数商品の行に重複し、カテゴリ名を変えるときに更新異常が起きます。推移従属の起点となる非キー属性(category_id)を軸に、テーブルを分けます。
CREATE TABLE categories (
category_id CHAR(3) PRIMARY KEY,
category_name VARCHAR(100) NOT NULL
);
CREATE TABLE products (
product_id CHAR(3) PRIMARY KEY,
product_name VARCHAR(100) NOT NULL,
category_id CHAR(3) NOT NULL,
FOREIGN KEY (category_id) REFERENCES categories(category_id)
);何が嬉しいか: カテゴリ名という「カテゴリという別の実体が持つべき事実」が categories に1行だけ存在するようになり、名称変更が1か所で済みます。実務で 3NF まで正規化しておけば、多くの更新・挿入・削除の異常は解消されます。「まず 3NF を目指す」のが設計の基本方針として広く使われています。
NOTE
3NF の直感的な標語として有名なのが、「非キー属性はキーに、キー全体に、キーだけに従属する(the key, the whole key, and nothing but the key)」です。「キーに」が1NFの土台、「キー全体に」が2NF(部分従属の除去)、「キーだけに」が3NF(推移従属の除去)に対応します。
ボイス・コッド正規形(BCNF)
定義: すべての非自明な関数従属 X → Y について、その決定項 X がスーパーキーであること。3NF をさらに厳しくしたもので、「候補キーが複数あり、それらが重なり合う(複合候補キー同士が属性を共有する)」ような場合に、3NF では取り切れない冗長性を除去します。
3NF は「非キー属性が推移従属していないこと」を求めますが、キーを構成する属性(キー属性)が絡む従属は見逃すことがあります。ここが 3NF と BCNF の差です。
例として、講座の予約割り当てを考えます。ルールは次の通りです。
- 各時間帯(slot)で、講師(teacher)は1人の受講者を担当する ->
(slot, teacher) → student - 各時間帯で、受講者は1人の講師に割り当てられる ->
(slot, student) → teacher - 講師は必ず1つの科目(subject)を教える ->
teacher → subject
| slot | teacher | subject | student |
|---|---|---|---|
| A1 | 山田 | 数学 | 鈴木 |
| A1 | 佐藤 | 英語 | 高橋 |
| A2 | 山田 | 数学 | 高橋 |
このテーブルの候補キーは (slot, teacher) と (slot, student) です。subject は候補キーに含まれない非キー属性で、どの候補キーにも部分従属・推移従属していないため、3NF は満たします(subject は候補キーの一部である teacher に従属していますが、3NF は「非キー属性が非キー属性に従属していないこと」を求めるだけなので、決定項がキー属性 teacher であるこのケースは 3NF 上は許容されます)。
しかし teacher → subject の決定項 teacher はスーパーキーではありません(teacher 単独では行を一意に決められない)。したがって BCNF は満たしません。実際、同じ講師の科目が複数行に重複し、講師の担当科目を変えると更新異常が起きます。BCNF にするには、teacher → subject を別テーブルへ切り出します。
-- 講師とその科目(teacher → subject を独立させる)
CREATE TABLE teachers (
teacher CHAR(10) PRIMARY KEY,
subject VARCHAR(50) NOT NULL
);
-- 割り当て(slot と teacher で student が決まる)
CREATE TABLE assignments (
slot CHAR(4) NOT NULL,
teacher CHAR(10) NOT NULL,
student VARCHAR(50) NOT NULL,
PRIMARY KEY (slot, teacher),
FOREIGN KEY (teacher) REFERENCES teachers(teacher)
);何が嬉しいか: 「講師の科目」という事実が teachers に1行だけ存在し、割り当ての行数に関わらず重複しなくなります。BCNF は「すべての決定項がスーパーキーである」という、正規化の直感を最もきれいに満たす形です。
WARNING
BCNF への分解は、まれに関数従属を保存できないことがあります(分解後、元の FD が単一テーブルの制約として表現できず、テーブルをまたいだチェックが必要になる)。多くの実務では 3NF まで到達していれば十分で、BCNF は「候補キーが重なる特殊な設計」で問題が出たときに検討する、と捉えておくと現実的です。
どこまで正規化するか・非正規化のトレードオフ
正規化は矛盾を防ぐ強力な手段ですが、「常に最大まで正規化するのが正解」ではありません。正規化を進めるほどテーブルが細かく分かれ、参照時にJOIN の回数が増えるためです。
非正規化(denormalization)は、あえて重複を持たせて読み取りを速くする設計判断です。使いどころは限定的で、次のような場合に検討します。
- 参照が圧倒的に多く、更新がまれ(更新異常のリスクが小さい)
- 多数のテーブルを JOIN する集計が重く、レスポンスが要件を満たせない
- 集計値(合計・件数など)をキャッシュ列として持ちたい
| 観点 | 正規化 | 非正規化 |
|---|---|---|
| データの重複 | 少ない | 多い(意図的) |
| 更新・挿入・削除の異常 | 起きにくい | 起きやすい(要対策) |
| 読み取り時の JOIN | 多くなりがち | 減らせる |
| 書き込みの一貫性 | 保ちやすい | アプリ側の担保が必要 |
| 向く場面 | 一般的な業務システム | 読み取り特化・集計・分析 |
非正規化を選ぶなら、重複した値をどう同期するか(トリガー、アプリ側の二重書き込み、バッチ再計算など)をセットで設計する必要があります。同期を怠れば、正規化が防いでいたはずの更新異常がそのまま再来します。
NOTE
実務の定石は「まず 3NF で正規化して正しく作り、計測してボトルネックが確認できたら、その箇所だけ意図的に非正規化する」です。最初から推測で非正規化するのは、多くの場合、早すぎる最適化になります。読み取り性能が課題なら、非正規化の前にデータベースインデックス入門で触れたインデックス設計を先に見直す価値があります。
実務のポイント
- まず FD を洗い出す: 「何が決まれば何が決まるか」を列挙するのが正規化の出発点。テーブル分割はその結果として決まる。
- 基本は 3NF を目標に: 多くの業務システムは 3NF で更新・挿入・削除の異常がほぼ解消される。BCNF は候補キーが重なる特殊ケースで検討。
- サロゲートキーと正規化は別の話:
idの連番(サロゲートキー)を主キーにしても、正規化の判断は「業務上の自然キーに対する FD」で考える。サロゲートキーを付けたから正規化できている、とはならない。連番かランダムかの選び方はUUID/ULID入門も参考になる。 - 外部キー制約を活用する: テーブルを分割したら
FOREIGN KEYで参照整合性を DB に守らせる。アプリ任せにしない。 - 非正規化は最後の手段: 計測してから、影響範囲を限定して行う。同期方法を必ずセットで設計する。
正規化はトランザクションの整合性とも密接に関わります。分割したテーブルへの複数更新を安全に確定させる仕組みはトランザクションとACID・分離レベル入門で解説しています。また、正規化されたデータを API として返す際の一貫した設計はREST API設計の基礎と冪等性も合わせて押さえると、設計の見通しが良くなります。
まとめ
- 正規化は「1つの事実を1か所にだけ持つ」ことで、更新・挿入・削除の異常を構造的に防ぐ理論。
- 前提は関数従属(FD)とキー。FD を洗い出せば、どこを分割すべきかが決まる。
- 1NF: 1セル1値(繰り返しグループの排除)。SQL のすべての土台。
- 2NF: 部分従属の除去。複合キーの一部だけに従属する属性を切り出す。
- 3NF: 推移従属の除去。非キー属性が別の非キー属性を経由してキーに従う状態を解消。「キーに、キー全体に、キーだけに」。
- BCNF: すべての決定項がスーパーキー。候補キーが重なる場合に 3NF で残る冗長性を除去。
- 非正規化は読み取り最適化のための例外的な判断。計測してから、同期方法とセットで限定的に行う。
正規化は「暗記する理論」ではなく、「事実と従属関係を見て、テーブルの形を決める考え方」です。FD を軸に考えられるようになれば、初めて見るドメインでも、破綻しないテーブル設計を自分で導けるようになります。
参考リンク
- Database normalization(Wikipedia)
- Boyce-Codd normal form(Wikipedia)
- Constraints(PostgreSQL 公式・外部キー等)
- FOREIGN KEY Constraints(MySQL 公式)


