
分析用とCDP用のデータモデルを考える ーローデータから2つの目的を満たす設計論

はじめに
データ基盤とデータマートを設計していると、こんな疑問にぶつかることがあります。
「分析チームが使うデータマート」と、「CDPやMAに連携するためのデータマート」は、同じ構造でいいのかな?
結論から言うと、同じ構造では足りません。 2つは目的がまったく異なるため、あるべきテーブルの形も変わってきます。
この記事では、その違いを整理したうえで、広告媒体・GA4・メール・CRMといった複数のローデータソースから、2種類のデータマートを効率よく構築するための設計パターンを紹介します。
目次[非表示]
2つのデータマートは「目的」が違う
まず前提として、なぜ2種類が必要なのかを整理します。

分析用マート:「何が起きたか?」を集計する
BIツールやダッシュボードで使う分析用データマートは、集計のための構造が必要です。
「チャネル別×月別の売上は?」「新規ユーザーの流入経路は?」——こうした問いに答えるには、集計の軸(ディメンション)を自由に切り替えられるスタースキーマが適しています。ファクトテーブルを中心に、日付・ユーザー・チャネル・商品などのディメンションテーブルが放射状に配置される構造です。
主な操作は GROUP BY による集計。出力は数値・グラフ・KPIです。
CDP用マート:「誰に届けるか?」を絞り込む
一方、CDPや広告配信・MAツールに連携するためのデータは、最終的な出力がユーザーIDのリストです。
「直近30日未購買かつLTV上位20%のユーザー」を抽出するには、複合条件を1テーブルに横断して走らせる必要があります。JOINを極力減らし、1ユーザー1行のワイドテーブルに属性・行動サマリ・セグメントフラグを事前計算して持つ構造が適しています。
主な操作は WHERE による条件抽出。出力はユーザーIDのリストです。
観点 | 分析用マート | CDP用マート |
主な問い | 何が起きたか? | 誰に届けるか? |
主な操作 | 集計(GROUP BY) | 条件抽出(WHERE) |
出力 | 数値・グラフ・KPI | ユーザーIDリスト |
テーブル構造 | スタースキーマ(縦の構造) | ワイドテーブル(横の構造) |
主語 | イベント・期間・チャネル | ユーザー |
一言でまとめると、分析用は「切り口を自由に変えられる縦の構造」、CDP用は「条件を横断して絞り込める横の構造」です。
ローデータから2つを作るには「共通の中間層」が鍵
では実際に、広告媒体・GA4・メール・CRMなどのローデータから、この2種類のマートをどう構築すればよいのでしょうか。
ここでよくある失敗が、ローデータから直接それぞれのマートを作ろうとすることです。変換ロジックが重複・分散し、ソースが変わるたびに両方の修正が必要になります。
正解は、共通の中間層を挟むアーキテクチャです。

レイヤーの役割を整理する
ステージング層は、ソースの形をそのまま保ちながら、型・命名だけを統一します。「広告のuser_idとCRMのcustomer_idは同じ概念だ」という判断はここでは行いません。ビジネスロジックは一切入れない、忠実な複製層です。
統合中間層が設計の核心です。複数ソースのIDを「同一人物」として名寄せするIDスティッチングを行い、全ソースの行動ログを縦積みで統合します。ここで作る int_user_identity(統合ユーザーID)が、下流の2つのマートが同じ「ユーザー」を主語にするための基盤になります。
設計の核心:IDスティッチング
複数のデータソースには、それぞれ異なるIDが存在します。
GA4には client_id
CRMには customer_id
メールツールには email_address
広告プラットフォームには gclid や fbclid
これらを user_unified_id という共通キーに名寄せするのがIDスティッチングです。

この統合IDが確立されることで、「GA4で流入したユーザーが、その後CRMで購買した」という横断的な行動履歴が初めてつなぎ合わせられます。
IDスティッチングの設計品質が、下流すべての品質を決めると言っても過言ではありません。ここが甘いと、「同一人物への重複配信」や「購買済みユーザーへの新規獲得広告」といった施策ミスに直結します。
統合中間層から2つのマートへ
共通の中間層が整ったら、それぞれの目的に合わせて変換します。
分析マート(ディメンショナルモデル)
int_user_events の行動ログを集計・再構成し、スタースキーマに変換します。
1 2 3 4 5 | fact_conversions ├── dim_date (日付ディメンション) ├── dim_user (int_user_identity から) ├── dim_channel (ソース × キャンペーン情報) └── dim_product (商品マスタ) |
CDPマート(ワイドテーブル)
int_user_events をユーザー単位に集約し、属性・行動サマリ・セグメントフラグを横持ちにします。
1 2 3 4 5 6 7 8 | user_master(1ユーザー1行) ├── ltv_total (累計LTV) ├── purchase_count_30d (直近30日購買回数) ├── last_purchase_date (最終購買日) ├── last_channel (最終接触チャネル) ├── email_open_rate_90d (メール開封率) ├── is_vip (VIPフラグ) └── is_churn_risk (離反リスクフラグ) |
セグメントフラグを事前に計算して持つことで、MA・広告配信側との連携がシンプルになり、抽出クエリが WHERE is_vip = true AND purchase_count_30d = 0 のように直感的に書けます。
よくある落とし穴
この設計を理解していないと、現場でこんな問題が起きます。
分析用マートをCDP抽出に使おうとする → 毎回重いJOINが発生し、セグメント抽出が遅くなる。施策のリードタイムが伸びる。
CDP用のワイドテーブルで細かい分析をしようとする → 集計軸の柔軟性がなく、「チャネル別月次の推移」のような問いに答えられなくなる。
IDスティッチングをスキップして両マートを作る → 「同一人物」の行動がつながらず、分析もセグメントも精度が落ちる。
2つは目的が違うので、レイヤーを分けて設計するのが正解です。
dbtでの実装イメージ
Snowflake + dbt で構築する場合、フォルダ構成はこのようになります。
1 2 3 4 5 6 | models/ staging/ ← stg_google_ads, stg_ga4_events, ... intermediate/ ← int_user_identity, int_user_events marts/ analytics/ ← fact_*, dim_*(分析マート) cdp/ ← user_master, user_segments(CDPマート) |
intermediate/ が共有資産として機能し、ソースが変更されてもここを修正すれば下流両方に反映されます。変換ロジックの重複がなく、テストも書きやすい構成です。
まとめ
今回の内容を整理します。
分析用マートとCDP用マートは目的が異なり、テーブル構造も変わる
ローデータから直接それぞれを作ろうとすると、ロジックが重複・分散して管理不能になる
共通の統合中間層(IDスティッチング)を挟むのが理想のアーキテクチャ
統合中間層から分析マート(縦の集計構造)とCDPマート(横の抽出構造)を別々に作る
dbtでは staging/ → intermediate/ → marts/analytics/ + marts/cdp/ の3層構成で整理できる
データ基盤を設計するときは、「このデータは何のために使われるのか」という問いから始めることが重要です。分析とCDPでは問いが違うため、答えとしてのデータ構造も変わる——その認識が、使われるデータ基盤を作る第一歩だと思います。





