データベース正規化とは?第1〜第3正規形の手順と非正規化の判断を解説
データベース正規化とは、1つの表に詰め込まれたデータを、重複が生まれない単位まで複数のテーブルへ分解する設計手順です。目的は容量の節約ではなく、同じ事実が2か所以上に書かれている状態を無くして、更新の取りこぼしによる矛盾を構造で防ぐことです。この記事では、正規化の目的と更新時異常が起きる仕組み、判断の土台になる関数従属の見つけ方、第1正規形から第3正規形・ボイスコッド正規形までの分解手順、SQLでのテーブル分割と稼働中システムでの段階的な移行、そして非正規化を採るかどうかの判断基準までを実装者の目線で整理します。データベースそのものの種類や選定はデータベースとは何かを実装目線で解説した記事で扱うため、ここでは論理設計の一手順である正規化に絞ります。
まとめ:データベース正規化の手順と非正規化の判断を先に要約
先に結論を示します。実務での正規化は、第3正規形までを基本線に置き、そこから先へ進めるかどうかは案件ごとに判断する、という運用で足ります。第4・第5正規形まで機械的に適用する場面は限られ、代わりに押さえるべきはボイスコッド正規形で拾える「複合キーの中に隠れた従属」です。手順としては、実データから関数従属を洗い出し、繰り返し項目を分け(第1正規形)、複合キーの一部にだけ従属する列を切り出し(第2正規形)、主キー以外の列に従属する列を切り離す(第3正規形)という順に進めます。
非正規化については、速度が出ないという理由だけで重複を持たせる判断を先に採らないでください。結合が遅い原因の多くは正規化そのものではなく、索引の不足や取得件数の設計にあります。順序としては、索引の見直し、集計結果を別テーブルへ切り出す設計、参照専用のビューやマテリアライズドビューの3つを先に試し、それでも要件に届かない場面だけ重複を持たせる。重複を持たせると決めたなら、書き込み側で必ず同期を取る仕組みまで含めて設計します。以下で、定義から手順、移行、判断までを順に具体化していきます。
データベース正規化とは何か|定義と目的・更新時異常が起きる仕組み
まず、正規化という言葉が指す作業と、なぜ表を分けるのかという目的を押さえます。分けない設計で何が壊れるかを先に見ると、各正規形の意味が結果として理解しやすくなります。
データベース正規化の定義と、重複排除で更新時異常を防ぐという目的
正規化は、リレーショナルデータベースにおいて、1つの事実が1か所にだけ記録される状態へテーブルを分解する作業を指します。関係モデルを1970年に発表したE.F.Coddが、その翌年にあたる1971年に第1〜第3正規形として定式化しました。分解の判断基準になるのは、列と列の間にある依存の向きです。よく誤解されるのが目的で、ディスク容量を減らすためだと説明されることがありますが、記憶装置が安価になった現在でも正規化の価値は落ちていません。狙いは、同じ値を複数の行に書き写す状態を無くし、更新漏れという人為ミスがそもそも発生しえない構造を作ることにあります。設計の全体像とDBMSが担う機能はDBMSとは何かを整理した記事で扱っています。
非正規形のテーブルで起きる更新時異常・挿入時異常・削除時異常
正規化されていないテーブルで起きる不都合は、3種類に整理されます。注文明細に顧客名と顧客住所を毎行コピーしている表を想像してください。第1に更新時異常で、顧客が引っ越したとき、その顧客の全行を漏れなく書き換えないと同じ顧客に2つの住所が存在する状態になります。第2に挿入時異常。まだ1件も注文していない顧客を登録したくても、注文明細の行がなければ顧客情報を入れる場所がありません。第3が削除時異常で、最後の注文を取り消すと顧客情報まで一緒に消えてしまう。この3つは運用の注意で防ぐものではなく、テーブルの形が原因で起きます。
正規化の土台になる関数従属と候補キー・主キーの決め方と確認手順
分解の判断は、関数従属という関係で行います。列Aの値が決まれば列Bの値が1つに定まるとき、BはAに関数従属していると表現する。たとえば顧客IDが決まれば顧客名は1つに決まるので、顧客名は顧客IDに関数従属します。次に、行を一意に識別できる列の組み合わせを候補キーと呼び、その中から実際に使うものを主キーとして選びます。候補キーが複数ある場合は、値が変わらないもの、桁数が短いもの、業務上の意味に左右されないものを優先する。ここまで決まれば、あとは「主キー以外の列が、主キー全体にだけ従属しているか」を確かめる作業になります。エンティティと関連を図で整理する手法はER図の書き方を解説した記事が対応します。
第1正規形から第3正規形まで|非正規形を段階的に分解する手順
ここからが正規化の本体です。注文管理の表を例に、非正規形から第3正規形まで、どの列をどこへ動かすかを順に見ていきます。
第1正規形|繰り返し項目を排して1つのセルに1つの値を入れる
最初の段階は、1つのセルに複数の値を詰め込んだ状態を解消することです。1件の注文行に「商品名1・数量1・商品名2・数量2」と列を横に並べたり、1つのセルへ商品名をカンマ区切りで入れたりしている表が該当します。この形では、3つ目の商品を追加するたびに列を足す羽目になり、特定の商品を含む注文を検索する処理も書けません。解消の手順は、繰り返す部分を別の行として縦に展開し、注文本体と注文明細の2つのテーブルに分けることです。
-- 非正規形(1行に商品を横並び)
orders(order_id, order_date, customer_id, item1_name, item1_qty, item2_name, item2_qty)
-- 第1正規形(繰り返しを別テーブルの行へ展開)
orders(order_id, order_date, customer_id)
order_items(order_id, line_no, item_name, item_qty)
注文明細側の主キーは、注文IDと行番号の組み合わせという複合キーになります。この時点でテーブルは2つに増えますが、商品数の上限が消え、検索も集計も素直なSQLで書けるようになりました。SQLの文の種類と実行順序を解説した記事も併せて参照できます。
第2正規形|複合キーの一部にだけ従属する部分関数従属を排除する
第2正規形は、複合キーを持つテーブルにだけ関係する段階です。第1正規形を満たしたうえで、主キーの一部分にだけ従属する列を切り出します。先ほどの注文明細に、商品名と商品単価が入っているとしましょう。主キーは注文IDと行番号の組み合わせですが、商品名と単価は商品IDだけで決まり、注文IDには依存しません。これが部分関数従属で、同じ商品が別の注文に現れるたびに商品名が重複します。
-- 第1正規形(部分関数従属が残る)
order_items(order_id, line_no, item_id, item_name, item_price, item_qty)
-- 第2正規形(商品の属性を別テーブルへ)
order_items(order_id, line_no, item_id, item_qty, unit_price_at_order)
items(item_id, item_name, item_price)
ここで実務上の分岐が1つあります。注文時点の単価は、商品マスタの現在価格とは別に保持する必要があるデータです。商品の値上げで過去の注文金額が変わってしまう事態を避けるため、注文明細側にunit_price_at_orderのような列を残す設計を採ります。これは正規化の例外ではなく、注文時点の価格という別の事実を記録していると解釈できます。
第3正規形|主キー以外の列に従属する推移的関数従属を切り離す
第3正規形は、主キー以外の列を経由した依存を排除する段階です。注文テーブルに顧客IDだけでなく顧客名と顧客住所も入っている場合、顧客名は注文IDではなく顧客IDによって決まります。主キーから顧客IDへ、顧客IDから顧客名へと2段階で決まるこの形を推移的関数従属と呼び、顧客テーブルへの切り出しで解消します。
-- 第2正規形(推移的関数従属が残る)
orders(order_id, order_date, customer_id, customer_name, customer_address)
-- 第3正規形(顧客の属性を別テーブルへ)
orders(order_id, order_date, customer_id)
customers(customer_id, customer_name, customer_address)
この分解によって、住所変更は顧客テーブルの1行を書き換えるだけで済みます。冒頭に挙げた更新時異常・挿入時異常・削除時異常の3つは、第3正規形まで進めた時点でほぼ解消される。実務で「正規化する」と言うとき、多くの現場が指しているのはここまでの範囲です。
ボイスコッド正規形と第4・第5正規形をどこまで適用するかの線引き
第3正規形の先にも複数の段階があります。ボイスコッド正規形(BCNF)は1974年にRaymond F. BoyceとE.F.Coddが提案したもので、候補キーが複数あって互いに列を共有する場合に残る依存を扱うものです。担当者・科目・教室のように候補キーが重なるテーブルでは、第3正規形を満たしていても異常が残ることがあり、BCNFで拾えます。第4正規形(1977年提案)は多値従属を、第5正規形(1979年提案)は結合従属を扱う段階ですが、適用が必要な形のテーブルは実務ではまれです。
| 正規形 | 排除する依存 | 実務での適用 |
|---|---|---|
| 第1正規形 | 繰り返し項目 | 必ず適用する |
| 第2正規形 | 部分関数従属 | 複合キー時に必須 |
| 第3正規形 | 推移的関数従属 | 基本線として適用 |
| BCNF | 候補キー重複の依存 | 候補キー複数なら確認 |
| 第4・第5正規形 | 多値従属・結合従属 | 該当時のみ検討 |
線引きの目安はこうです。まず第3正規形まで進める。候補キーが複数あるテーブルだけBCNFを確認し、多値の組み合わせが独立して増減する列を見つけたときだけ第4正規形を検討する。試験対策では第5正規形まで暗記が要りますが、設計の現場では「該当するテーブルがあるか」を確認する運用で足ります。
正規化をSQLで実装する手順|テーブル分割と稼働中システムの移行
理屈が分かっても、既にデータが入っているテーブルをどう分けるかは別の問題です。調査から移行までの実装手順を、稼働中のシステムを前提に整理します。
既存テーブルから関数従属を洗い出す調査と分割案の作り方と検証手順
新規設計と違い、既存テーブルでは従属関係が資料に書かれていないことがほとんどです。調べ方は実データからの逆算になります。ある列Aの値ごとに別の列Bの値が何種類あるかをGROUP BYで数え、すべて1種類ならBはAに関数従属している疑いが濃い。ただしデータ量が少ない時期はたまたま1種類に見えるだけの場合もあるため、業務側への確認と併せて判断します。
-- customer_id ごとに customer_name が何種類あるかを数える
SELECT customer_id, COUNT(DISTINCT customer_name) AS name_variations
FROM orders
GROUP BY customer_id
HAVING COUNT(DISTINCT customer_name) > 1;
この問い合わせが0行を返せば、顧客名は顧客IDに従属していると見なせます。1行以上返る場合は、表記ゆれか、同じIDで名称変更が起きたかのどちらかで、分割前にデータの寄せ方を決める必要がある状態です。ここを飛ばして分割すると、移行時に外部キー制約が張れず作業が止まります。
分割先へデータを移すSQLと外部キー制約・一意制約の張り方と確認手順
分割の手順は、新テーブルの作成、既存データからの重複排除つき投入、参照列の付け替え、制約の付与という順序です。投入はINSERT INTO SELECT DISTINCTで行い、投入後に一意制約と外部キー制約を張って、以降のデータで重複や不整合が生まれない状態にします。
CREATE TABLE customers (
customer_id BIGINT PRIMARY KEY,
customer_name VARCHAR(120) NOT NULL,
customer_address VARCHAR(255)
);
INSERT INTO customers (customer_id, customer_name, customer_address)
SELECT DISTINCT customer_id, customer_name, customer_address FROM orders;
ALTER TABLE orders
ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (customer_id) REFERENCES customers (customer_id);
制約を張るところまでを1つの作業単位にしてください。制約なしで運用を続けると、アプリ側のバグで存在しない顧客IDを持つ注文行が入り、後から張ろうとしても失敗します。移行のSQLは複数文をまとめて実行するため、途中で失敗したときに中途半端な状態を残さないよう、トランザクションで囲んで実行する。トランザクションとACID・分離レベルを解説した記事に、囲む単位の決め方を整理しています。
稼働中のシステムで正規化を進める段階的な移行と切り戻しの設計
止められないシステムでは、一度の切り替えではなく段階を踏みます。手順は4つに分かれます。第1段階で新テーブルを作り、既存データを投入したうえで、書き込み時に旧列と新テーブルの両方を更新する二重書き込みに切り替える。第2段階では読み取りだけを新テーブルへ寄せ、旧列は残したまま値の一致を定期的に照合します。第3段階で照合が安定したらアプリの参照を完全に移し、第4段階で旧列を削除する、という順序です。
この進め方の利点は、各段階で切り戻せる点にあります。第2段階までは旧列が生きているため、問題が出れば読み取りを戻すだけで復旧できる。逆に、新テーブルへの移行と旧列の削除を同じリリースに含めると、切り戻し手段が失われます。旧列の削除は、照合で不一致が出ない期間を1〜2週間ほど確認してから別のリリースで行うのが安全でしょう。
正規化と非正規化のどちらを採るか|受託開発の現場で線を引く基準
検索上位の解説は第3正規形までの手順で終わるものが大半です。実務で判断が要るのは、どこまで正規化し、どこで重複を許すかという線引きになります。条件で整理します。
第3正規形を基本線に置いて正規化を進めてよい判断条件と適用範囲
結論から言えば、業務システムの基幹データは第3正規形を初期値にします。この判断が妥当なのは、金額・在庫・契約のように矛盾が業務事故に直結するデータ、複数の画面や外部連携から同じデータが更新されるシステム、そして数年にわたって仕様変更が続く前提の案件です。正規化された構造は、後から属性を足すときの影響範囲が1テーブルに閉じるため、変更に強い。逆に重複を持つ構造は、初期の開発速度こそ出るものの、変更のたびに更新箇所を探す作業が発生します。受託開発では引き渡し後の保守を見据えるため、この差が総コストに効いてきます。
非正規化を採る前に試す3つの代替手段と、それでも採る場面の判断基準
正規化すると結合が増え、検索が遅くなるという指摘があります。ただし、遅い原因を測らずに重複を持たせる判断へ飛ぶのは順序が違います。試すべき代替手段は3つです。
- 索引の見直し:結合列と絞り込み条件の列に索引が張られているかを実行計画で確認する
- 集計テーブルの分離:日次の売上集計など、参照が重い集計だけを別テーブルへ切り出して定期更新する
- ビューの利用:結合の記述を共通化し、DBMSによってはマテリアライズドビューで結果を保持する
この3つを試しても要件に届かない場面はあります。数億行の履歴テーブルに対する一覧表示、1秒未満の応答が契約上の要件になっている検索、参照だけが桁違いに多い分析用のテーブルなどが該当します。そこで初めて、表示用の列を複製する非正規化を検討する。採ると決めたなら、複製元が変わったときに複製先を更新する仕組みを同時に作り、どちらが正で、ずれたときに何を見て直すかを設計書へ残してください。なお、結合を避けるためにアプリ側でループして1件ずつ問い合わせる書き方は非正規化ではなく別の問題で、N+1問題の仕組みと対策を解説した記事で扱っています。
正規化を見送った設計が技術的負債に変わる失敗パターンと予防策
手戻りが大きくなるのは、いくつかの典型があります。第1に、初期に「まず動かす」ことを優先して1つの巨大テーブルで作り、仕様追加のたびに列を足し続けた設計です。列が100を超えたあたりから、どの列がどの条件で使われるかを誰も説明できなくなります。第2が、複製した列の同期をアプリ側の実装だけに任せる形。実装した人が抜けた後、片方だけ更新する経路が追加されて静かにずれていきます。第3は、外部キー制約を張らずに参照関係を運用ルールで守ろうとするパターンで、テストデータの投入や一括更新の場面で必ず破れる。いずれも、設計時に構造で防げたものを運用の注意力に肩代わりさせた結果として起きています。
データベース設計を内製するか外部に任せるかの判断の分かれ目と評価基準
正規化の理屈を知ることと、既存システムを止めずに構造を作り替えることは別の技能です。内製で進められるのは、テーブル設計とSQLの経験者が社内にいて、移行後の運用まで継続して見られる場合になります。反対に、10年前に作られた巨大テーブルを分解する、データ量が増えて設計から見直す、分析基盤へ連携するために構造を整えるといった局面は、上流の判断が絡むぶん経験差が出ます。既存データ構造の調査から正規化を踏まえたテーブル設計、分析基盤への連携までを一貫して支援するのが、株式会社一創のデータ分析基盤構築・MLOps構築支援です。どこまで分解すべきか判断がつかない段階からご相談いただけます。
よくある質問
データベース正規化について検索されることが多い質問に答えます。
データベースの正規化とは簡単に言うと何ですか?
1つの表に混ざっている複数の事実を、事実ごとの表へ分ける作業です。注文の表に顧客名を毎回書き写す代わりに、顧客の表を別に作って番号でつなぐ、と考えると分かりやすいでしょう。こうしておくと、顧客の住所が変わっても1か所を直すだけで済み、書き換え漏れによる矛盾が起きません。
正規化は第3正規形まででよいのですか?
実務では第3正規形までを基本線として構いません。第3正規形まで進めれば、更新・挿入・削除で起きる異常はおおむね解消されます。候補キーが複数あって列を共有するテーブルだけボイスコッド正規形まで確認し、多値の組み合わせが独立して増減する列があれば第4正規形を検討する、という順序で足ります。
第2正規形と第3正規形の違いは何ですか?
排除する依存の種類が違います。第2正規形は、複合キーの一部分にだけ従属する列(部分関数従属)を切り出す段階で、主キーが単一列のテーブルでは自動的に満たされます。第3正規形は、主キー以外の列を経由して決まる列(推移的関数従属)を切り離す段階です。前者は主キーとの関係、後者は主キー以外の列との関係を見る、という違いになります。
正規化しすぎると遅くなるというのは本当ですか?
結合の回数が増えるのは事実ですが、遅さの原因が正規化とは限りません。多くの場合、結合列に索引がない、必要のない列まで取得している、取得件数を絞れていないといった別の要因が効いています。実行計画で遅い箇所を特定し、索引の追加や集計テーブルの分離を先に試したうえで、それでも届かない場面だけ重複を持たせる判断へ進んでください。
NoSQLでも正規化は必要ですか?
考え方は流用できますが、適用の度合いが変わります。ドキュメント型などのNoSQLは、結合を前提にしない代わりに関連データを1つの文書へまとめて保持する設計を採ることが多く、意図的に重複を許す設計です。ただし重複した値の同期は書き込み側の責任になるため、どの値が正で、更新経路をどう1本にするかは、リレーショナルデータベース以上に明確な設計が要ります。
関連記事
- データベースとは?種類・DBMSとRDB・NoSQLの選び方:正規化の前提となるデータベースの種類と設計の全体像です。
- ER図とは?書き方・エンティティやリレーションの解説:分解した結果を図として設計に落とし込む手法です。
- SQLとは?文の種類と実行順序・RDBMS方言の解説:テーブル分割と移行を書くための土台になります。
- トランザクションとは?ACID・分離レベルの実装解説:移行のSQLを囲む単位と整合性の担保の話です。
- N+1問題とは?仕組みと検出方法・ORM別の対策:結合が増えたときに現れる性能問題の代表例です。