- データベース
- 技術 💻
データベースの正規化をわかりやすく|第1〜第3正規形の例
データベースの正規化とは、同じデータを複数の場所に重複して持たないように、テーブルを分割していく設計の手順です。目的は、データを追加・更新・削除したときに、矛盾したデータが生まれないようにすることです。
実務では、多くの場合第3正規形まで正規化すれば十分とされています。この記事では、1枚の注文伝票を例に、非正規形から第1・第2・第3正規形へと、テーブルが分かれていく過程を順を追って解説します。あわせて、正規化しないと何が起きるのか、実務であえて正規化を崩すのはどんなときか、試験で問われやすいポイントまで紹介します。
正規化しないと起きる3つの異常
次の表は、注文の情報を1つの表にまとめたものです。
| 注文番号 | 注文日 | 顧客ID | 顧客名 | 住所 | 商品ID | 商品名 | 単価 | 数量 |
|---|---|---|---|---|---|---|---|---|
| 1001 | 9/1 | C01 | 山田 | 東京都 | P01 | ノート | 200 | 3 |
| 1001 | 9/1 | C01 | 山田 | 東京都 | P02 | ペン | 100 | 5 |
| 1002 | 9/2 | C02 | 佐藤 | 大阪府 | P01 | ノート | 200 | 1 |
| 1003 | 9/3 | C01 | 山田 | 東京都 | P03 | 消しゴム | 80 | 2 |
一見わかりやすい表ですが、同じ情報が何度も出てきます(山田さんの住所が3回、ノートの単価が2回)。このため、次の3つの問題(異常)が起きます。
| 異常 | 例 | 起きること |
|---|---|---|
| 更新時異常 | 山田さんが引っ越した | 3行すべての住所を更新する必要がある。1行でも漏れると、どれが正しい住所かわからなくなる |
| 挿入時異常 | 新しい商品を登録したい | 注文がないと、商品だけを登録する行が作れない |
| 削除時異常 | 注文1002を取り消した | 佐藤さんの顧客情報まで消えてしまう |
正規化は、この3つの異常をなくすための手順です。
正規化の前に:2つの用語
正規化の説明では、次の2つの言葉がよく出てきます。
- 関数従属 — ある項目の値が決まると、別の項目の値が1つに決まる関係。「顧客IDが決まれば、顧客名が決まる」とき、「顧客名は顧客IDに関数従属する」と言い、「顧客ID → 顧客名」と書きます
- 候補キー — その値が決まれば、行が1つに決まる項目(または項目の組み合わせ)。候補キーのうち、代表として選んだものが主キーです
正規化は、「どの項目が、どの項目に従属しているか」を整理し、従属する相手ごとにテーブルを分ける作業だと考えると理解しやすくなります。
非正規形:1つの行に繰り返しがある
紙の注文伝票をそのまま表にすると、1つの注文に複数の商品が並びます。
| 注文番号 | 注文日 | 顧客ID | 顧客名 | 住所 | 商品(商品ID・商品名・単価・数量) |
|---|---|---|---|---|---|
| 1001 | 9/1 | C01 | 山田 | 東京都 | P01・ノート・200・3 / P02・ペン・100・5 |
| 1002 | 9/2 | C02 | 佐藤 | 大阪府 | P01・ノート・200・1 |
1つのセルに複数の値が入っている(繰り返し項目がある)状態を、非正規形と呼びます。このままではSQLで「ペンを買った注文」を探すことも、数量を合計することも困難です。
第1正規形:1つのセルに1つの値
繰り返し項目をなくし、1つのセルに1つの値だけが入るようにした状態が第1正規形です。商品ごとに行を分けると、冒頭の表になります。
| 注文番号 | 注文日 | 顧客ID | 顧客名 | 住所 | 商品ID | 商品名 | 単価 | 数量 |
|---|---|---|---|---|---|---|---|---|
| 1001 | 9/1 | C01 | 山田 | 東京都 | P01 | ノート | 200 | 3 |
| 1001 | 9/1 | C01 | 山田 | 東京都 | P02 | ペン | 100 | 5 |
| 1002 | 9/2 | C02 | 佐藤 | 大阪府 | P01 | ノート | 200 | 1 |
この表の主キーは、「注文番号+商品ID」の組み合わせです(同じ注文の中で、同じ商品は1行)。
「電話番号1、電話番号2、電話番号3」のように、同じ種類の項目を列として並べるのも繰り返し項目の一種です。これも別のテーブル(または行)に分けるのが第1正規形の考え方です。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「データベースの正規化をわかりやすく|第1〜第3正規形の例」
第2正規形:主キーの一部だけに従属する項目を分ける
第1正規形の表で、各項目が何に従属しているかを整理します。
| 項目 | 何が決まれば決まるか |
|---|---|
| 注文日・顧客ID・顧客名・住所 | 注文番号だけで決まる |
| 商品名・単価 | 商品IDだけで決まる |
| 数量 | 注文番号+商品IDの両方で決まる |
主キーは「注文番号+商品ID」ですが、注文日や商品名は、主キーの一部だけで決まります。これを部分関数従属と呼びます。
部分関数従属をなくし、主キーの一部だけで決まる項目を別のテーブルに分けた状態が第2正規形です。
注文テーブル(主キー:注文番号)
| 注文番号 | 注文日 | 顧客ID | 顧客名 | 住所 |
|---|---|---|---|---|
| 1001 | 9/1 | C01 | 山田 | 東京都 |
| 1002 | 9/2 | C02 | 佐藤 | 大阪府 |
| 1003 | 9/3 | C01 | 山田 | 東京都 |
商品テーブル(主キー:商品ID)
| 商品ID | 商品名 | 単価 |
|---|---|---|
| P01 | ノート | 200 |
| P02 | ペン | 100 |
| P03 | 消しゴム | 80 |
注文明細テーブル(主キー:注文番号+商品ID)
| 注文番号 | 商品ID | 数量 |
|---|---|---|
| 1001 | P01 | 3 |
| 1001 | P02 | 5 |
| 1002 | P01 | 1 |
| 1003 | P03 | 2 |
これで、注文がなくても商品を登録できるようになり(挿入時異常の解消)、商品名の変更も1か所で済みます。
主キーが1つの項目だけでできている表は、部分関数従属が起こりえないため、第1正規形であれば自動的に第2正規形を満たします。
第3正規形:主キー以外の項目に従属する項目を分ける
第2正規形の注文テーブルを見ると、まだ山田さんの情報が繰り返されています。
- 注文番号 → 顧客ID(注文が決まれば、顧客が決まる)
- 顧客ID → 顧客名・住所(顧客が決まれば、名前と住所が決まる)
顧客名と住所は、主キー(注文番号)から、顧客IDを経由して間接的に決まっています。これを推移的関数従属と呼びます。
推移的関数従属をなくし、主キー以外の項目で決まる項目を別のテーブルに分けた状態が第3正規形です。
注文テーブル
| 注文番号 | 注文日 | 顧客ID |
|---|---|---|
| 1001 | 9/1 | C01 |
| 1002 | 9/2 | C02 |
| 1003 | 9/3 | C01 |
顧客テーブル
| 顧客ID | 顧客名 | 住所 |
|---|---|---|
| C01 | 山田 | 東京都 |
| C02 | 佐藤 | 大阪府 |
これで、山田さんの住所の変更は1行だけで済み(更新時異常の解消)、注文を削除しても顧客情報は残ります(削除時異常の解消)。
最終的なテーブル構成
| テーブル | 主キー | 項目 |
|---|---|---|
| 顧客 | 顧客ID | 顧客名、住所 |
| 商品 | 商品ID | 商品名、単価 |
| 注文 | 注文番号 | 注文日、顧客ID |
| 注文明細 | 注文番号+商品ID | 数量 |
1つの表が、「顧客」「商品」「注文」「注文明細」という、現実の物事の単位に分かれたことがわかります。正規化に慣れてくると、最初から「この項目は何についての情報か」を考えて、このような構成を設計できるようになります。
実務のポイント:注文時の単価は、注文明細にも持つ
ここで、実務で必ず問題になる点があります。上の構成では、単価は商品テーブルにしかありません。商品の値上げで単価を変更すると、過去の注文の金額まで変わってしまいます。
実務では、注文明細に「注文時の単価(販売単価)」を持たせるのが一般的です。
| 注文番号 | 商品ID | 数量 | 販売単価 |
|---|---|---|---|
| 1001 | P01 | 3 | 200 |
これは正規化に反しているように見えますが、そうではありません。商品テーブルの単価は「現在の定価」、注文明細の販売単価は「その注文のときの価格」で、意味の違う別のデータです。値引きや、時期によって変わる価格を正しく記録するためにも必要です。
同じ考え方で、配送先住所も、顧客テーブルの住所とは別に、注文ごとに持つのが一般的です。顧客が引っ越しても、過去の注文の配送先は変わらないからです。
「そのデータは、いつの時点の事実か」を考えることが、正規化を実務に活かすうえでの大切な視点です。
第3正規形より先の正規形
| 正規形 | 条件(概要) |
|---|---|
| ボイスコッド正規形(BCNF) | すべての関数従属について、決定する側が候補キーである |
| 第4正規形 | 互いに独立した複数の「多値」の情報を、1つのテーブルに持たない |
| 第5正規形 | それ以上、情報を失わずに分割できない状態 |
第3正規形のテーブルでも、候補キーが複数あって重なっている場合などに、まれに異常が残ることがあり、それを解消したのがボイスコッド正規形です。実務では、第3正規形(場合によってはボイスコッド正規形)までを目標にするのが一般的で、第4・第5正規形を意識する場面は多くありません。
あえて正規化を崩す:非正規化
正規化するとテーブルが増え、データを取り出すときにJOIN(結合)が必要になります。そのため、次のような場合には、あえて正規化を崩す(非正規化)ことがあります。
| 場面 | 非正規化の例 | 注意点 |
|---|---|---|
| 集計に時間がかかる | 注文テーブルに「合計金額」を持たせる | 明細を変更したら、合計も確実に更新する |
| 表示のたびに多くのJOINが必要 | 一覧表示用に、必要な項目をまとめたテーブルを作る | 元のデータとの同期の仕組みが必要 |
| 分析・レポート用のデータ | データウェアハウスで、あえて横に広い表にする | 更新ではなく、分析の速さを優先する用途 |
非正規化は、正規化した設計を理解したうえで、性能の問題が確認できたときに、理由を持って行うのが原則です。最初から崩した設計にすると、更新時の矛盾に悩まされることになります。
正規化の手順のまとめ
| 段階 | やること | 解消するもの |
|---|---|---|
| 非正規形 → 第1正規形 | 繰り返し項目をなくし、1セル1値にする | 繰り返し項目 |
| 第1正規形 → 第2正規形 | 主キーの一部だけで決まる項目を分ける | 部分関数従属 |
| 第2正規形 → 第3正規形 | 主キー以外の項目で決まる項目を分ける | 推移的関数従属 |
試験で問われやすいポイント
基本情報技術者試験・応用情報技術者試験・データベーススペシャリスト試験では、正規化が頻出です。
- 第2正規形と第3正規形の違いを、「部分関数従属」と「推移的関数従属」という言葉で説明できるようにする
- 表を見て、関数従属の関係(A → B)を書き出す練習をすると、どの正規形かを判定しやすくなる
- 「第3正規形である」ためには、第2正規形の条件も満たしている必要がある(段階を飛ばせない)
- 候補キーが複数ある問題では、非キー属性(どの候補キーにも含まれない項目)が何かを正しく見極める
- データベーススペシャリスト試験では、ボイスコッド正規形や、関数従属から候補キーを求める問題も出題される
よくある質問
Q. 正規化はどこまでやればいいですか? A. 実務では、第3正規形までを目標にするのが一般的です。そのうえで、性能などの理由があれば、部分的に非正規化します。
Q. 正規化するとSQLが遅くなりませんか? A. JOINが増えるため、遅くなる場合もあります。ただし、適切なインデックスがあれば、多くの場合は問題になりません。遅くなった部分だけを、実行計画で確認してから対策するのが確実です。
Q. IDの列(サロゲートキー)を主キーにすれば、正規化は不要ですか? A. 不要にはなりません。IDを主キーにしても、同じ情報を複数の行に重複して持っていれば、更新時の異常は起きます。正規化は「どの情報が、何に従属しているか」を整理する作業で、主キーの選び方とは別の問題です。
Q. NoSQLでも正規化は必要ですか? A. NoSQLのデータベースでは、読み取りの速さを優先して、あえて重複を持たせる設計がよく使われます。その場合も、重複したデータをどう更新するかを、アプリケーション側で設計する必要があります。
まとめ
- 正規化は、重複をなくして、更新・挿入・削除の異常を防ぐためのテーブル設計の手順
- 第1正規形:繰り返しをなくす。第2正規形:主キーの一部だけで決まる項目を分ける。第3正規形:主キー以外の項目で決まる項目を分ける
- 結果として、テーブルは顧客・商品・注文・注文明細のような、現実の物事の単位に分かれる
- 注文時の単価や配送先は、現在の値とは別の事実として持つ
- 実務は第3正規形が目標。非正規化は、性能の問題を確認してから、理由を持って行う
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「データベースの正規化をわかりやすく|第1〜第3正規形の例」
株式会社RJCでは、テーブル設計のような土台の技術を、未経験から入社したメンバーも含めて一緒に学んでいます。こうした知識を仲間と共有しながら、一緒に成長を楽しんでいます。
株式会社RJC ― SI事業・SES事業・AI駆動開発。未経験からエンジニアに育てるのが得意な会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。