ディメンショナルモデリングを理解するためにStar Scherma The Complete Referenceを読んでみた

# 最初に

データ基盤の設計にあたり、ディメンショナルモデリングをより詳しく学ぶため、「Star Schema The Complete Reference」を読みました。必ずしも本文を忠実に訳しているわけではありません。私の理解をもとに章立てを再構成しています。

[amazonリンク：Star Schema The Complete Reference / Christopher Adamson](https://www.amazon.co.jp/Schema-Complete-Reference-Christopher-Adamson/dp/0071744320)

# 構成

はじめに、基本的な定義をおさらいします。
次に、設計を進めるなかで発生しうるトピック・検討事項に対して、それぞれの推奨方針を記述します。

# スタースキーマとは
データウェアハウス（DWH）および分析システムのためのモデリング手法の一つに、ディメンショナルモデリングがあります。モデリング図の形状から、スタースキーマとも呼ばれます。

### 業務システムと分析システムとの違い
|業務システム|分析システム|
|:---|:---|
|業務の実行をサポートする|業務の評価をサポートする|
|業務を終えればデータは削除orアーカイブする|過去データも保持する|
|整合性を保ち登録・更新・削除する|大量データを様々な切り口で検索する|

ディメンショナルモデリングは、これらの特徴に特化した設計手法です。

## ファクトとは
各プロセスの実績です。例えば、数値、金額、件数、発生頻度　等が含まれます。

## ディメンションとは
集計単位や抽出条件です。例えば、年月日、顧客、製品　等が含まれます。


## ファクトのパターン
### トランザクション
プロセスのイベントを記録します。タイミングの異なるプロセスは別のファクトとして扱います。例えば、契約、出荷　等が含まれます。

### スナップショット（周期的なファクト）
定期的な間隔で各時点の状態を記録します。例えば、在庫、温湿度、残高　等が含まれます。

### 累積スナップショット
上記のスナップショットは原則追記のみですが、累積スナップショットは状態の移行ごとに更新します。例えば、ある契約における承認状況、経過日数、状態移行日付を1レコードで表します。

### 数値を持たないファクト（ファクトレスファクト）
トランザクションと同様にプロセス発生を記録しますが、集計対象となる値を持ちません。プロセス発生の頻度・件数・間隔　などを分析します。

# データモデル設計時の検討事項

## 過去のプロセス発生時点の属性で分析したい
例として、顧客住所が今年変わったが、昨年までの売上は変更前の住所で集計したいとします。この場合、過去時点の属性を保持する必要があります。ディメンションの履歴を保持する方法はSCD (Slowly Changing Dimension) type 1,2,・・・ と呼ばれます。SCDは他の記事でわかりやすく紹介されているため触れません。
履歴を保持すると、ソースシステムのキー（自然キー）ではユニークになりません。そこでディメンションとの関連づけにはサロゲートキーを使います。データロード時にサロゲートキーを採番します。
- SCD type2ロード処理ステップ
    - ディメンションのロード
        1. ソースシステムのキーにより新規・変更を判別する
        1. 既存レコードに「最新」列があれば更新する
        1. サロゲートキーを採番する
        1. レコードを追加する
    - ファクトのロード
        1. 最新状態のディメンションのサロゲートキー割当する
        1. ※過去時点のファクトの場合、該当するディメンションから割当する


## 複数のファクト・属性を関連づけて分析したい
例として、受注額に対する売上額を比較するとします。この場合、発生タイミングの異なる2つのファクトを同じ粒度に揃えてから結合する必要があります。もし、集約単位やディメンションのコード体系が異なると正しく分析できません。
このため、それぞれのファクトは同じディメンションに関連づけられているか、もしくは
ディメンション間の整合性(conformance)がとれている必要があります。
- 整合性のとれたディメンションとは
    - 同一の粒度であること　または
    - 集約により粒度を揃えられること（日⇔月、市町村⇔都道府県）

- 引用：Star Scherma The Complete Reference 図5-3
![fig5-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164170/e530b164-f7a0-7e13-8506-8e9e631f642a.png)
    
分析対象が増えてくると、スタースキーマではわちゃわちゃして見づらくなります。関係するファクト・ディメンションの整理にはマトリックスを用います。

- 引用：Star Scherma The Complete Reference 図5-7
    ![fig5-7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164170/1a95a9be-e35b-b30e-5d53-54a390c40435.png)

※複数のファクトにまたがる分析はドリルアクロスと呼ばれます。


## １つのファクトに対応する属性値が複数存在する
正しくファクトを集計するため、ファクトとディメンションの関係はｎ：１とする必要があります。
もし、属性値が複数ありｎ：ｎの関係になる場合はどうすればよいでしょうか。
例として、担当者が二人いる、または　１つの製品が複数業種に適用される　とします。

重複数が確定しているならば、一つのディメンションレコードに横持ちすることも可能です。
もしくは、担当者の役割（ロール）の違うならば、ファクトの列を増やしてロールごとに担当者を割り当てます。（販売担当者、検査担当者　等）

また別の方法として、「業種グループ」を設け、ｎ：ｎの関係を１：ｎに変換します。この「業種グループ」はブリッジと呼ばれます。
- 引用：Star Scherma The Complete Reference 図9-11
  　![fig9-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164170/14310ea2-15aa-053b-5271-7a9a1efeee08.png)

また別のケースとして、もし属性値が階層構造をもつ場合はどうすればよいでしょうか。例として、顧客企業だけでなく顧客の親会社で集計したいとします。
階層の深さが確定しているなら横持ちします。
もしくは、先程と同様にブリッジを介して上位階層または下位階層と紐付けます。
- 引用：Star Scherma The Complete Reference 図10-12
![fig10-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164170/2d71f516-d44e-f032-21f0-cf5d9015ed16.png)

### ブリッジの注意点
いずれの例にしても、ブリッジを用いた場合のリスクがあります。
- 構造が理解しづらくなる
- 誤ってクエリするとダブルカウントする

一般利用者向けのインターフェースとしては、単一の値（主担当者 or 最上位の親会社）を返すのが安全です。

## ファクトに対応する属性値がない（NULL）
もしファクトに対する属性値が未定・不明・誤り等のためにセットされない場合、NULL値が発生します。
ディメンションモデルに限った話ではないですが、NULLを考慮したソートやフィルタは面倒です。一般利用者は誤った分析結果を導いてしまう可能性もあります。
対応として、NULLは「invalid」等の値に置き換えます。
    
## ディメンションの列数が多くなってきた
100カラム程度であれば列数が多いこと自体は問題ありません。
しかし属性の変更頻度が高い場合は、ストレージコストが上がる、ロード処理時間が伸びる、等の問題があります。対応として、属性を分割する方法があります。
- 一つの同じプロセス・業務・タイミングに関連する属性を同じディメンションとする
- どのメジャーなプロセスにも属さないもの（junk dimension ）を切り出す
- 変更頻度の高いパターンを別ディメンションに切り出す
（例）
     - 保険契約者の属性のうち「既婚/未婚、家族数、扶養家族数、扶養未成年者数」の組み合わせパターンを別ディメンションとする
- 共通属性とパターン別属性に分割する
（例）
    - 共通属性
        - 商品（価格、）
    - 種別属性
        - 書籍単体商品（著者、ISBN・・・）
        - 楽曲単体商品（アーティスト、レーベル・・・）
        - 定期購読商品（発行頻度、購読期間・・・）
            
## 利用者の分析効率を改善したい

### 認知負荷を下げる
- わかりやすいデータを提供する（認知負荷を下げる）
    - 「07」よりも「United States」
    - 姓＋名　だけでなくフルネームも保持する

### パフォーマンスを上げる

#### ファクトの列を減らす
レコード数が多いほど効果は大きくなります。

#### 結合を減らす
スノーフレークスキーマはBIツール等での制約がない限り避けます。

#### ファクトの集約
集計レコードを設けることで、大幅に性能改善が可能です。ただし、明細との整合性を保つことが必要です。
たとえば、ディメンション属性変更を過去にさかのぼって反映（scd type1）する場合、変更属性値のみの加減算だけでは集計値が正しくない可能性があります。属性変更時には全て再計算する必要があるかもしれません。

また、一般利用者向けには明細・集計を同時に提供すると混乱を招く可能性があります。インターフェース上は明細・集計のいずれか一方とする方がよいです。

#### 派生形
分析クエリでの性能を重視して設計を一部変更するケースもあります。
- marged fact
    - 分析時に頻繁に同時に扱われる2つのファクトを一つにする
- pivoted fact
    - 横持ちする
- sliced fact
    - 条件別にファクトを分ける（件数を減らす）
- set operations fact
    - 分析時に利用頻度の高い計算値をファクトに保持しておく
- behaviour dimenshon
    - 分析時に利用頻度の高い値をディメンションに保持しておく
    （例）
        - 最新取引日
        - 年間売上

※これらの集約・派生形は、ロードプロセスの複雑性とのトレードオフを検討する必要があります。

# 最後に
この本は、下の記事を見かけて、よし読んでみよう、と思いたちました。
- [データエンジニア道の俺のバイブル : Zenn](https://zenn.dev/pei0804/articles/data-enginner-my-bible)

おかげさまで知識が整理され、クリアになりました。紹介いただき感謝します。