データベースの正規化という言葉を聞いたことはあっても、「なんだか難しそう」というイメージをもっていないでしょうか。
実際は、データベースを一度でも触ったことがある人なら、正規化を理解するのはそれほど難しくありません。本記事では初めて正規化に触れる人でも直感的に理解できるよう、具体的なテーブル例を使いながら解説します。
本記事で身につくスキル
- 既存データベースが正規化のどの段階にあるのかを見分けられる。
- データベースを段階的に正規化できる(第3正規化まで)。
- 正規化のメリット・デメリットを理解し、実務的な設計判断ができる。
目次
- 正規化の概要と目的
- 非正規形:まずはここから
- 第1正規化:繰り返しをなくす
- 第2正規化:主キーの一部への依存をなくす
- 第3正規化:非キー同士の依存をなくす
- あえて正規化しないケース
- まとめ
正規化の概要と目的
リレーショナルデータベースにおける正規化とは、データの重複を排除し、一貫性と保守性を高めるために、テーブルを整理・分割する手法です。
正規化には第1から第6正規形まで定義されていますが、順番をスキップすることはできません。ほとんどのアプリケーションでは第3正規化まで行えば十分ですので、本記事でもそこまでの手順に焦点を絞ります。
メリット
- 容量削減: 重複を削ることでディスクやメモリの節約になります。
- 整合性向上: 「一部のデータだけ更新し忘れた」というミス(データ矛盾)を防げます。
- クエリの単純化: 1箇所の修正ですべてに反映されるため、更新処理が楽になります。
デメリット
- テーブル結合(JOIN)の増加: テーブルが分かれるため、データを取得する際に結合が必要になり、パフォーマンスが低下する場合があります。
非正規形:まずはここから
非正規形とは、1つのセルに複数の値が入っていたり、表の中に表があるような、整理されていない状態です。
【プロジェクト管理テーブル(非正規形)】
| プロジェクトID (PK) | プロジェクト名 | 予算 | 参加従業員 |
|---|---|---|---|
| P01 | クラウド移行 | 500万 | [(E101, 田中, 開発), (E102, 佐藤, 営業)] |
| P02 | セキュリティ監査 | 200万 | [(E101, 田中, 開発), (E103, 鈴木, 法務)] |
この状態では、SQLで「開発職のメンバーだけ抽出する」といった操作が非常に困難です。
第1正規化:繰り返しをなくす
第1正規化とは、セルの中の値を「これ以上分解できない最小単位(原子値)」にすることです。
手順
複合的なデータ(参加従業員)をバラバラにして、1行1値の状態にします。
具体的な手順:
- 元の主キー(プロジェクトID)をコピーし、従業員情報の各配列要素に追加する。例:[(P01, E101, 田中, 開発), (P01, E102, 佐藤, 営業)]
- 従業員情報を別テーブル(プロジェクト参加メンバー)とする。このテーブルの主キーは、元の主キー(プロジェクトID)と、従業員を一意に特定する配列要素の値(従業員ID)の複合キーとする。
第一正規化後のテーブル
【プロジェクト主テーブル】
| プロジェクトID (PK) | プロジェクト名 | 予算 |
|---|---|---|
| P01 | クラウド移行 | 500万 |
| P02 | セキュリティ監査 | 200万 |
【プロジェクト参加メンバーテーブル】
| プロジェクトID (PK1) | 従業員ID (PK2) | 従業員名 | 職種 |
|---|---|---|---|
| P01 | E101 | 田中 | 開発 |
| P01 | E102 | 佐藤 | 営業 |
| P02 | E101 | 田中 | 開発 |
| P02 | E103 | 鈴木 | 法務 |
これで、標準的なSQLで検索や集計ができるようになりました。
このように第1正規化されたテーブルを「第一正規形( (First Normal Form: 1NF)」と呼びます。
第一正規形の定義は、以下の通りです。
- すべてのカラムの値が原子値であり、複数値やネストされたテーブルではないこと。
第2正規化:主キーの一部への依存をなくす
第2正規化のターゲットは、複合主キー(複数の列を組み合わせた主キー)を持つテーブルです。
先ほどの「プロジェクト参加メンバーテーブル」を見てください。主キーは「プロジェクトID + 従業員ID」のセットです。
しかし、「従業員名」や「職種」は「従業員ID」が決まれば自動的に決まります。 プロジェクトIDが何であるかは関係ありません。
このように、主キーの一部(従業員ID)にだけ依存している列がある状態を部分関数従属と呼び、これを切り出すのが第2正規化です。
部分関数従属のデメリット
部分関数従属のデメリットは以下の通りで、いずれも非主キー属性においてデータ重複が存在することが原因です。
- レコード更新・削除異常、及び非効率性。
- レコード挿入異常。
上記のプロジェクト参加メンバーテーブルの例では、1行目と3行目の従業員名(田中)と職種(開発)が重複しています。
このため、もし社内移動などで職種が変わったり、退社した場合、複数行を更新・削除しなければなりません。これは非効率というだけでなく、データの一貫性をそこなう危険性を孕んでいます。例えば、1行目は更新されたが3行目は更新されなかった等の事故の可能性を排除できません(更新異常)。
また、新しい従業員が入社してまだプロジェクトにアサインされていない場合、そもそも従業員レコードを挿入することができないという事態(挿入異常)が発生します。
このような異常事態を防ぐため、部分関数従属がない状態にするのが第2正規化の目的です。
第2正規化の手順
部分関数従属を解消するため、主キーの一部にのみ依存している属性を別テーブルに分離します。
- 別テーブルを作成し、そのテーブルの単一主キーを、従属していた複合主キーの一部とする。
- 部分関数従属していた非キーカラムを新テーブルに移動する。
第2正規化後のテーブル
【プロジェクト割り当てテーブル】
どのプロジェクトに誰がいるか、という「事実」のみを管理します。
| プロジェクトID (PK1) | 従業員ID (PK2) |
|---|---|
| P01 | E101 |
| P01 | E102 |
| P02 | E101 |
| P02 | E103 |
【従業員マスター】
| 従業員ID (PK) | 従業員名 | 職種 | 職種手当 |
|---|---|---|---|
| E101 | 田中 | 開発 | 30,000 |
| E102 | 佐藤 | 営業 | 20,000 |
| E103 | 鈴木 | 法務 | 25,000 |
これにより、従業員がプロジェクトに所属していなくても、従業員情報を登録できるようになります(挿入異常の解消)。
また、結婚で名字が変わったとしても、更新するレコードは1行のみです(更新異常の解消)。
このように第2正規化されたテーブルを「第2正規形( (Second Normal Form: 2NF)」と呼び、定義は以下の通りです。
- 第1正規形であること。
- 部分関数従属していないこと。
第3正規化:非キー同士の依存をなくす
第3正規化は、主キー以外の列同士で「Aが決まればBが決まる」という関係を解消することです。
先ほどの「従業員マスター」を見ると、「職種」が決まれば「職種手当」が決まるという関係があります(職種 → 職種手当)。これは主キー(従業員ID)を介した間接的な依存であり(従業員ID → 職種 → 職種手当)、推移的関数従属と呼びます。
この状態を放置すると、以下のようなデメリットがあります。
- 更新異常: 会社の規定が変わり「開発手当を35,000円にする」となった場合、開発職全員のレコードを書き換える必要があります。1レコードでも更新漏れがあったら、データ不整合が発生します。
- 削除異常: 開発職の従業員が全員退職してしまったら、「開発職の手当はいくらだったか」というマスター情報までデータベースから消えてしまいます。
- 挿入異常:新しい職種と職種手当を策定した場合、社員がその職種に異動するまで、職種手当の金額を記録できません。
第3正規化の手順
- 新規テーブルを作成し、元テーブルで推移的関数従属されている属性を新規テーブルの主キー及び元テーブルの外部キーとする。
- 元テーブルで推移的関数従属している属性を新規テーブルに移動する。
従業員マスターテーブルでの手順例:
- 職種マスターという新規テーブルを作成し、職種属性を新規テーブルの主キーとしてコピー。従業員マスターテーブル上の職種属性は外部キーとする。
- 職業手当属性を職種マスターテーブルに移動する。
第三正規化後のテーブル
【従業員マスター】
| 従業員ID (PK) | 従業員名 | 職種(FK) |
|---|---|---|
| E101 | 田中 | 開発 |
| E102 | 佐藤 | 営業 |
| E103 | 鈴木 | 法務 |
【職種マスター】
| 職種 (PK) | 職種手当 |
|---|---|
| 開発 | 30,000 |
| 営業 | 20,000 |
| 法務 | 25,000 |
これで、手当の金額が変わった際も「職種マスター」の1行を直すだけで済むようになります。
このように第三正規化されたテーブルを第3正規形(Third Normal Form: 3NF)と呼び、定義は以下の通りです。
- 第2正規形であること。
- 推移的関数従属がないこと。
あえて正規化しないケース
実務では、パフォーマンスや運用のしやすさを考えて、あえて正規化を崩す(非正規化)ことがあります。
最も有名な例は住所データです。
以下のカラムを持つ顧客住所テーブルがあるとします。
- 顧客ID(主キー)
- 郵便番号
- 都道府県
- 市区町村
- 町名番地
- 建物名
理論上、郵便番号が決まれば都道府県や市区町村は決まります。そのため、第三正規化を厳格に適用するなら以下のように分離することになります。
| テーブル名 | カラム構成 |
| 顧客マスター | 顧客ID(PK), 郵便番号(FK), 町名番地, 建物名 |
| 住所マスター | 郵便番号(PK), 都道府県, 市区町村 |
しかし、実務においてこのように切り出すことは稀です。
- 理由1: 住所を表示するたびに巨大な住所マスターとJOINすると、表示速度が落ちる。
- 理由2: 郵便番号の統廃合や市町村合併など、外部要因によるデータメンテナンスが非常に煩雑になる。
そのため、第2正規化で止め、1つのテーブルに「郵便番号〜建物名」までをすべて持たせる構造が一般的です。
| 顧客ID (PK) | 郵便番号 | 都道府県 | 市区町村 | 町名番地 | 建物名 |
| 1001 | 154-0017 | 東京都 | 世田谷区 | 世田谷1-1-1 | 〇〇ビル |
まとめ
正規化の段階を一覧表にまとめました。
| 正規形の段階 | 特徴 | 解決する問題 |
| 非正規形 | 1セルに複数値がある | SQLで扱えない |
| 第1正規形 | すべて原子値 | 基本的なSQL操作が可能になる |
| 第2正規形 | 部分関数従属がない | 主キーの一部に依存する重複を排除 |
| 第3正規形 | 推移的関数従属がない | 非キー列同士の依存・重複を排除 |
正規化は「やりすぎ」てもパフォーマンスを損なうことがあります。基本は第3正規化までを検討し、参照スピードが極めて重要な箇所ではあえて統合したままにするなど、バランス感覚を持って設計しましょう!
