冗長なデータを「テーブルを分割」していくことで矛盾を防ぐ。注文書の例で段階的に正規化していきます。
正規化(Normalization)は、データの冗長性を排除し、更新異常・挿入異常・削除異常を防ぐためにテーブルを分割していく作業です。
「冗長」とは、同じ情報があちこちに重複している状態。たとえば「田中さんの住所」が複数行に書かれていると、住所が変わったとき全部書き換えないと整合性が崩れます。1 つの情報は 1 箇所だけに置く ── これが正規化の目指す姿です。
正規化のレベルは段階的:非正規 → 第1正規形(1NF) → 第2正規形(2NF) → 第3正規形(3NF)。実務ではほぼ 3NF まで進めれば十分で、まずはこの 3 段階を押さえましょう。上のツールで段階的に分割される様子を確認できます。
正規化が不十分だと、データを操作するときに 3 つの異常 が起きえます。これが正規化を必要とする最大の理由。
・更新異常:同じ情報が複数行にあると、1 箇所だけ更新したつもりが他に古い情報が残る
・挿入異常:データを追加したいのに、関連する別データがないと挿入できない(または不要な NULL 行が必要)
・削除異常:1 件削除したつもりが、付随する別情報まで消えてしまう
上のツール最初のステップで、注文書テーブルにこれら 3 つの異常があることを確認できます。正規化を進めるごとに異常が解消されていく様子も観察してください。
正規化を理解する核心が関数従属(Functional Dependency, FD)です。A → B と書き、「A の値が決まると B の値が一意に決まる」関係を意味します。
例:
・学生ID → 学生名:学生ID が決まれば名前は一意に決まる
・商品ID → 単価:商品ID が決まれば単価は一意に決まる
正規化の各段階で問題となる特殊な FD:
・部分関数従属:複合主キー (A, B) の一部だけで決まる従属(例:(注文番号, 商品ID) → 商品名 を 商品ID だけで決まる) → 2NF で解消
・推移的関数従属:A → B かつ B → C のような段階的な依存(例:注文番号 → 顧客ID → 顧客名) → 3NF で解消
上のツールで各ステップ下に表示される FD タグの色で「正常/部分/推移」を見分けられます。
| 正規形 | 条件 | 解消する問題 |
|---|---|---|
| 1NF | すべてのセルが単一値(繰り返し項目なし) | 繰り返し項目 |
| 2NF | 1NF かつ部分関数従属がない | 主キーの一部だけで決まる列 |
| 3NF | 2NF かつ推移的関数従属がない | A→B→C の段階的依存 |
覚え方のコツ:「繰り返し → 部分 → 推移」の順番で解消していく、と覚えるとシンプル。各段階で何を解消するのかが理解の要点です。
さらに上位の正規形にボイス・コッド正規形(BCNF)、第4・5正規形がありますが、実務では 3NF までで十分なケースが大半です。
| メリット ✓ | デメリット ✕ |
|---|---|
| データの冗長性が排除される | テーブル数が増える |
| 更新異常・挿入異常・削除異常を防げる | 結合(JOIN)が必要になり読み取り性能が落ちる |
| データの一貫性が保たれる | クエリが複雑化 |
| ストレージ消費が減る | スキーマ変更時の影響範囲が広い |
正規化にはトレードオフがあります。データの整合性 vs 検索性能。
ビッグデータ・分析用途ではあえて正規化を崩す「非正規化(denormalization)」が選ばれることもあります。読み込みが極端に多い場合、結合コストを下げるためにデータを重複させて持つ戦略です。要するに「正規化のデメリット=結合性能」と整理しておけばよいでしょう。
1NF・2NF・3NF それぞれ「なぜ分けるのか」を、注文書の例で整理します。根本の目的は同じ ── 「1つの表に1種類の意味だけを持たせる」こと、そして「同じ情報を1か所だけに書く」ことです。
1NF(第1正規形)へ:1セルに1つの値
「りんご,みかん」のように1マスに複数の値が入っていると、特定の商品を検索したり数を数えたりできません。まず1セル1値に分けるのが1NFです。
2NF(第2正規形)へ:主キーの一部だけで決まる列を分離
注文明細のような複合主キー(注文番号+商品ID)の表で、「商品名」は注文番号に関係なく商品IDだけで決まります。なぜ問題か ── 同じ商品名が注文ごとに重複して書かれ、商品名の変更時に全行を直す必要が生じるからです。商品情報を別表に出すことでこれを防ぎます。
3NF(第3正規形)へ:段階的な依存を断ち切る
注文番号 → 顧客ID → 顧客名、のように「注文番号で顧客IDが決まり、顧客IDで顧客名が決まる」という連鎖があると、顧客名が注文ごとに何度も書かれることになります。顧客情報を別表に分けて、注文表には顧客IDだけを置けばすっきりします。
「3つの異常」は抽象的に聞こえますが、上の図のような注文表に顧客名を直接書いてしまった状態を想像すると分かりやすくなります。
更新異常:「田中さんが引越しで名前が変わった」ときに注文001・002の両行を直す必要があります。直し忘れたら「田中」と「田中(旧姓)」が混在するという整合性の崩れが起きます。
挿入異常:新しい顧客「山田さん」を登録したいのに、まだ注文がなければこの表に書く行がありません。顧客情報を保存するために「中身のない注文行」を無理やり作るしかないという矛盾が起きます。
削除異常:注文003(鈴木さんのバナナ注文)を削除すると、鈴木さんの情報まで消えてしまいます。消したかったのは注文だけなのに、顧客まで消えるという予期しない問題です。
これら3つはすべて「1つの表に複数の意味(注文の意味・顧客の意味)を詰め込んだ」ことが根本原因です。正規化でテーブルを分けることで解消されます。
実際にデータベースを設計するときの流れ:
・① 業務を洗い出す:紙の伝票や Excel のシートをそのまま見る
・② 1NF に揃える:繰り返し項目をなくし、表として整える
・③ 関数従属を書き出す:何が決まれば何が決まるかを列ごとに整理
・④ 2NF・3NF に進める:FD に沿ってテーブルを分割
・⑤ パフォーマンス調整:必要なら非正規化や索引追加で性能を取り戻す
「与えられた関数従属からどの正規形か判定する」「次の正規形にするには何をするか考える」という見方ができると理解が深まります。上のツールで各ステップでの FD と問題点を見比べて感覚を掴んでください。