データモデリングにおけるいくつかの注意ポイント

# はじめに

社内の新人向けDB研修でデータモデリング（RDB論理設計）に関してレビューを行うことがあります。その際によく指摘する内容をいくつかリストアップしておきたいと思います。（例として用いている題材は実際に研修で使っているものとは異なります）

設計方法論というより基礎的かつ多少ニッチな内容を選んでいるのですが、既存システムでも問題があるケースも散見されるので、参考になればと思います。

# 本題
## 1. 主キーは更新されないものを選択する

一般に主キーが満たすべき条件として以下が挙げられます。

- 必須性
- 一意性
- 完全関数従属
- 不変性

上３つは概ね守られるのですが、４番目の不変性（主キーの値は変わらない）を満たせないモデルをたまに見かけます。そのようなモデルは好ましくないとされています。

よく例として挙げられるのは、ユーザーがシステムにログインする際のIDとしてメールアドレスを用いることから、ユーザーテーブルの主キーをメールアドレスにするモデルです。

![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/907000c6-d3cc-56c1-c594-dc62f57a381b.png)

頻度は高くないかもしれませんが、ユーザーのメールアドレスは変更されることがあり得ます。上述のモデルでは同一ユーザーによるメールアドレス変更前後の２つの注文で、同じユーザーによる注文と判別することが難しくなります（２つの注文が異なる注文ユーザーメールアドレスを持つため）。

上の例は有名なのでさすがに実システムで見ることはありませんが、以下のようなケースは時々見かけます。

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/8b207331-9414-a200-3c6f-59dbee97f8df.png)

ここでは明細No は明細一覧の表示順として 1, 2, 3, ...という連番を想定します。このレコードは注文が確定した際に初めて作成され、その後に変更がないのであれば問題ないのですが、表示順の並び替えや途中への明細の追加などがあると、連番を保つために明細Noが更新されることになります。

こういうケースでは、表示順というカラムを属性として別途持ち、明細Noは不変とする方が好ましいです。

主キーの値が変更されるデータモデルは、上で述べた通りそれをリレーションシップで参照するテーブルがある場合に問題になりますが、それ以外でも主キーの値が

- ユーザーにメールで通知される
- 他のシステムへ連携される
- データ分析のためにDWHへ転送される

などシステム外に連携されると（大体のデータは連携されるはずですが）、連携されたIDが指し示すデータがどれだったか辿ることが困難になります。その観点でも主キーは不変なものを選ぶことが好ましいです。

![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/263bcf1e-42de-ad7c-591e-7a01e11acc6e.png)

（DWHへのデータ連携パイプラインを実装する際に、このような主キーに変更が入るテーブルはとても厄介です）

## 2. 主キーの使い回しはしない

厳密にはデータモデルの話とは言いづらいのですが、主キーの使い回しも問題を生みやすいです。

以下のようなテーブルを考えてみます。

![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/016dec86-3d50-7981-6f2c-c1933ff48c1d.png)

商品ID=101をベッドから別の商品（例えば敷布団）に使い回してしまうと、注文明細テーブルから辿ってどの商品が注文されたかを追うことができなくなります。（多くのケースでは注文明細テーブルに商品名なども非正規化して持たせることが多いので破綻はしないのですが）

テーブル設計者として、こういう運用がされないようにしっかり管理したいものです。

（今はどうか分かりませんが、昔の楽天市場の購入履歴表示はこの問題があったような）

## 3. １レコードの単位を明確にする

ER図やテーブル定義を見ていて、1レコードがビジネス上の何に対応するのか分かりづらいケースがたまにあります。

例えば、居酒屋などの会計データをモデリングする際に、以下のようにモデリングしたとします。

![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/2873a695-756d-e60f-7bfd-a4f4ccb4de86.png)

まずビール４杯を頼み、その後にビール２杯と枝豆１つを注文した場合、何レコードできるでしょうか？

- ２レコード：ビール×6、枝豆×1
- ３レコード：ビール×4、ビール×2、枝豆×1

どちらでもテーブルに格納することはできますが、注文の流れを後で確認・分析したいのであれば後者のように格納する必要があるので、テーブル定義としてそのルールを明示すべきです。

逆に、前者としたいのであれば、主キーを会計IDとメニューIDの組にするなど工夫した方が良いと思います。

この手の話は履歴を管理するテーブルでも良くあります。例えば、商品の価格が原則年１回４月に改定されるが、例外的に任意の月でも変わることもある場合を想定します。

以下のようなデータモデルにおいて、

![06a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/40b42518-a606-8df6-edd9-1a4862273ade.png)

- 価格が変わったタイミングでのみ１レコード増やすのか、毎月分のレコードを持つのか？
    - 前者と後者では、商品テーブルとの結合ロジックが異なる
    - 前者の場合、年月というカラム名はやめて有効開始年月というカラム名にした方が良い
- 年１回の価格改定でたまたま価格が変わらなかった商品もレコードを新たに持つのか？
    - 持たない場合は、年１回の価格改定時において個々の商品ごとに変更有無のチェックが必要
    - 持つ場合、価格が変わったかどうかの判定が少しだけ複雑になる（運用を考えると大体こちらになる気がしますが）

というのは明確にしておくと良いと思います。

あとは、マスターテーブルでも１レコードが何を指すかブレると危険です。

- B2Bシステムの顧客マスタで１レコードは企業か？部署か？担当者か？
- 商品マスタなどにおいてセット商品（複数の商品をまとめたもの）をどう扱うか？

データによって１レコードの単位がバラバラだと、データ分析する際にはかなり大変になります。（１レコードの単位が大きすぎて望む分析ができないケースなど）

## 4. 既存のID体系を利用する場合は採番されるタイミングや範囲などを意識する

主にマスターデータに関してですが、新たに主キーのIDを採番せずに既存のID体系を利用する場合があります。

- 営業管理システムを新たに作る際の営業先企業マスターのIDとして、社内の販売管理部門が既に管理している顧客企業マスターのIDを利用する
- 購買先企業マスターのIDとして、社外で管理されているID（[TDB企業コード](https://www.tdb.co.jp/lineup/code.html)や[国税局法人番号](https://www.houjin-bangou.nta.go.jp/)、[LEI](https://www.lei.jpx.co.jp/lei/about.html)など）を利用する

自分のシステムで独自にIDを採番すると、他システム/他組織とのデータ連携/共有時にマッピングが必要になるといった弊害があるので、既存のID体系を利用することは一般的に良く行われます。

ただし、その際にいくつか留意点があります。

- 採番されるタイミング
    - 上の営業管理システムの例において、今まで取引がない企業（＝販売実績がない企業）を登録したい時点で、その企業は販売管理部門の顧客企業マスターに既に含まれているか？もしくは営業管理システムに登録したいタイミングで販売管理部門側にも登録をお願いできるか？
    - （加えて削除タイミングや変更ポリシーなどIDライフサイクルがマッチするかも重要）
- 採番対象の範囲
    - 購買先マスターのIDとして法人番号を利用する場合、法人番号が割り振られていない団体と取引することはないか？（最近はかなり減った印象ですが）
- IDの粒度
    - TDB企業コードなどは厳密に１企業に対して１つのIDを割り振るが、そのルールでジシステムにとって問題ないか？（特定の大企業の事業所は個別の企業として扱いたい、共同取引など）

一方で、上述のような課題を回避するために独自にID採番を行うとそれは別の大きな問題を生むので、実際には要件を調整する、既存ID管理組織と交渉するなどしながらID体系の再利用を検討することが多いです（特に大きな組織では）。ただし、その判断がどのような問題を発生させるかは事前に洗い出しておく必要があります。

## 5. 異なるタイミングで発生するデータはエンティティーを分ける

ECサイトの以下のような要件（わざと曖昧な部分を残しています）を例に説明します。

- ユーザーは商品を購入することができる（単純化のため、１回の購入では１種類の商品のみ購入可能とする）
- ユーザーは注文した商品に対してコメント（感想、評価など）をフリーテキストで投稿することができる

好ましくない例として以下のデータモデルを考えてみます。

![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/04c210ab-844f-dfeb-871b-3359023ab937.png)

このデータモデルは以下のような点で困ることがあります（パフォーマンスなどの問題は除く）。

- コメントに関して例えば以下のような仕様変更を行いたい場合、注文テーブルに影響が及び、注文機能の改修が必要
    - コメントがフリーテキストだけでなく、評価軸（価格、品質など）に対するスコア（数値）も付けられるようにしたい
    - 一回の注文で複数のコメントをつけられるようにしたい
    - 注文していなくともコメントがつけられるようにしたい（ECサイト以外に実店舗も持っていて、実店舗で購入した場合でもコメントを付けられるようにしたいなど）
- パフォーマンスや個人情報保護の観点で、注文情報は一定期間経過したら削除したいが、コメントは他のユーザーの参考になるため残しておきたい

これらは一例ですが、問題の本質は「注文」という機能と「コメント」という機能が注文テーブルを介して影響を及ぼし合ってしまうことにあります。

このあたりの影響を見極める必要があり、複雑な業務では簡単ではないのですが、簡易的なチェック方法として異なるイベント（今回でいえば「注文」と「コメント付与」）で同じテーブルを作成・更新している場合は注意した方が良いです。これをチェックするためにもCRUD・IRUN分析をするのが好ましいです。

特にIRUN分析を行うと、１つのエンティティーに対して２つの機能から I があるので何かおかしいと気付きやすいです。（CRUDだと注文機能でC、コメント付与機能でUとなり、ステータス変更などと見分けが付きづらい）

![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/30927f2a-6043-615c-9abd-84511a65c486.png)

昔に先輩から、CRUD・IRUNは機能とテーブルアクセスのマッピングを整理するだけではなく、データのライフサイクルを明確にすることも重要な目的と教わりました。その観点でいうとライフサイクルが違うデータは１つのテーブルに同居させないとも言い換えることができるかなと思います。

また、話が脇道にそれますが、テーブルをトランザクション系とマスター系に分類することが良くありますが、これをイベント系とリソース系と呼ぶこともあるそうです。トランザクションテーブルの１レコードは１つの業務イベントに紐付くということを意識できるので、後者の呼び方の方が私個人は好きです。

## 6. リレーションシップの参照先により業務ルール・制約に影響が出ることを意識する

前の章の注文とコメントの例を引き続き考えます。

注文とコメントのエンティティーを別に分けたとして、コメントエンティティーのリレーションシップ参照先は２通りあります。（単純化のためにユーザーエンティティーは除外しています）

- A案
![08a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/b6fdad4b-9fbd-273d-42a9-f5ff941e65c8.png)

- B案
![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/5473d4d9-f575-59a9-afe4-5624ad0503b9.png)

A案とB案はコメントエンティティーからのリレーションシップの参照先が異なりますが、それにより業務上の意味が異なってきます。

- A案
    - 注文に対してのみコメントを付与できる（注文なしでコメントを残せないことを強制できる）
    - 同じ商品を２回注文しても、それぞれに別のコメントを付けられる（１回目に購入した時には「良かった」、２回目の購入時には「悪かった」というコメントも残せる）
    - コメントを残したまま保持期間を過ぎた注文を削除することはできない
- B案
    - 注文しなくてもコメントが残せるデータモデルになっている（実際にそれを許すかはアプリの仕様次第）
    - 同じ商品を2回以上注文した場合、その商品に関するコメントがいつ注文した商品に紐づいているかを追うことはできない
    - コメントを残したままでも保持期間を過ぎた注文を削除できる

どっちが正解かは業務都合なので一概には言えないですが、リレーションシップの張り方によって業務ルール・制約が異なってくると認識しておく必要があります。（正確には逆で、業務ルール・制約に基づいてリレーションシップを選択する必要があるということですが）

## 7. その他

その他でよく指摘・説明する内容を以下に示します。この辺りはデータモデリングの書籍などでもよく出てくるので、説明は割愛します。
 
- 依存/非依存のリレーションシップを区別する
- 履歴保持の有無を検討する
- 命名規則（ドメイン管理も含む）

# さいごに

新人向けの研修でよく指摘する内容ということで、設計方法論というより具体的な話を取り上げてみました。

結局はいろんな観点を学んだ上で経験を積んでいくしかないのかなとは思いますが、何かの参考になれば。

最後に初級者向けのデータモデリングの入門となるような書籍を挙げておきます。

- [実践的データモデリング入門](https://www.shoeisha.co.jp/book/detail/9784798103853)
    - 基礎的な部分から丁寧に説明されていて、入門にとても良いと思います

https://www.shoeisha.co.jp/book/detail/9784798103853

- [グラス片手にデータベース設計 販売管理システム編 第2版](https://www.shoeisha.co.jp/book/detail/9784798144641)
    - 販売管理という業務を題材にデータモデリングが学べる本
    - 業務の要件・制約がデータモデリングにどう反映されるかが分かりやすい
    - 業務知識も学べるのが良い（「会計システム編」、「生産管理システム編」もおすすめ）

https://www.shoeisha.co.jp/book/detail/9784798144641