はじめに
初めてDB設計を担当することになり、まず「そもそもDB設計とは何をするのか」というところから勉強を始めました。その中で必ず出てくるのが「正規化」で、「1次正規化・2次正規化…」という言葉に最初はかなり戸惑いました。正直、最初は「正規化」が何を指すのかもよく分かっていませんでした。
調べていくと、これは第1正規形・第2正規形…とテーブルを段階的に整えていく作業のことでした。第1〜第2あたりまでは何とか飲み込めたのですが、第3正規形から先がずっとふわっとしていたので、思い切って第1〜第5正規形まで通しで自分の理解を整理してみることにしました。
第1〜第3正規形は「注文テーブル」を1つ用意して、それを段階的に直していく形で説明します。第4・第5正規形は注文テーブルだと問題が自然に出てこないので、その部分だけ専用の例に切り替えます(理由も本文で説明します)。
初心者の学習メモです。特に第4・第5正規形は間違えやすいところなので、誤りがあればご指摘ください。
対象読者
- これからDB設計を始める人
- 第3正規形くらいまでは聞いたことがあるが、その先が曖昧な人
- 「結局どこまでやればいいの?」が気になる人
そもそも正規化とは
データの重複や矛盾が起きないように、テーブルを適切に分割していく作業です。1つの大きな表にすべてを詰め込むと、同じ情報があちこちにコピーされ、片方だけ直し忘れて食い違う、といった事故が起きます。それを段階的に防いでいきます。
段階には第1〜第5正規形(と、第3と第4の間にBCNF)があり、数字が大きいほど厳しくなります。
第1正規形(1NF):1つのセルに複数の値を入れない
解決すること:繰り返し項目・複数値を1セルに詰め込むのをやめる。
こんな注文テーブルがあるとします。1回の注文に複数商品が入っていて、1セルに詰め込まれています。
違反している例
| 注文ID | 顧客名 | 商品 | 数量 |
|---|---|---|---|
| 1 | 田中 | りんご, みかん | 2, 3 |
| 2 | 佐藤 | ばなな | 1 |
商品 のセルに複数値が入っている(数量 も同様)のが違反です。これだと「みかんは何件売れた?」を集計できませんし、どの商品がいくつなのかも対応が取りづらいです。
1NFにした後(1行1商品に分解)
| 注文ID | 顧客名 | 商品 | 数量 |
|---|---|---|---|
| 1 | 田中 | りんご | 2 |
| 1 | 田中 | みかん | 3 |
| 2 | 佐藤 | ばなな | 1 |
キーワード:1セル1値。
第2正規形(2NF):主キーの一部だけで決まる項目を分ける
解決すること:主キーが複合キー(複数列)のとき、その一部の列だけで決まってしまう項目を分離する。
前提として、これは主キーが複数列の組み合わせ(複合キー)になっているときに問題になる話です。
上の表の主キーを (注文ID, 商品) の複合キーだとします。ここで 顧客名 を見ると、これは 商品 とは関係なく 注文ID だけで決まります。主キーの一部(注文ID)だけで決まる項目がある——これが部分関数従属で、2NF違反です。
違反している状態:顧客名 が 注文ID だけに依存しているのに、(注文ID, 商品) の表に同居している。→ 同じ注文の顧客名が行数ぶん重複する。
2NFにした後(注文の情報と、注文明細を分ける)
注文テーブル
| 注文ID | 顧客名 |
|---|---|
| 1 | 田中 |
| 2 | 佐藤 |
注文明細テーブル
| 注文ID | 商品 | 数量 |
|---|---|---|
| 1 | りんご | 2 |
| 1 | みかん | 3 |
| 2 | ばなな | 1 |
キーワード:部分関数従属の除去(複合キーの一部にだけ依存する項目を分離)。
第3正規形(3NF):主キー以外の項目に依存する項目を分ける
解決すること:主キーではなく、別の項目を経由して決まる項目を分離する。
注文テーブルに顧客の情報を足してみます。主キーは 注文ID です。
違反している例
| 注文ID | 顧客名 | 顧客ランク | 送料区分 |
|---|---|---|---|
| 1 | 田中 | ゴールド | 送料無料 |
| 2 | 佐藤 | シルバー | 通常送料 |
| 3 | 田中 | ゴールド | 送料無料 |
この表をよく見ると、依存関係が2段になっています。
-
顧客名が決まると顧客ランクが決まる(田中さんはゴールド) -
顧客ランクが決まると送料区分が決まる(ゴールドは送料無料)
つまり 顧客ランク や 送料区分 は、主キー 注文ID そのものではなく、顧客名 を経由して間接的に決まっています。この「キー以外の項目を経由して決まる」状態が推移的関数従属で、3NF違反です。田中さんのランクが変われば、田中さんの注文の行すべてを直す必要が出てしまいます。
3NFにした後(顧客を独立したテーブルに切り出す)
切り出すときに、顧客を一意に識別するための 顧客ID を新しく振ります。
注文テーブル
| 注文ID | 顧客ID |
|---|---|
| 1 | C01 |
| 2 | C02 |
| 3 | C01 |
顧客テーブル
| 顧客ID | 顧客名 | 顧客ランク | 送料区分 |
|---|---|---|---|
| C01 | 田中 | ゴールド | 送料無料 |
| C02 | 佐藤 | シルバー | 通常送料 |
これで顧客ランクや送料区分の変更は、顧客テーブルの1行を直すだけで済みます。
厳密には、この顧客テーブルにはまだ
顧客ランク → 送料区分という依存が残っています。より徹底するならランクと送料区分をさらに別テーブルに分けますが、実務ではここまでで止めることも多いです。まずは「キーを経由しない依存を切り出す」という考え方をつかむのが目的です。
キーワード:推移的関数従属の除去(キー以外の項目に依存する項目を分離)。
実務ではここがゴールになることがほとんどです。多くのテーブルは3NFまでやれば十分整います。
ここから先(BCNF・4NF・5NF)について
第3正規形までは1つの注文例で説明できましたが、ここから先は注文テーブルだと問題が自然に発生しません。BCNF以降は「候補キーが複雑に絡む」「多値従属」「結合従属」といった特殊な状況で初めて出てくるためです。
なので、ここからは各正規形ごとに、その問題が起きる最小の専用例で説明します。そして正直に言うと、この3つは実務で出番が少なく、「こういうものがある」と知っておくレベルです。
ボイス・コッド正規形(BCNF):3NFの取りこぼしを直す
3NFでも、候補キーが絡む特殊なケースで矛盾が残ることがあります。BCNFは「すべての関数従属について、決める側(左辺)が候補キー(その値が決まれば行が一意に定まる列の組み合わせ)であること」を求める、3NFを少し厳しくした形です。
例:塾の受講を管理する表を考えます。ルールはこうです。
- 1人の生徒は、1つの科目につき1人の教師に習う(だから
(生徒, 科目)で行が一意に決まる=主キー) - 各教師は1科目だけを担当する(だから
教師 → 科目が成り立つ)
| 生徒 | 科目 | 教師 |
|---|---|---|
| Aさん | 数学 | 山田 |
| Bさん | 数学 | 山田 |
| Aさん | 英語 | 鈴木 |
この表は3NFは満たしています(主キー以外の非キー属性が推移的に依存する、という状態ではない)。しかし 教師 → 科目 という従属があり、その左辺の 教師 は候補キーではありません。「候補キーでないものが、他の項目を決めている」——これがBCNF違反です。実際、山田先生の担当科目が変われば、山田先生の行すべてを直す必要が出ます。
BCNFにした後:教師 → 科目 を別テーブルに切り出します。
教師テーブル:教師(主キー), 科目
受講テーブル:生徒, 教師(この2つで元の情報を復元できる)
キーワード:候補キーでない項目が他項目を決めている状態の除去。
第4正規形(4NF):無関係な多対多を1つの表に同居させない
多値従属という状況で起きます。1つのキーに対して、互いに無関係な(独立した)複数の項目が、それぞれ多対多でぶら下がっているケースです。ここで「無関係であること」がこの正規形のいちばんの肝です。
例:ある社員が持つ「スキル」と「趣味」を1つの表にしたとします。スキルと趣味は互いに何の関係もありません。
違反している例
| 社員 | スキル | 趣味 |
|---|---|---|
| 田中 | Java | 釣り |
| 田中 | Java | 登山 |
| 田中 | Python | 釣り |
| 田中 | Python | 登山 |
田中さんのスキル2つ × 趣味2つで、無関係なものの全組み合わせ4行ができています。スキルを1つ足すと、趣味の数だけ行が増えてしまう。この「無関係な項目同士のかけ算」で行が膨らむのが、多値従属による4NF違反です。
4NFにした後(無関係なものは別テーブルに)
スキルテーブル:社員, スキル
趣味テーブル:社員, 趣味
こうすれば、スキルを足しても趣味の行は増えません。
間違えやすいポイント
「無関係な2つの多値項目が同居しているとき」だけが4NFの話です。たとえば「スキル」と「そのスキルの資格」のように、片方がもう片方に紐づく(=無関係でない)場合は、ただの1対多であって4NFの問題ではありません。"互いに無関係な多値項目の同居"かどうか、で線を引きます。
キーワード:多値従属の除去(無関係な多対多を分離する)。
第5正規形(5NF):3つに分解して結合で元に戻せるなら分解する
結合従属という、正規化の中でいちばん特殊で、いちばん誤解されやすい状況です。ざっくり言うと、3つ以上の要素が絡む関係を、2つずつのペアの表に分解し、それらを結合すると元の表がちょうど復元できる——そういう場合に、分解しておくのが5NFです。
例:「営業担当・商品・仕入先」の3者の関係を考えます。ここで、次のような循環したルールが成り立つ場合だけを扱います。
担当がその商品を扱っていて、かつ その商品をその仕入先が供給していて、かつ その担当がその仕入先と取引がある。この3つがすべて揃ったときだけ、その(担当・商品・仕入先)の組み合わせが成立する。
このルールがあるとき、1枚の3列表で持つより、次の3つのペア表に分解できます。
担当 × 商品商品 × 仕入先担当 × 仕入先
この3つを結合すると、元の表がちょうど復元できます(情報が増えも減りもしない)。1枚表のままだと、ルール上ありえない組み合わせを防げず冗長も出るため、分解しておくのが5NFです。
間違えやすいポイント(最重要)
これは「担当・商品・仕入先を、とにかく3つに割ればいい」という話ではありません。上のような循環したルールが成り立つときだけ、3分解して結合しても元に戻ります。その条件が無いのに機械的に3分解すると、結合したときに元の表に無かった余計な行が生まれてしまい(=復元に失敗し)、むしろデータが壊れます。「無条件に3分解してよい」と書いている解説を時々見かけますが、それは誤りです。ここが5NFで一番つまずくところでした。
キーワード:結合従属の除去(3分解して結合で正しく復元できるときだけ分解する)。
5NFは理論的な色が濃く、上のような循環ルールが成り立つ場面自体がまれなので、実務で意図的に適用することはほとんどありません。自分も「こういう概念がある」という理解にとどめています。
まとめ表(各正規形の早見表)
| 正規形 | 解決する問題 | キーワード | 実務での頻度 |
|---|---|---|---|
| 1NF | 繰り返し・複数値 | 1セル1値 | ほぼ必須 |
| 2NF | 部分関数従属 | 複合キーの一部への依存 | ほぼ必須 |
| 3NF | 推移的関数従属 | キー以外への依存 | 実務のゴール |
| BCNF | 3NFの取りこぼし | 候補キーが絡む従属 | たまに |
| 4NF | 多値従属 | 無関係な多対多の同居 | まれ |
| 5NF | 結合従属 | 条件付きで3分解できる | ほぼ理論 |
正規化にはデメリットもある(非正規化の話)
ここまで「正規化しよう」という話をしてきましたが、勉強していて「じゃあ正規化はいいことだらけなのか?」という疑問が湧きました。調べてみると、正規化には代償もあることが分かりました。
正規化はテーブルを分割していく作業なので、その裏返しとして次のようなデメリットが出ます。
- 結合(JOIN)が必要になる:1つの表で済んでいたものが複数に分かれるので、「注文と顧客名を一緒に見たい」だけでもテーブルをつなぎ直す必要が出ます。分割が進むほど、1つの画面を作るのに何個も結合することになります。
- データが大量だと重くなる:巨大なテーブル同士を結合すると、(インデックスが無ければ)処理が重くなります。分けた分だけ、読むときに繋ぐコストがかかります。
- 集計を毎回やり直す:合計や件数などを、分割された生データから都度計算することになります。
マスタとトランザクションで出方が違う
面白いのは、このデメリットがどのデータでも等しく出るわけではないことです。テーブルは大きく2種類に分けられます。
- マスタ(顧客・商品など、あまり増減しないデータ):件数が少ないので、分割しても結合は軽い。→ デメリットはほぼ出ない。しっかり正規化してOK。
- トランザクション(注文・履歴など、どんどん増えるデータ):件数が膨大になる。→ 結合や集計が重くなるのは主にこちら。
つまり「正規化で重くなる」問題は、増え続けるトランザクション系で顕在化する、という切り分けができます。
あえて崩す:非正規化
デメリットが実際に問題になったとき、あえて正規化を崩して速さを優先する選択肢があります。これを非正規化(denormalization)と呼びます。たとえば「毎回JOINするのが重いから、注文テーブルに顧客名をあえてコピーして持つ」といった対応です。
非正規化は「速さ」と引き換えに、正規化で防いだはずの"重複と矛盾"のリスクを引き受けることです。顧客名をコピーして持てば、名前が変わったときにコピーした全部を直す必要が出てきます。だから安易にやるものではありません。
自分なりの結論
いろいろ調べて、順番はこう理解しました。
まず正規化する。デメリットが実際に問題になって初めて、必要な箇所だけ非正規化する。
最初から「重くなりそうだから崩しておこう」はやらない、という点が大事だと感じました。正規化してきちんと作っておけば、後から崩すのは選べます。でも最初から崩してしまうと、あとで正しい形に戻すのは大変です。
おわりに
「DB設計とは何か」から入って、その一部として正規化に出会った自分ですが、第1〜第5正規形まで通して整理してみて、一番の学びは 「全部やればいいわけではない」 という点でした。実務では第3正規形(必要に応じてBCNF)までで十分整うことがほとんどで、4NF・5NFは「そういう概念がある」と知っておくレベル、という温度感がつかめました。
数字を上げること自体が目的ではなく、重複と矛盾をなくすための手段、という理解で使い分けていきたいです。同じように「正規化・1次正規化って何?」から入った人の、地図がわりになれば嬉しいです。