スキマ時間で理解する正規化

# はじめに
今回の記事では、正規化についてまとめていきます。

# 事前知識　（主キーと外部キー）
## 主キー（Primary Key）
主キーは、各行（レコード）を一意に特定するための列（カラム）で、各テーブルに必ず一つだけ存在する必要があります。

下の従業員テーブルでは、従業員IDで各レコード、つまり一人の従業員を特定することができるため、従業員IDが主キーになります。逆に、名字カラムでは山田、名前カラムでは太郎、部署IDカラムでは1が重複しているため、これらのカラムは主キーになることができません。

![スクリーンショット 2024-09-06 13.56.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/fc7685cc-6ac9-0706-dc45-82e9dbbebcfa.png)

カラム名の下線は主キーを表します。
　

:::note info
複数のカラムの組み合わせで主キーを構成することもできます。これを**複合主キー**と呼びます。　
:::

## 外部キー（Foreign Key）
外部キーは、親テーブルの主キーを参照するためのカラムです。下の図では、従業員テーブルの部署IDが外部キーとなり、部署テーブルの部署IDを参照します。
　
![スクリーンショット 2024-09-06 14.00.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/58e2ff60-f87f-936e-9555-df89bc1d80ab.png)
　
この性質により、親テーブルに存在しない無効なデータを子テーブルに追加することができなくなり、データの整合性が保証されます。（**参照整合性制約**）例えば、部署テーブルに存在しない総務部というデータを、従業員テーブルに追加することができなくなります。




# 正規化とは
**正規化**とは、データベース（DB）に対してデータの追加・更新などの処理を不備なく行うために、事前にテーブルを整理（分割）する作業です。

正規化によって作られる**正規形**には、第1〜第5までのレベルが存在しますが、基本的には第3までで大部分をカバーできるため、本記事では第3正規形までをまとめていきます。

# 第1正規形
第1正規形は、以下を満たす状態です。
　
* テーブル内の各セルに値が一つだけ入っている

 　
下の従業員テーブル（非正規形）では、一つのセルに複数の電話番号が登録されているので、第1正規形を満たしません。そのため、電話番号の数だけ行を保持することで第1正規形を満たすようになります。
　
![スクリーンショット 2024-09-06 14.10.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/0f3badee-dca6-d0f4-c5ce-fa2ca669cb80.png)

ちなみにですが、従業員テーブル（正規形）の主キーは、{従業員ID、電話番号}の複合主キーになります。　


# 第2正規形
第2正規形は、以下を満たす状態です。
　
* 第1正規形を満たしている
* 主キーの一部に依存する非キー列が存在しない（**部分関数従属**が存在しない）　

　　
さて、以下の従業員テーブルは第2正規形を満たすでしょうか。
　
![スクリーンショット 2024-09-06 14.11.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/29971edf-f2b2-a25c-02b4-89c229e31e69.png)
　

このテーブルは第2正規形ではありません。なぜなら、主キーの一部の列に依存する非キー列が存在する、つまり**部分関数従属**が存在するからです。
このテーブルは、複合主キー {従業員ID、部署ID} を持ち、名前・都道府県コード・住所カラムは従業員IDのみに依存し、部署名カラムは部署IDのみに依存しています。

　
この部分関数従属を取り除くために次のようにテーブルを分割することで、第2正規形を満たすようになります。

![スクリーンショット 2024-09-06 15.14.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/ba238752-585e-2e94-8c3b-045b6483a42e.png)

# なぜ第2正規形を満たしてないとダメなのか
第2正規形を満たしていない場合、どんな不都合があるのでしょうか。先ほどの第2正規形ではない従業員テーブルを見てみます。
 　
![スクリーンショット 2024-09-06 14.11.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/41417bc2-411a-a5e8-6852-a4f77419151a.png)
　
ここで、総務部という部署を新たに設立し、総務部所属の従業員がまだ存在しないとします。
この状況で、総務部をテーブルに追加すると、主キーである従業員IDがNULLになってしまうため、総務部を追加することができません。そのため、先ほどのように部署テーブルを独立させることで、総務部の追加が可能になります。
 　
![スクリーンショット 2024-09-06 15.13.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/1224200e-c93a-b501-e58b-7ee375a7f24b.png)
　

:::note warn 
**NOT NULL制約**
主キーのカラムには、NULL値を入れることができない。
:::

# 第3正規形
第３正規形は、以下を満たす状態です。
　
* 第2正規形を満たしている
* 全ての非キー列が、主キーに対してのみ依存する
→ 非キー列が他の非キー列に依存しない（**推移的関数従属**が存在しない）　

　 
さて、先ほど作成した第2正規形のテーブルは、第３正規形も満たすでしょうか。　
　
![スクリーンショット 2024-09-06 15.14.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/cb89c5e0-118c-23cd-3ab7-b27ba0ee6355.png)
　
答えは、従業員テーブルにおいて住所カラム（非キー列）が都道府県コードカラム（他の主キー列）に依存しているため、第３正規形を満たしません。つまり、このテーブルでは都道府県コードが従業員IDに依存し、住所が都道府県コードに依存しており、2段階の依存関係（**推移的関数従属**）が存在しています。
　
以下のように都道府県テーブルを独立させ、2段階の依存関係を解消してあげることで、第３正規形を満たすようになります。
　
![スクリーンショット 2024-09-07 11.01.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/c5b567aa-f6e4-aede-850e-336183b56474.png)
　　

# なぜ第3正規形を満たしていないとダメなのか
第3正規形を満たしていない場合、どんな不都合があるのでしょうか。先ほどの第3正規形ではない従業員テーブルを見てみます。

![スクリーンショット 2024-09-06 14.56.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/038a8fd4-b6f7-aea5-fe8a-1de987906126.png)
　
 

現在、従業員が住んでいる都道府県は、東京都、神奈川県、千葉県です。そして、今後入社してくる従業員が他の都道府県に住んでいる可能性を考慮して、他県のデータを事前に追加したい状況を考えます。この時、従業員IDにNULLを設定しない限り、他の都道府県を追加することができません。そのため、以下のように都道府県テーブルを独立させることで、他県を事前に追加できるようになります。
　
![スクリーンショット 2024-09-06 15.21.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/7077a8a0-44b2-b1a2-6d8e-660e3cecfe6d.png)


# 正規化の欠点
ここまでデータの追加・更新時の不整合をなくすために、正規化を行なってきましたが、正規化にも欠点が存在します。
　
正規化を行いテーブルを整理・分割していくとテーブル数が多くなります。これまでの例ですと、元々1つだった従業員テーブルが、正規化により最終的に4つのテーブルになりました。

これにより、4つのテーブルに分散したデータを取得する際に、テーブルを結合する必要があり、クエリの実行に時間がかかるようになります。SQLでの結合(JOIN)処理は重たい処理なので、パフォーマンスが低下してしまいます。
　　
ただ、一般的には第3正規形までを行うことが推奨されています。

# 正規化は無損失分解
正規化によって分割されたテーブルは、正規化する前のテーブルに戻すことができます。これを**無損失分解**と言います。

![スクリーンショット 2024-09-07 11.02.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3576475/553c6edb-3669-aaf9-d54a-663cbba05bef.png)

# 参考
[達人に学ぶDB設計徹底指南書　初級者で終わりたくないあなたへ（ミック）](https://www.amazon.co.jp/%E9%81%94%E4%BA%BA%E3%81%AB%E5%AD%A6%E3%81%B6DB%E8%A8%AD%E8%A8%88-%E5%BE%B9%E5%BA%95%E6%8C%87%E5%8D%97%E6%9B%B8-%E5%88%9D%E7%B4%9A%E8%80%85%E3%81%A7%E7%B5%82%E3%82%8F%E3%82%8A%E3%81%9F%E3%81%8F%E3%81%AA%E3%81%84%E3%81%82%E3%81%AA%E3%81%9F%E3%81%B8-%E3%83%9F%E3%83%83%E3%82%AF/dp/4798124702)
[Database Normalization – Normal Forms 1nf 2nf 3nf Table Examples](https://www.freecodecamp.org/news/database-normalization-1nf-2nf-3nf-table-examples/#heading-the-third-normal-form-3nf)