current-state 分類:現在のプラン状態を intermediate に切り出す
Stage 4 — 第1章 | dbt入門カリキュラム 推定学習時間:40〜50分 | 難易度:★★★★☆
この章で学ぶこと
Stage 3 までで、activity mart と membership mart を学びました。 Membership mart が 「何人いるか」 を数えるのに対し、Monetization 次元は 「今その人はどの課金状態か」 を表します。
SaaS やサブスクリプション型プロダクトでは、会員数と課金状態は別の問いです。 会員数は terms 同意や登録日を軸に数えますが、課金状態は entitlement(権利) と purchase(決済履歴) を組み合わせて定義します。 この定義を mart ごとにコピーすると、Premium セグメントの解釈が表ごとにずれます。
そこで dbt では intermediate モデル に current-state 分類を 1 箇所に固定し、下流 mart が共通の次元として参照するパターンが一般的です。
まず dbt の作法を整理し、続けて DS Playground の int_member_status_current を読み解きます。
この章を終えると、こんなことができるようになります:
premium/free/former_premiumの定義をソース表付きで説明できるanon_user_keyが導入される位置づけを理解できる- current-state 分類の 歴史的限界(as-of 分析不可)を説明できる
- 転換 mart との接続(
signed_up_date,first_paid_date)を追える
current-state 分類とは何か
課金状態を表す列(例: member_status, customer_tier)には、大きく 2 種類の設計があります。
| 設計 | 意味 | 典型用途 |
|---|---|---|
| current-state | build 時点(または最新スナップショット)での状態 | ダッシュボードの現状セグメント、配信対象の絞り込み |
| as-of / point-in-time | 任意の過去日時点での状態 | コホート分析、イベント当時の tier 再分類 |
dbt の intermediate で current-state を切り出すとき、入力はだいたい次の 3 系統に分かれます。
同意・登録 … 誰を「会員」とみなすか(DS Playground では terms 同意)。
entitlement … 今、有効なアクセス権があるか。
purchase / billing … 過去に課金したことがあるか。
Auth テーブル(ログイン情報)を analytics で読まないのは、アクセス制御 で学んだ境界と同じです。 課金状態は ビジネス上の権利と決済 から定義し、認証基盤とは分けます。
current-state 分類"] M1["転換 mart"] M2["activity 分割 mart"] M3["engagement mart"] CON --> INT ENT --> INT PUR --> INT INT --> M1 INT --> M2 INT --> M3
intermediate の出力 grain は 1 行 = 1 会員(母集団に含まれるユーザー) です。
下流 mart はこの表を JOIN して member_status 列を付与するだけに留め、CASE 文をコピーしません。
3 値 + unknown 分類の decision tree
current-state の典型パターンは、entitlement を優先し、なければ購入歴で分岐し、それもなければ free とする 3 値分類 です。
entitlement あり?"} Q1 -->|Yes| PRE["premium"] Q1 -->|No| Q2{"過去に paid 相当の
premium_member 購入あり?"} Q2 -->|Yes| FP["former_premium"] Q2 -->|No| FR["free"] ACT["int_user_activity_events の user"] --> J["LEFT JOIN int_member_status_current"] J --> UNK["マッチなし → unknown
(status 分割 mart のみ)"]
SQL 上の CASE は概念として次のとおりです。
case
when coalesce(has_active_premium_entitlement, 0) = 1 then 'premium'
when paid_purchases.user_id is not null then 'former_premium'
else 'free'
end as member_status
| 状態 | 自然言語 | 典型ユースケース |
|---|---|---|
premium |
現在有効な Premium entitlement | 有料機能利用モニタリング |
former_premium |
今は無効だが課金歴あり | 復帰キャンペーン対象分析(内部) |
free |
課金歴なしの terms 会員 | 無料体験・転換ファネル |
unknown |
terms ベースにいない activity | 品質・境界の監視 |
unknown は intermediate 本体には出さず、activity 行に status を LEFT JOIN するときだけ現れます。
定義上マッチしなかった行を隠さず、利用者が前提を確認できるようにするためです(Mart 設計 の grain 原則)。
current-state の限界
current-state 分類の重要な caveat は、過去イベントを build 時点の tier で遡及しない ことです。
例えば、先月 free で学習し今月 premium 化したユーザーは、過去の activity 行を premium に再分類しません。 厳密な「イベント日 as-of Premium」が必要なら、entitlement history の intermediate や snapshot が別途必要です。
指標定義 で学んだ「スナップショット指標 vs フロー指標」の区別がここに当てはまります。
ダッシュボードで current-state の member_status を使うときは、「今の状態での分割」 であることを併記してください。
Monetization status(今どの tier か)と Monetization conversion(いつ signed up / first paid したか)は別 mart です。 混ぜないのは Mart 設計 の「1 mart = 1 business question」原則どおりです。
int_member_status_current
DS Playground では、中間モデル int_member_status_current が Monetization 次元の SSOT です。
terms 同意会員を母集団とし、entitlements と purchases から 3 値分類を行います。
入力ソース
| ソース | 役割 |
|---|---|
stg_supabase__consents |
terms 同意 — Member の入口(このモデルは terms 会員のみ) |
stg_supabase__entitlements |
premium_member の 現在有効 アクセス |
stg_supabase__premium_purchases |
課金履歴(初回 paid_at など) |
stg_supabase__user_learning_preferences |
primary_goal(セグメント用) |
Auth は読みません。アクセス制御 と同じ境界です。
premium 判定の詳細
entitlements 側では、product_code が premium_member で、status が active、かつ有効期間内の行を集約します。
max(case
when entitlement_status = 'active'
and starts_at <= current_timestamp
and (ends_at is null or ends_at > current_timestamp)
then 1
else 0
end) as has_active_premium_entitlement
current_timestamp で有効性を判定するため、これは build 時点の current-state です。
paid 購入側は次の条件で「一度も課金した」とみなします。
where product_code = 'premium_member'
and purchase_status in ('paid', 'partially_refunded')
and paid_at is not null
partially_refunded を paid-like に含めるのは 実務上の「一度も課金した」 判定です。定義変更時は YAML caveats を更新します。
出力列の読み方
| 列 | 意味 |
|---|---|
user_id |
内部 JOIN 用(engagement mart 以外では非公開方針) |
anon_user_key |
md5(user_id::text) — 疑似匿名キー(後章) |
signed_up_date |
terms 同意初日 → membership / conversion mart |
first_paid_date |
初回 paid 購入日 |
has_active_premium |
boolean |
has_ever_paid |
boolean |
lifetime_paid_amount_total |
累計課金額(内部分析) |
primary_goal |
学習目的(demand 分析との接点) |
下流 mart との関係
int_member_status_current を変更すると、Premium セグメントを使う すべての mart の数字が変わります。
だからこそ intermediate に CASE 文を集約し、mart 側は JOIN だけに留めます。
体系コラム:カリキュラム上の位置づけ
| 項目 | 内容 |
|---|---|
| Stage / 章 | Stage 4 — 第1章 |
| 今回の論点 | 3 値分類、entitlement vs purchase、current-state 限界 |
| 前章との接続 | Incremental mart |
| 次章への伏線 | Engagement mart |
まとめ
課金状態は 1 つの CASE 文より、ソースごとの責務(同意・権利・決済)を整理したうえで intermediate に組み立てます。
DS Playground の int_member_status_current は terms 同意会員を入口に、entitlement 優先 → 購入歴 → free の順で分類し、転換・activity 分割・engagement mart の共通次元になっています。
current-state である限界(as-of 不可)を理解したうえで使うことが、Monetization 分析の前提です。
関連教材
- 前章(Stage 3): Incremental mart
- 次章: Engagement mart
- 関連(DM): オントロジーと意味
確認問題
問題 1
current-state の customer_tier 列について 正しい 説明はどれですか。
A. 任意の過去日のイベントを、その日時点の tier で自動再分類する
B. build 時点の状態であり、厳密な as-of 分析には snapshot 等が必要
C. OLTP の role 列と常に一致する
D. 公開ダッシュボードに raw user_id 付きで出してよい
正解: B
問題 2
entitlement(権利)テーブルと購入履歴テーブルを 両方 intermediate で読む主な理由はどれですか。
A. SQL を短くするため
B. 現在有効な権利と、過去購入の有無を組み合わせてステータスを定義するため
C. seed を自動生成するため
D. incremental を有効にするため
正解: B
問題 3
mart の member_status enum に unknown を残す 主な理由 はどれですか。
A. データ品質が悪いから必ず除外する
B. 定義上マッチしなかった行を隠さず、利用者が前提を確認できるようにするため
C. BI ツールの制約のため
D. staging が view だから
正解: B
用語メモ(この章)
| 用語 | 意味(この章での使い方) |
|---|---|
| current-state | build 時点での最新状態。過去 as-of 不可 |
| entitlement | プロダクトへのアクセス権(有効期間付き) |
| former_premium | 今は無効だが課金歴がある状態 |
| intermediate | 課金状態定義の SSOT を置く層 |
unknown |
terms 母集団にいない activity 行の status |