考えてみると、Qiitaの記事に取り組み始めた当初は、企業の研修に使えるようなものを書きたいと思っていたのでした。
記事を順に読んでもらえば知識がつくようなシラバスやテキストになるもの、なんて考えていたのが、いつのまにか初心を忘れていました。
初心に帰って基礎からやろう!
ということで正規化について振り返ります。
はじめに
「正規化ってなんだか難しそう…」
そんな方にこそ読んでほしいこの記事。
身近な「レシート」を題材に、データベースの正規化(第1〜第3正規形)をやさしく解説します。
対象読者は、データベースを最近使い始めた人、使おうとしている人、すでに使っているけど正規化について復習したい人を想定しています。
正規化とは?(概要)
「正規化」とは、データベースの中の情報を整理して、重複や矛盾が起きないようにする手法です。
まずは情報の整理方法と思ってもらえればいいかと思います。
この記事では正規化前の状態から、第1正規形から第3正規形へ正規化を進める例を説明します。
正規化する前(非正規形)
まずは、ある日のレシートを見てみましょう。
本物のレシートだと説明に不要な情報も含まれるため簡略化した例を用意しました。
上記のレシートから情報をそのまま抜き出してみます…
| レシートID | 日付 | 会員氏名 | 商品名 | 単価 | 数量 | 商品名 | 単価 | 数量 |
| ーーーーーー | ーーーーーーーー | ーーーー | ーーーー | ーーー | ーー | ーーーーーー | ーーー | ーー |
| R001 | 20251101 | 田中 | コーヒー | 300 | 1 | サンドイッチ | 500 | 2 |
※通常、システム開発では数値など半角にするのですが、Qiitaでは全角の方がきれいに表示されたので
この記事の例には全角を多用しています。
このように、1つのデータに複数の商品が含まれている状態は「非正規形」と呼ばれます。
複数のものが繰り返されている状態で、これはデータベースにとって扱いづらく、検索や集計にも不向きです。
ということで、データベースが扱いやすい形にしてあげましょう。
※検索や集計に不向きな状況だと
アプリの動きが遅かったり、
機能追加が難しくなる、といったことが考えられます。
その結果として、利用者離れや機能が古くて時代遅れになる、
といった影響が出てくるかもしれません。
第1正規形
第1正規形にするには、繰り返しになっている項目を排除し、1つのセルに1つの値を入れるようにします。(※)
ここでは商品情報が繰り返しになっているので、第1正規形として整理すると以下になります。
| レシートID | 枝番 | 日付 | 会員氏名 | 商品名 | 単価 | 数量 |
| ------ | -- | -------- | ---- | ------ | --- | -- |
| R001 | 1 | 20251101 | 田中 | コーヒー | 300 | 1 |
| R001 | 2 | 20251101 | 田中 | サンドイッチ | 500 | 2 |
※1つのセルに1つの値とすることについて「原子値」という言葉があります。
原子値:それ以上分割できない、最小単位のデータのこと。
枝番という項目を独自に追加しました。
追加した理由は、2つのデータを区別するために追加したのですが、画面に表示する順番としても使えると思います。
第2正規形
目的:主キーの一部にのみ依存する項目を分離する
主キー(Primary Key)とは、データベースの中で「レコード(行)を一意に識別するための鍵」となる項目です。
上記の第1正規形では、レシートIDと枝番のペアが主キーとなるが、日付や会員氏名はレシートIDのみで特定できる…
正しく説明しようとすると、このように難しい言葉になってしまいますね。
ここでは
「日付」や「会員氏名」はレシート全体に関係する情報であり、商品ごとに繰り返す必要がないことに着目しましょう。
レシート全体の情報と明細情報を分けます。
レシートテーブル
| レシートID | 日付 | 会員氏名 |
| ------ | -------- | ---- |
| R001 | 20251101 | 田中 |
レシート明細テーブル
| レシートID | 枝番 | 商品名 | 単価 | 数量 |
| ------ | -- | ------ | --- | -- |
| R001 | 1 | コーヒー | 300 | 1 |
| R001 | 2 | サンドイッチ | 500 | 2 |
第3正規形
第3正規形にするには、主キー以外の項目に依存する項目を分離します。
第2正規形の段階で「単価」はレシートIDと枝番のペアでも特定できるけど、商品名に依存しているので別テーブルに分けます。
そして、商品IDという項目を追加しました。(IDについては、このあと説明します)
商品テーブル
| 商品ID | 商品名 | 単価 |
| ---- | ------ | --- |
| P001 | コーヒー | 300 |
| P002 | サンドイッチ | 500 |
レシート明細テーブル
| レシートID | 枝番 | 商品ID | 数量 |
| ------ | -- | ---- | -- |
| R001 | 1 | P001 | 1 |
| R001 | 2 | P002 | 2 |
※以下の整理方法も考えられます。
・レシート明細テーブルは、枝番を外し、レシートIDと商品IDを主キーにする。
・会員テーブルを追加する。
商品IDは、商品テーブルとレシート明細テーブルの繋がりを明確にするために追加しました。
正規化を進めると、自然と「ID」が必要になってきます。
IDは、テーブル同士をつなぐ仲人さんのような存在です。
- 商品テーブルとレシート明細をつなぐ「商品ID」
- レシート情報と明細をつなぐ「レシートID」
それぞれのテーブルは独立していても、仲人さん(ID)が間に入ることで、正しい関係が結ばれるのです。
もう少し説明すると「1事実1カ所」という考え方があります。
文字通り「1つの事は1カ所にのみ記録する」ということです。
データベースの世界では、正しく正規化を行い、適切にIDを使うことで「1事実1カ所」を実現できます。
そのメリットを考えてみましょう。
商品名の変更、例えば「コーヒー」を「美味しいコーヒー」に変更するケースを考えてみます。
<IDを使っている場合>
上記の例を見ればわかるように、商品テーブルを1カ所変更するだけです。
<IDを使わず、商品名で複数のテーブルを関連づける場合>
以下、例です。
×よくない例
| レシートID | 枝番 | 商品名 | 数量 |
| ------ | -- | ---- | -- |
| R001 | 1 | コーヒー | 1 |
×よくない例
| 商品名 | 単価 |
| ---- | --- |
| コーヒー | 300 |
この例では「コーヒー」を「美味しいコーヒー」にするためには、2つのテーブルを修正する必要がありますね。
(先ほどは、1カ所だけでした)
この記事は1枚のレシートのみを対象業務としていますが
10種類の帳票があったら?
レジの画面、本部の管理システム、すべてに商品名の変更を反映するとしたら?
そのようなことになったら、関連するたくさんのテーブルが修正対象となり、影響調査やテストをするのにも時間がかかります。
データベースは、まず「1事実1カ所」にするのが基本です。
そのためにも正規化やIDを活用しましょう!
応用もありますが、それは別途、記事にできればと思います。
まとめ
- 第1正規形:繰り返し項目を排除
- 第2正規形:主キーの一部に依存する項目を分離
- 第3正規形:主キー以外に依存する項目を分離
- IDは、分割されたテーブルをつなぐもの(この記事では「仲人さん」に例えました)
- 正規化やIDを活用し、まず「1事実1カ所」に記録しましょう
正規化は情報の構造を明確にします。
活用しましょう!
