データモデリングにおける適切な関連の作り方

この投稿は[ちゅらデータアドベントカレンダー2023](https://qiita.com/advent-calendar/2023/churadata)の4日目の記事です。

# 導入
RDBを使ってデータベースを構築するときは、必ずデータモデリングを行う必要があります。RDBのRは ~~リレーション~~ リレーショナルのRなので、テーブル同士の ~~リレーション~~ リレーションシップ（関連）を適切に作成することが重要です。しかし、この「適切」というのは、具体的にどういうことなのでしょうか？　この記事では一つの提案として、テーブルをいくつかの種類に分けて、種類ごとの関連の条件をつけることで、システマチックに適切と思われる関連を作る方法を、披露したいと思います。

# あまりよく考えていないモデリングの例
ある**ユーザー**が、ECサイトで**商品**を**購入**して、**入金**されたら**出荷**するという、よくある構造のモデリングを行ってみます。この説明から単語を抜き出して、以下のようなテーブルが必要でないかと検討してみます。
```plantuml
@startuml
hide empty members
class ユーザー
class 商品
class 購入
class 入金
class 出荷
@enduml
```
少し単純ですが、良さそうです。さて、これらのテーブルにどうやって関連をつけていきましょうか？
「んーと、購入も入金も出荷も、それぞれユーザーが関連するものだよな。だからユーザーとこれらのデータを関連づけると良いんじゃないだろうか？　そして、商品を購入するわけだから、商品と購入の間にも関連があると良いんじゃなかろうか...」
そんな発想のもと、こんな関連をつけてみました。
```plantuml
@startuml
hide empty members
class ユーザー
class 商品
class 購入
class 入金
class 出荷
商品--購入
ユーザー--購入
ユーザー--入金
ユーザー--出荷
@enduml
```
一見うまく全てのテーブルがつながったように見えますが、さて、設計レビューは通るでしょうか？

## レビューにて
「このモデルって、入金があった時にどの購入に対するものかって、どうやってテーブルで管理しているの？」
「えっと、入金からユーザーを特定して、そこから購入を...」
「でもこのユーザー、過去にもたくさん購入しているかもしれないよね。どの購入が今回入金分だとわかるの？」
「さ、最新の購入が...」
「でも、同じ日に２回に分けて購入する場合もあるよね。」
「き、金額が同じだったら...」
「同じ金額の購入がないとは限らないし、それに金額間違って振り込んじゃうことも、あるんじゃない？　そういうイレギュラーケースには対応できないと困るよね。」
「...」
「それと、出荷テーブルからはどの商品を出荷したのか、わかるのかな？」
「...」
「日を改めて、もう一回レビューしよっか？」

初回レビューは散々だったようです。

# 適切なテーブル設計について

雰囲気でテーブル同士を繋いでも、「適切」な関連でなさそうなことはわかりました。さて、どうすれば良いでしょうか？　順に説明していきます。

## 1. テーブルを分類する
まず、テーブルを次のルールで分類してみることを提案します。
1. テーブルのレコードが、ユーザー操作により追加されるもの（ユーザー主導テーブル）と、管理者に追加されるもの（マスターテーブル）に分ける
1. ユーザー主導テーブルを、一度追加したら基本的には更新されないもの（トランザクションテーブル）と、ユーザー操作により何度も修正されるもの（ステートテーブル）に分ける
```plantuml
hide empty members
abstract class テーブル
abstract class ユーザー主導テーブル
abstract class マスターテーブル
abstract class トランザクションテーブル
abstract class ステートテーブル
テーブル<|--ユーザー主導テーブル
ユーザー主導テーブル<|--トランザクションテーブル
ユーザー主導テーブル<|--ステートテーブル
テーブル<|--マスターテーブル
```
そうしたら、実際にモデリングするテーブルを、トランザクションテーブル、ステートテーブル、マスターテーブルのどれかに属するように分類していきます。わかりやすいように、トランザクションは赤、ステートは黄色、マスターは緑にしてみましょう。
商品は、一般的に管理者により登録されるものなのでマスターです。
ユーザーはいくつかのケースがありますが、ここではユーザーが自身でサイトにサインアップすることで出来上がるものであり、その後ユーザー情報の変更として住所や決済方法を変更することも考えられるのでステートとして扱いましょう。
購入、入金、出荷はそれぞれその時点で発生した記録であり、その後変更されることはないものなのでトランザクションとして扱います。

```plantuml
@startuml
hide empty members
skinparam class {
    BackgroundColor<<マスター>> #ccffcc
    BackgroundColor<<ステート>> #ffffcc
    BackgroundColor<<トランザクション>> #ffcccc
}
class ユーザー <<ステート>>
class 商品 <<マスター>>
class 購入 <<トランザクション>>
class 入金 <<トランザクション>>
class 出荷 <<トランザクション>>
@enduml
```

## 2. トランザクション同士に関連を作成する
次にトランザクション同士に関連をつけていきます。ポイントとしては、トランザクションが出来上がる順序を意識して関連をつけていくということです。
今回の三つのトランザクションの中では、まず初めに購入ができあがります。その購入を元に入金が行われ、入金をトリガーにして出荷が行われます。全てのトランザクションが関連づけられたら、マスターやステートをトランザクションに関連づけていきます。
```plantuml
@startuml
hide empty members
skinparam class {
    BackgroundColor<<マスター>> #ccffcc
    BackgroundColor<<ステート>> #ffffcc
    BackgroundColor<<トランザクション>> #ffcccc
}
class ユーザー <<ステート>>
class 商品 <<マスター>>
class 購入 <<トランザクション>>
class 入金 <<トランザクション>>
class 出荷 <<トランザクション>>
購入--入金
入金--出荷
商品--購入
ユーザー--購入
@enduml
```

## 再レビュー
「どれどれ、お、テーブルに色がついているね。赤いのがトランザクションか、なるほど、なるほど」
「出荷の元になった入金、そして入金の元になった購入が辿れるようになりました」
「なるほど、確かに辿れるね。ところで、出荷は分割することもできるのかな？」
「分割は、今回は検討していませんでした。」
「このモデルのままだと、出荷が二つ存在した時にどちらに商品が関連づいているかは決まらないね。これは次の課題だね」
「はい」

宿題はもらったものの、未検討だった仕様の漏れに関する指摘だったので、良いレビューだったのではないでしょうか？

# まとめ
1. 抽出したテーブルを、「レコードを追加する主体」と「レコードの編集の有無」により分類する
1. トランザクションテーブル同士の関連を優先して作成する
この二つのルールに沿って設計することで、ある程度「適切」なモデリングが行える例を示しました。ぜひ、次にモデリングをする機会があったら試してみてください。

## 補足：トランザクション同士を関連づけると適切になる理屈について
トランザクションテーブルと、ステートテーブルあるいはマスターテーブルとの関連は、業務的な要請ではなく正規化の一環として作られるものが多いです。そのため、それらの関連を中心にモデリングを行っていくと、業務的な観点から不備や矛盾が発生しやすいため、今回のようなルールを提案しています。

## 参考資料
ここで紹介した方法は、ピーター・コードが記した「[Javaエンタープライズ・カラーUMLによるJavaモデリング](https://www.amazon.co.jp/Java%E3%82%A8%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%97%E3%83%A9%E3%82%A4%E3%82%BA%E3%83%BB%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E2%80%95%E3%82%AB%E3%83%A9%E3%83%BCUML%E3%81%AB%E3%82%88%E3%82%8BJava%E3%83%A2%E3%83%87%E3%83%AA%E3%83%B3%E3%82%B0-%E3%83%94%E3%83%BC%E3%82%BF%E3%83%BC-%E3%82%B3%E3%83%BC%E3%83%89/dp/4894712598)」にインスピレーションを受けて考えました。絶版になってしまっていますが良い本ですので、機会があれば読んでみることをお勧めします。