1章 DB設計総論
正規化を意識しながら、プログラミング言語の都合でデータ構造を崩さないかつプログラミング言語側に負担を かけないようなデータベース設計の実現の仕方を考える。
🚩DB設計の基本手順
データベース設計で大事な要点となるのは、「One Fact in One Place」。これを実現するのに用いる手法が正規化と言われるもの。正規化は、「データベースの部品化」を進めていくと捉えれば良い。
正規化 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
データベース設計の正規化に加えて2つの手法を導入する。
- IDの導入
- 業務の視点からの正規化
IDの導入
従来の正規化にテーブル間の関連付けを行うと、関連付いたレコードの値をそのまま使ってしまってしまうことにより洗い換え問題というものに直面する。洗い換え問題とは、先述したような関連付けを行い、値を変更した時に参照先のテーブルの値も続けて入れ替えなければならないような問題を指す。そこでIDを導入することで、洗い換え問題を起こらないようにします。IDはレコードの存在を示すものです。また外部参照キー(FK)はそのレコードを参照するものなので、参照先の値を入れ替えても問題は発生しません。これによりテーブル同士を密結合から疎結合にすることが出来るのでデータ構造の変更自体が柔軟に行える。
業務の視点からの正規化
例えばある商品と単価を比較した時に、2枚の伝票に異なる単価があった場合が存在する。業務側の担当者に確認すると「単価は決まっていて、値引きする場合もある」と言う。ここに「単価が決まっている」と「値引きされた単価」という2つの事実が存在する。最初に先述した「One Fact in One Place」に従えば、同じ単価というレコードであったとしても事実が異なるのでこれは分けて保持すべきということになる。正規化にこだわるあまり、ある項目を2つの場所に置くのを排除するという考え(実際には項目が同じでも別の事実が生まれている場合)では、プログラミング言語側で見分けるような実装になってしまうようなことにもなり得るので気を付けなければならない。
🚩DB設計の悩みと重要性
データベース設計における悩みは、突き詰めていくと次の三つにまとめることができる。
- 箱(エンティティ)の見出し方
- 主キーの設定
- ビジネス上の正規化
☑︎箱(エンティティ)の見出し方
エンティティとして、以下の二つに分類することが出来る。
- 「モノ」に関する記録のことは「リソース系エンティティ」
- 「出来事」についての記録のことは「イベント系エンティティ」
このリソース系とイベント系を手がかりに、エンティティ名を6W3Hによる整理を行うことでビジネスモデルとデータモデル間の整合性を見ていくことができる。データベース設計者は、担当者のビジネスモデルや業務要件をしっかりと理解するべき。(他の負担が軽くなるため)
☑︎キーの設定
業務において、顧客コードや商品コードなどの識別子はあくまでユーザーインターフェイスのためのものととして捉えることが大事。要は、人間の名前のようなもの。事実を客観的に捉える座標としてのIDとは意味が異なる。例えば業務上では、同じ商品であっても社内の商品コードと仕入れ先商品コードが異なる場合はよく存在する。(m:m問題) 自社と他社で商品は同じでもあだ名・呼び方が異なると捉えると良い。そういう時は、IDと外部キーを導入し交差エンティティ(中間テーブル)を用意すると良い。
☑︎ビジネス上の正規化
かなりあったので、さらに項目事に分けました。
重複の排除
重複とは何か?どういうこと状態か?と考えることが大事である。同じデータに見えても事実が異なっていればそれは「別の事実」なので重複とは言わない。この目印となるものが、各エンティティに格納されるアイデンティファイア(ID)である。各アイデンティファイアの事実に紐づくものがあればそのエンティティのカラムとして定義すれば良い。本では、見出し・明細形式を例に出している。システムの都合を考えればもっと最適化した構造化もできるが、あくまでデータはビジネスのためのものなので導出(デリベーション)ルールに従わなければならない。例えば、売上発生時の単価を記録するための「販売時単価」の項目をどこのエンティティに定義するか考える。商品の単価が一律なのであれば、販売時単価は冗長であっても商品テーブル内で管理されるべき。
遷移状態するデータの扱い
予約→チェックイン→宿泊中の各種サービス利用→チェックアウトという状態遷移するホテル予約などを例にして、テーブル設計を考えるとします。このような場合は、予約、チェックイン、サービス利用、チェックアウトなどの各エンティティを用意することが大切。仮にこのフローが変更したとしても、別のテーブルを追加するだけで良いのでリスクを抑えることができる。(ただしプログラミング側は大変かもしれない)
デリベーションルール
例えば、単価×数量=金額が成り立つような導出可能なものは、可逆性という。対して、値引きなどが原因で単価×数量≠金額のような導出できないものはは可逆性という。可逆性である導出項目はプログラミングで導出するようにするのではなくデータベース設計の一部としてデータベースに配置する。論理モデルをもとに導出項目をビュー化するというアプローチをすると良い。(ここら辺はあんまりよくわかっていない)
バリデーションルール
ビジネスというのは「関係」を管理するのがキモとなる。つまり「関係」を管理するリレーションシップのエンティティ化は欠かせないものとなる。ここに出てくるテーブルの例は、rails実践の課題でもあるuserとpostのテーブル間の中間であるLikesテーブルや同じusersテーブル内でのインスタンスの関係を比較するrelationshipが当てはまる。
複雑なビジネスルール
例えば「単価」という項目を定義する時に、データベース設計者とビジネス側でどういう「単価」なのか?を深掘りして確認しないと意味が全く異なる解釈をして、ビジネスモデルに当てはまらないテーブル設計となりプログラム側が複雑な処理を対応しなければならないなんてことがある。データベース設計者は、正規化する対象を洗い出し正規化をしっかりと施すことが大切。エンティティ数が膨大であってもビジネスルールの実態に当てはめていくことが重要、優先する。
🚩DB設計の手順
ざっくりとした流れは下記のようになる。
- おおまかにブロック分けを行う(業務単位か部門単位か)
- それぞれのブロックごとにイベント系を洗い出す。(この時点では、正規化を意識しない)
- イベント系に対する正規化を行なって、リソース系を洗い出す。
- リソース系に対する分類の洗い出しを行なって、リソース系の正規化を行う
- 導出系の整理をして、最終的な正規化を行う
1. ブロック分け
ブロックの切り方は、細かすぎず大まかすぎずが基準。例えば、部門単位なら営業、購買、生産、経理といった形になります。また、業務単位なら販売管理、購買管理、、生産管理という形になります。大抵は、組織は業務上の役割の分類で括られているので、近似値になる場合が多い。
- ある程度のブロックに分ける
- 分けるときは、プロジェクト内で統一した切り口に揃える
2.イベント系エンティティの抽出
システム化の目的は、イベントを管理する為とも言える。顧客情報が管理されているのも売上を上げたいから情報管理を強化したいという目的がある。これを手がかりに設計を進めていく。イベント系のエンティティは意外と少ない。イベント系を見分けるには、「いつ(when)」が属性として設定できるもの、もしくは「~する予定」などタイムスタンプを打てるもの。会計と業務プロセスとシステムの紐付け方に関する書籍は役に立つ。まずは、正規化は考えないで大きなエンティティを書き出すことを意識すると良い。大抵は、その組織の役割に沿ったものが出てくるだけで見出す作業は済む。次のような組織体の入力系業務と出力系業務を見ればおおよそのあたりはつく。
- 入金と出金
- 入庫と出庫
- 受注と発注
購買部門
- 発注
- 受入
営業部門
- 受注
- 見積
- 納品
3.イベント系エンティティの正規化
イベント系のエンティティの見当がついたら正規化をしてみます。イベント系エンティティの正規化が進むとリソース系のエンティティ候補が浮かび上がってきます。
4.リソース系エンティティの分類を整理
リソース系のエンティティが見出せたら、その分類を整理していくことになる。柔軟に対応するために交差エンティティなどをできるだけ使っておくと良い。まずは論理データ構造を構築することを優先する。join処理などの多発は後になって考える。
5.ブロック間でのリソース統合
データベース設計においてここが一番の肝となる。イベント系エンティティはそれぞれのブロックで独立しているがリソース系はあちこちで使われることが多い。なので、ブロック間でのリソース系エンティティ統合を行う。
6.導出系の整理
導出ルールの整理を行なって冗長性の排除を行なっていく。複数エンティティを組み合わせて導出するものは、ビューで済ませる。