semi-structured データ:multi-select を mart に載せる
Stage 4 — 第3章 | dbt入門カリキュラム 推定学習時間:35〜45分 | 難易度:★★★☆☆
この章で学ぶこと
前章 では、Engagement mart で primary_goal をセグメント軸として使いました。
有料化前のプロダクトでは、「ユーザーは何を学びたいか」「Premium に何を期待するか」 を定性的に把握することが重要です。
Demand 分析には 2 種類のデータが混在します。
現在値(current-state) … ユーザー設定や preferences。1 ユーザー 1 値。
イベント(event) … アンケート回答。1 回答に複数選択肢(multi-select)があり得る。
これらを同じ grain で JOIN すると、分母がずれ、share の解釈が壊れます。
dbt では 問いごとに mart を分け、multi-select は UNNEST で行に広げてから集計するのが典型パターンです。
まず dbt の作法を整理し、続けて DS Playground の fct_learning_goal_share と fct_member_survey_option_share を読み解きます。
この章を終えると、こんなことができるようになります:
- current-state preferences と survey 回答の データモデル差 を説明できる
UNNEST(selected_options)パターンを読めるresponse_shareとselected_option_shareの分母の違いを説明できる- pre-monetization research exposure との接続を理解できる
Demand 分析
Demand ファミリーは 「何が欲しいか」 に答える mart 群です。 Membership / Activity / Engagement とは grain が異なります。
| 問いの型 | データ性質 | 典型 grain |
|---|---|---|
| 今の設定・嗜好は | current-state | 1 行 = 1 カテゴリ値(集計済み) |
| 調査への回答は | イベント(multi-select 可) | 1 行 = survey × 選択肢 |
| 行動から推測 | 別 mart(本章外) | — |
同じ「学習目的」という単語でも、Learning Goal(現在の preferences) と Survey Response(イベント) はエンティティが違います。 オントロジー で学んだ「同じ単語でもエンティティが違う」典型例です。
学習目的"] CURRENT["Current-state entity
現在の設定・嗜好"] EVENT["Survey Response entity
回答イベント"] PREF["user preferences"] G["goal share mart"] SUR["survey responses"] U["UNNEST
option share mart"] TERM -.->|"現在値として扱う"| CURRENT --> PREF --> G TERM -.->|"イベントとして扱う"| EVENT --> SUR --> U classDef term fill:#fff7ed,stroke:#ea580c,color:#1f2937 classDef current fill:#eff6ff,stroke:#2563eb,color:#1f2937 classDef event fill:#f0fdf4,stroke:#16a34a,color:#1f2937 class TERM term class CURRENT,PREF,G current class EVENT,SUR,U event
share 列を出すとき、分母を列名で固定 することが重要です。 率と KPI で学んだ「分母の固定」を、mart 列レベルで実装します。
multi-select の展開パターン
アンケートで複数選択可の場合、1 回答行に配列(例: selected_options)が載ります。
集計するには 1 選択肢 1 行 に広げる必要があります。
PostgreSQL では unnest(selected_options) を使います。
with responses as (
select
response_id,
user_id,
survey_key,
unnest(selected_options) as selected_option
from stg_survey_responses
),
option_counts as (
select
survey_key,
selected_option,
count(distinct response_id) as responses
from responses
group by 1, 2
)
1 回答で A, B を選択すると 2 行に広がります。 このとき share には 2 種類 定義できます。
| 列 | 分母 | 解釈 |
|---|---|---|
response_share |
その survey の 回答数(response_id) | 「回答の何 % がこの選択肢を含むか」 |
selected_option_share |
その survey の 選択肢総数 | multi-select 全体における選択肢の割合 |
分母が違えば数字の意味も違います。YAML で business question と列定義を固定してください。
current-state goal share の caveats
preferences から goal 構成比を出す mart は current-state のみ です。 過去に goal を変更した履歴はこの mart では見えません。履歴化が必要なら snapshot(Stage 5)が教学例です。
preferences 未設定ユーザーは staging の NULL 処理に依存し、分布 mart では unset と対応します。
survey を preferences と単純 JOIN しない理由も同じです。 grain が違う 別問い として扱います。
Survey mart ファミリー
DS Playground では Demand ファミリーに 2 モデルがあります。
fct_learning_goal_share— 現在のprimary_goal構成fct_member_survey_option_share— 調査ごとの選択肢シェア
fct_learning_goal_share
business question: 「会員の学習目的構成比はどうなっているか」
grain: 1 行 = 1 primary_goal 値
with goal_counts as (
select primary_goal, count(*) as users
from stg_supabase__user_learning_preferences
group by 1
)
select
primary_goal,
users,
safe_ratio(users, total_users) as user_share
from goal_counts;
fct_member_survey_option_share
business question: 「各 survey で、選択肢はどれだけ選ばれているか」
grain: 1 行 = survey_key × selected_option
DS Playground では unnest(selected_options) で展開し、singular test assert_member_survey_selected_options_non_empty が空配列を防ぎます。
exposure との接続
metabase_pre_monetization_research_dashboard depends_on:
fct_learning_goal_sharefct_member_survey_option_sharefct_problem_quality(次章)fct_problem_category_quality
Survey mart 単体では 教材品質 まで見ません。 exposure が 研究ダッシュボードの SSOT セット を定義します。
分析上の注意
| 落とし穴 | 対策 |
|---|---|
| goal の履歴変化を見たい | snapshot または将来の event 履歴 |
| survey を preferences と単純 JOIN | grain が違う — 別問いとして扱う |
| 選択肢 share を CVR と混同 | business question を YAML で固定 |
体系コラム:カリキュラム上の位置づけ
| 項目 | 内容 |
|---|---|
| Stage / 章 | Stage 4 — 第3章 |
| 今回の論点 | current goal vs multi-select、unnest、2 種 share |
| 前章との接続 | Engagement mart — primary_goal 軸 |
| 次章への伏線 | Content quality mart |
まとめ
Demand 分析は「アンケートを読む」ことではなく、定義済み grain の share 表 として SSOT 化することです。
DS Playground では current-state の goal share と、multi-select を unnest した survey share を分け、safe_ratio で分母を列名に固定しています。
関連教材
- 前章: Engagement mart
- 次章: Content quality mart
- 関連(DM): セマンティックレイヤー
確認問題
問題 1
カテゴリ別シェア mart(例: 1 行 = 1 カテゴリ値)の grain として正しいのはどれですか。
A. 1 行 = 1 ユーザー
B. 1 行 = 1 カテゴリ値(集計済み)
C. 1 行 = 1 アンケート回答
D. 1 行 = 1 日
正解: B
問題 2
multi-select アンケートで unnest(selected_options) を使う理由はどれですか。
A. PII を削除するため
B. 1 回答の複数選択肢を集計可能な行に広げるため
C. incremental build するため
D. OLTP と JOIN するため
正解: B
問題 3
選択肢シェア(option share)の分母として 一般的に正しい のはどれですか。
A. 全登録ユーザー数
B. そのアンケート回答における選択肢総数(multi-select 合計)
C. DAU
D. seed 行数
正解: B
用語メモ(この章)
| 用語 | 意味(この章での使い方) |
|---|---|
| current-state | 最新の preferences 値。履歴は見えない |
| multi-select | 1 回答で複数選択肢を選べる形式 |
unnest |
配列を行に広げる PostgreSQL 関数 |
response_share |
回答数を分母にした share |
selected_option_share |
選択肢総数を分母にした share |