SQL イベントモデリング — アトリビューション分析の応用

応用イベントモデリングセッション化アトリビューションWindow関数 / 再帰CTEGaps and IslandsPostgreSQL対応全5問
1 / 5 · 学習中 0 / 5 問完了
QUESTION 6

セッション化 — LAG で前イベント時刻を取得し、30分超ギャップで条件付き累積SUM によりセッションIDを採番

LAGSUM OVERセッション化Window関数
前提知識

イベントモデリングで最頻出の前処理がセッション化(Sessionization)です。同一ユーザーの連続するイベントを、一定の非アクティブ時間(例: 30分)で区切ってセッションに束ねます。Window関数 LAG で直前イベント時刻を取得し、ギャップ判定の0/1フラグを累積SUM することでセッションIDが採番できます。

-- LAG(): 同一PARTITION内で1行前の値を取得
LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time)

-- 累積SUM でセッションIDを採番(境界フラグの累積 = セッション番号)
SUM(is_new_session) OVER (PARTITION BY user_id ORDER BY event_time)
セッション化の3段階パターン:(1) LAG で前時刻を取得 → (2) ギャップ判定で 0/1 フラグ化 → (3) 累積SUM でセッションIDに変換。この3段CTE は実務イベント分析のもっとも基本的なワークホースであり、後段のセッション長・ページ遷移分析・直帰率算出すべての出発点になります。
問題

user_events テーブルから、同一ユーザーの直前イベントから30分超のギャップがあれば新しいセッションとして区切り、ユーザーごとにセッション数・総イベント数・1セッションあたり平均イベント数を集計してください。出力列は user_id, session_count, total_events, avg_events_per_session(小数第1位)、user_id 昇順で返してください。

使用テーブル
► user_events(9行)
user_idevent_time
12024-01-10 10:00
12024-01-10 10:05
12024-01-10 10:20
12024-01-10 12:00
12024-01-10 12:10
22024-01-10 09:00
22024-01-10 09:15
22024-01-10 11:30
32024-01-11 14:00
期待出力
user_idsession_counttotal_eventsavg_events_per_session
1252.5
2231.5
3111.0
QUESTION 7

アトリビューション — FIRST_VALUE + LAG で初回流入チャネルと購入直前タッチを抽出する

FIRST_VALUELAGアトリビューションマーケ分析
前提知識

マーケティング分析の中核がアトリビューション(Attribution)です。あるコンバージョン(購入)が「どのチャネルの貢献によるものか」を割り当てる分析で、「初回タッチ」「ラストタッチ」「マルチタッチ」などのモデルがあります。Window関数で各ユーザーの行動履歴に「いつでも参照できる初回値」「直前の値」をぶら下げるのが基本テクニックです。

-- FIRST_VALUE: PARTITION 内の先頭行の値を全行に伝搬
FIRST_VALUE(channel) OVER (PARTITION BY user_id ORDER BY event_time)

-- LAG: 直前1行の値を取得(購入直前タッチに利用)
LAG(channel) OVER (PARTITION BY user_id ORDER BY event_time)
FIRST_VALUE のフレーム既定値に注意:PostgreSQL の Window関数で ORDER BY を指定すると、フレームの既定は RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW です。FIRST_VALUE は「フレームの先頭」の値を返すため、ORDER BY 指定があればパーティションの先頭値を全行に伝搬する挙動になり、初回タッチ抽出にそのまま使えます。
問題

user_events テーブルから、各購入(purchase)イベントに対して「そのユーザーの初回タッチチャネル(first_touch)」と「購入直前のチャネル(last_touch)」を抽出してください。出力列は user_id, first_touch, last_touch, purchase_time、user_id 昇順で返してください。購入のないユーザーは出力に含めません。

使用テーブル
► user_events(9行)
user_idevent_timeevent_typechannel
12024-01-10 10:00viewgoogle_ads
12024-01-10 11:00vieworganic
12024-01-10 12:00clickfacebook_ads
12024-01-10 13:00purchasedirect
22024-01-11 09:00vieworganic
22024-01-11 10:00purchaseemail
32024-01-11 11:00viewgoogle_ads
32024-01-11 12:00clickgoogle_ads
42024-01-12 09:00viewtwitter_ads
期待出力
user_idfirst_touchlast_touchpurchase_time
1google_adsfacebook_ads2024-01-10 13:00
2organicorganic2024-01-11 10:00
QUESTION 8

ファネルステップ間時間 — LAG + EXTRACT(EPOCH) で平均コンバージョン所要時間を計算

LAGEXTRACT EPOCHファネル分析所要時間
前提知識

基礎編で「ファネル到達数」「順序検証」を扱いましたが、実務では「ステップ間の所要時間」もコンバージョンの質を測る重要指標です。view → click の平均時間が長すぎる場合は決断材料の不足、click → purchase が長い場合は決済フローのフリクションが疑われます。

-- 直前イベントとの時間差を秒単位で取得
EXTRACT(EPOCH FROM (event_time - prev_event_time))

-- 60で割って分単位、ROUND で小数第1位
ROUND(AVG(...) / 60, 1)
EXTRACT(EPOCH FROM interval) — interval を秒に変換:timestamp - timestamp は interval 型を返し、EPOCH を抽出すると秒単位の数値になります。MySQL の TIMESTAMPDIFF(SECOND, t1, t2)、BigQuery の TIMESTAMP_DIFF(t2, t1, SECOND) と同じ役割を果たします。秒からの単位変換は /60 → 分、/3600 → 時間 です。
問題

user_events テーブルから、各ユーザーの連続イベント(view → click、click → purchase)の所要時間を計算し、遷移種別ごとの件数と平均分数を出力してください。出力列は transition, event_count, avg_minutes(小数第1位)、view → click、click → purchase の順で返してください。

使用テーブル
► user_events(8行)
user_idevent_typeevent_time
1view2024-01-10 10:00
1click2024-01-10 10:10
1purchase2024-01-10 11:00
2view2024-01-11 09:00
2click2024-01-11 09:30
3view2024-01-11 14:00
3click2024-01-11 14:05
3purchase2024-01-11 14:35
期待出力
transitionevent_countavg_minutes
view -> click315.0
click -> purchase240.0
QUESTION 9

再帰CTE — 日付ディメンションを生成し LEFT JOIN で DAU 時系列のゼロ埋めを行う

WITH RECURSIVELEFT JOIN時系列分析再帰CTE
前提知識

DAU(Daily Active Users)の時系列を可視化するときの最大の落とし穴がイベントのない日が結果から消えてしまう問題です。素直に GROUP BY date するとアクティビティがゼロの日は存在しない行になり、グラフが連続せず誤った印象を与えます。これを防ぐのが再帰CTE による日付ディメンションの生成です。

WITH RECURSIVE date_series(d) AS (
  SELECT MIN(event_time::date) FROM user_events    -- 起点(アンカー)
  UNION ALL
  SELECT d + 1                                  -- 再帰: 累積結果の各行から+1日した行を生成
  FROM   date_series
  WHERE  d < (SELECT MAX(event_time::date) FROM user_events)
)
再帰CTE は「アンカー」「再帰部」「終了条件」の3点セット:(1) アンカー = 最初に作られる行、(2) 再帰部 = 累積結果に毎回追加される行の生成ロジック、(3) 終了条件 = WHERE 句に書く再帰停止条件。再帰部はそれまでの累積結果(自分自身)を参照でき、終了条件が偽になるまで「次の行」を生成し続けます。今回は「最後のイベント日に到達したら停止」が終了条件です。
問題

user_events テーブルから、最初のイベント日から最後のイベント日まで全ての日付を出力し、各日付の DAU(アクティブユーザー数)を計算してください。イベントのない日は DAU=0 として出力します。出力列は event_date, dau、event_date 昇順で返してください。

使用テーブル
► user_events(5行)
user_idevent_time
12024-01-10
22024-01-10
12024-01-12
32024-01-12
12024-01-14
期待出力
event_datedau
2024-01-102
2024-01-110
2024-01-122
2024-01-130
2024-01-141
QUESTION 10

連続ログイン日数 — Gaps and Islands パターンで日付 − ROW_NUMBER 差分でグループ化し最長ストリークを算出

ROW_NUMBERGaps & Islandsストリーク分析継続率
前提知識

「連続ログイン日数」「連続購入日数」のように連続した塊(ストリーク)を検出する問題は、Gaps and Islands という古典的なパターンで解きます。各行に ROW_NUMBER() を採番し、日付 − 行番号 という不思議な差分を計算すると、連続した日付の塊では同じ値、ギャップが入ると別の値になるという性質を利用します。

-- 日付 - row_number(日) で「島」を識別する魔法のキー
login_date - ROW_NUMBER() OVER (
  PARTITION BY user_id ORDER BY login_date
) * INTERVAL '1 day' AS streak_group
なぜ 日付 - row_number で島が見えるのか:連続日付では「日付」も「row_number」も1ずつ増えるため、引き算した値は変化しません(島の中で一定)。ギャップが入ると日付だけ飛び、row_number は飛ばないため差分が増えます。同じ差分値を持つ行が同じ島 = 連続塊として識別できます。
問題

user_logins テーブルから、各ユーザーの最長連続ログイン日数(longest_streak)を計算してください。出力列は user_id, longest_streak、user_id 昇順で返してください。

使用テーブル
► user_logins(10行)
user_idlogin_date
12024-01-01
12024-01-02
12024-01-03
12024-01-05
12024-01-06
22024-01-10
32024-01-15
32024-01-16
32024-01-17
32024-01-18
期待出力
user_idlongest_streak
13
21
34