[新卒PMがはじめて学ぶ] DBの正規化

##はじめに

Qiita初投稿記事となります。

新卒でPMとしてのキャリアをはじめ、これまで何も考えずにDBを使ってきました。
今回実際にアプリケーションを作り、DB設計の奥深さを知ったので、その中でも重要なDBの正規化についてメモしておきます。

##今回使用するDB例

<img width="831" alt="スクリーンショット 2019-06-27 20.07.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/6961c57f-1cf1-547c-7f39-bb5b801d2f84.png">
想定：コンビニに商品を卸している卸売会社のDB

##DBの正規化を理解するにあたっての前提知識
###DBのキー
DBにはキーという概念が存在し、キーによって行の特定、SQLにおけるテーブルの結合を行うことができる
#####候補キー
- テーブルにおいて**この列の値がわかると行を1つ特定できる**という列のこと
    - 候補キーはユニークであるからこそ、行を特定できる
    - 候補キーは複数キーの組み合わせによって成立している場合もある

#####非キー
- 候補キーに含まれない列

###従属
DBの各列には関係性が存在し、その関係性によって後述するDBの正規化を行っていくことになる
列の関係性は以下のように分類される
#####1. 完全関数従属
- 複数のキーが候補キーとなったときのみ、ある要素を特定できる関係
    - ○ {A, B} →　C 
    - × A →　C , B →　C
        - Cは{A, B}に完全関数従属している

#####2. 部分関数従属
- ある候補キーがある要素を特定できる関係
    - ○ {A,B} → C
    - ○ A → B, B → C のいずれか

#####3. 推移的関数従属
- キーをたどることである要素を特定できる関係
    - ○ A → B, B → C
    - × B → A

##DBの正規化
DBの正規化は第6正規化まで提唱されているが、第3正規化までで十分なようなので今回は第3正規化まで上記DB例にそって紹介する


####第1正規化

- 第1正規化では単一ではない値、内容が繰り返しになっている列を無くす
- 列を増やすのではなく、行を増やすことで解消する
    - 列がどんどん増えていくことを防ぐ（例の場合一回の注文商品数が増えると列がどんどん増えてしまう）


<img width="815" alt="スクリーンショット 2019-06-27 20.05.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/47d89dfd-b652-5239-94b2-c8178e57b51e.png">






- DB例では無いが、1つの列に複数の値が入っていない状態にする
    - データが複数入っていると、その値が正しい型かチェックするのが難しくなる
        - (ex)商品名にジュースとおにぎりの2つの値が入っている

####第2正規化

- すべての非キーが候補キーに完全関数従属している状態にする
- テーブルを分けて、部分関数従属している列は切り出す

<img width="837" alt="スクリーンショット 2019-06-27 20.08.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/a9d63dbe-2fcc-0586-21e5-7d5df29ebc45.png">


<img width="841" alt="スクリーンショット 2019-06-27 20.01.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/1f433213-e89c-c9ff-abb2-13e3ee7a2182.png">

- DB例の候補キー（そのキーがわかると行を特定できる）は**注文IDと商品ID**
- ○ {注文ID, 商品ID} → 非キー | ○ 注文ID →　非キー, 商品ID → 非キー　となるキーは切り出す

<img width="824" alt="スクリーンショット 2019-06-27 20.20.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/999c1f4a-59e5-99c6-b574-7cac22aaed1c.png">


- 取引先IDの方は注文IDで行を特定できるため、注文IDを候補キーとなっている
    - 候補キー → 取引先 → 住所になっているため部分関数従属はない
- 一方、商品の方は、{注文ID, 商品ID}で行を特定できるため、複数列で候補キーとなっているが、商品ID →　商品名・単価となっているため部分従属は残っている
    - 商品IDを候補キーとして、商品名・単価を切り出す

####第3正規化
- どの非キーも候補キーに推移的関数従属していない状態にする

<img width="851" alt="スクリーンショット 2019-06-27 20.23.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/f5103c8c-6799-2070-6a6e-c7d00932e7ab.png">


- 注文ID → 取引先ID → 取引先名は推移的従属している状態なので、切り出す

####結果
DB例をルールに従って第3正規化まで行った場合、以下のようにテーブルが分割される

<img width="825" alt="スクリーンショット 2019-06-27 20.30.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/8714c056-baa7-d121-1a96-029f3cff0815.png">

<img width="851" alt="スクリーンショット 2019-06-27 20.23.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/f5103c8c-6799-2070-6a6e-c7d00932e7ab.png">

<img width="830" alt="スクリーンショット 2019-06-27 20.31.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/d3d0fd13-d2df-12d6-1ec0-1d07a0377489.png">

<img width="824" alt="スクリーンショット 2019-06-27 20.20.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/427335/999c1f4a-59e5-99c6-b574-7cac22aaed1c.png">




##DBの正規化を行なう理由

正規化はデータ記録の観点で以下のような利点がある一方、そのまま捉えると実用面では疑問点があるのでその点をまとめる

####利点
- １つの事実に対して、１つのテーブルに格納される簡易な状態にできるためデータに矛盾が生じにくい
    - DBの設計レベルで矛盾を防ぐことが出来る
    - データベースを修正する場合のコストを削減することができる
- 取引など**関連付けを表す**性格のテーブルと取引先など**実体を表す**テーブルを明確にできる

   
####実用面での懸念点
- テーブルの数が増え、データが細々とするためそのままでは使いにくいのでは？
    - **SQLでテーブルを結合することでデータをまとめて把握することが可能になる**
- 値段の変更など、実体を表すデータに改定があった場合、変更後の値が参照される可能性があるのでは？
    - (ex) 商品単価を120円から150円に変更した場合に、そのままでは過去の値段がデータとして残らず、売上の計算に150円が適用される可能性がある
    - **値段など変更が起こりやすいデータは関連付けを表すテーブルに別途列を作って対応**
    - (ex) 注文テーブルにその時々の販売単価を記載することで変更に対応
- 候補キーが複数のテーブルに現れるため、候補キーが変更した場合に矛盾が生じる可能性があるのでは？
    - **DBの設定によって対応が可能（参照整合性・連鎖更新）**


##DBを非正規化する場合

正規化を進めるデメリットを考慮して、あえて正規化せずに設計を行なうこと
デメリットを考慮した上で以下の場合は、正規化しない可能性もある

- 少ないテーブルですませパフォーマンスを重視したい
    - クエリの実行時間
    - クエリの作成時間の短縮
    - 今後テーブルが修正される可能性が低い
- 実体が変更する可能性があり、その時の記録を残したい