アトリビューション分析 — FIRST_VALUE / LAST_VALUE でファースト&ラストタッチを同時取得する
FIRST_VALUE / LAST_VALUE はパーティション内の最初・最後の値を返すウィンドウ関数です。ウィンドウフレーム(ROWS BETWEEN …)を明示しないと意図しない値が返るため注意が必要です。
FIRST_VALUE(channel) OVER ( PARTITION BY user_id ORDER BY touched_at ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING -- パーティション全体を窓に ) AS first_channel
RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW です。この既定では LAST_VALUE が現在行の値を返してしまい、最終行の値になりません。必ず ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING を明示してください。touch_events テーブルから、各ユーザーのファーストタッチチャネル・ラストタッチチャネル・総接触回数・両者が一致しているかを取得してください。取得列は user_id, first_channel, last_channel, total_touches, is_same_channel、user_id 昇順で返してください。
| user_id | channel | touched_at |
|---|---|---|
| 1 | 2024-01-05 | |
| 1 | 2024-01-10 | |
| 1 | direct | 2024-01-15 |
| 2 | 2024-02-01 | |
| 2 | 2024-02-08 | |
| 3 | 2024-02-10 | |
| 3 | 2024-02-12 | |
| 3 | 2024-02-20 |
| user_id | first_channel | last_channel | total_touches | is_same_channel |
|---|---|---|---|---|
| 1 | direct | 3 | FALSE | |
| 2 | 2 | TRUE | ||
| 3 | 3 | TRUE |
セッショナイゼーション — LAG + 累積SUM OVER で30分間隔ごとにセッションIDを採番する
セッショナイゼーションはWeb解析の最重要パターンの一つです。一定時間(典型的には30分)を超えて空いたアクセスを別セッションとして分割します。実装にはLAGで前行との時間差を測り、しきい値超過にフラグを立て、その累積和でセッションIDを採番します。
-- セッショナイゼーションの中核ロジック:累積SUMで連番化
new_session_flag cumulative SUM (= session_id)
1 ← 最初の行 → 1
0 ← 30分以内 → 1
0 → 1
1 ← 30分超 = 新セッション → 2
0 → 2
1 → 3
EXTRACT(EPOCH FROM (t2 - t1)) / 60 で「t1からt2までの分数」を取得できます。page_views テーブルから、各ユーザーの全ページビューに対し30分を超えて空いたら新セッションとしてセッションIDを採番してください。取得列は user_id, viewed_at, gap_minutes, session_id、user_id・viewed_at の昇順で返してください。
| user_id | viewed_at |
|---|---|
| 1 | 2024-01-05 10:00 |
| 1 | 2024-01-05 10:15 |
| 1 | 2024-01-05 10:25 |
| 1 | 2024-01-05 11:30 |
| 1 | 2024-01-05 11:45 |
| 2 | 2024-01-05 14:00 |
| 2 | 2024-01-05 15:00 |
| 2 | 2024-01-05 15:10 |
| user_id | viewed_at | gap_minutes | session_id |
|---|---|---|---|
| 1 | 2024-01-05 10:00 | NULL | 1 |
| 1 | 2024-01-05 10:15 | 15 | 1 |
| 1 | 2024-01-05 10:25 | 10 | 1 |
| 1 | 2024-01-05 11:30 | 65 | 2 |
| 1 | 2024-01-05 11:45 | 15 | 2 |
| 2 | 2024-01-05 14:00 | NULL | 1 |
| 2 | 2024-01-05 15:00 | 60 | 2 |
| 2 | 2024-01-05 15:10 | 10 | 2 |
最長連続購入ストリーク — GAP-AND-ISLAND + ランキング + 継続中判定で実務レベルのKPI算出
基礎編で学んだ GAP-AND-ISLAND をさらに発展させ、各ユーザーの最長連続購入期間とそのストリークが現在も継続中かを1クエリで算出します。GAP-AND-ISLAND で島(連続期間)を検出した後、ユーザーごとに最長島をランク付けし、最終購入日との一致を確認します。
-- 4段のCTEで進める設計
1) numbered : ROW_NUMBER で連番付与
2) grouped : date - (rn-1) で island キー生成
3) streaks : MIN/MAX/COUNT で各 island を集約
4) ranked : ROW_NUMBER で最長島を1位に、MAX(end) OVER で最新購入日を取得
5) 最終SELECT: rnk=1 行を抽出、is_active 判定
ORDER BY streak_days DESC, streak_end DESC)。実務上「最近の最長記録」のほうが施策判断に有用なためです。daily_orders テーブルから、各ユーザーの最長連続購入ストリーク(開始日・終了日・日数)と、そのストリークが現在も継続中かを算出してください。「継続中」とは「最長ストリークの終了日 = そのユーザーの最新購入日」であることを意味します。取得列は user_id, max_streak_days, streak_start, streak_end, is_active、user_id 昇順で返してください。
| user_id | order_date |
|---|---|
| 1 | 2024-01-01 |
| 1 | 2024-01-02 |
| 1 | 2024-01-03 |
| 1 | 2024-01-05 |
| 1 | 2024-01-06 |
| 2 | 2024-01-10 |
| 2 | 2024-01-11 |
| 2 | 2024-01-12 |
| 2 | 2024-01-13 |
| 2 | 2024-01-14 |
| 3 | 2024-01-20 |
| 3 | 2024-01-21 |
| 3 | 2024-01-25 |
| user_id | max_streak_days | streak_start | streak_end | is_active |
|---|---|---|---|---|
| 1 | 3 | 2024-01-01 | 2024-01-03 | FALSE |
| 2 | 5 | 2024-01-10 | 2024-01-14 | TRUE |
| 3 | 2 | 2024-01-20 | 2024-01-21 | FALSE |
招待ツリー階層分析 — WITH RECURSIVE で多段の招待関係を辿り招待深度と人数を集計する
基礎編では WITH RECURSIVE を「日付シーケンス生成」に使いました。応用編では本来の目的である階層構造(ツリー)の探索に挑みます。招待関係や組織図、コメントスレッド、ファイルシステムなど自己参照テーブルを辿る場面の定番テクニックです。
-- 階層再帰の構造(招待ツリーを上から辿る) WITH RECURSIVE tree AS ( -- アンカー:ルート(招待者なしのユーザー)から開始 SELECT user_id, 0 AS depth FROM users WHERE invited_by IS NULL UNION ALL -- 再帰:前回の結果と自己参照JOIN、depth を +1 SELECT u.user_id, t.depth + 1 FROM tree t JOIN users u ON u.invited_by = t.user_id )
FROM tree と書くと、前回の再帰イテレーションで追加された行のみを参照します(累積結果全体ではない点に注意)。これにより JOIN が「次の階層」だけを生成する設計になっています。WHERE NOT visited やパス配列追跡でサイクル検出するのが安全です。users テーブル(自己参照:invited_by 列がそのユーザーを招待した人を指す)から、各ユーザーの招待深度(depth:ルートから何段下か)と、そのユーザーが直接招待した人数を取得してください。取得列は user_id, name, depth, direct_invitees、depth・user_id 昇順で返してください。
| user_id | name | invited_by |
|---|---|---|
| 1 | Alice | NULL |
| 2 | Bob | 1 |
| 3 | Carol | 1 |
| 4 | Dave | 2 |
| 5 | Eve | 2 |
| 6 | Frank | 4 |
| 7 | Grace | 3 |
| user_id | name | depth | direct_invitees |
|---|---|---|---|
| 1 | Alice | 0 | 2 |
| 2 | Bob | 1 | 2 |
| 3 | Carol | 1 | 1 |
| 4 | Dave | 2 | 1 |
| 5 | Eve | 2 | 0 |
| 7 | Grace | 2 | 0 |
| 6 | Frank | 3 | 0 |
2軸RFMマトリクス — Recency × Monetary の3×3グリッドで実践的セグメントを構築する
基礎編では NTILE(4) で Monetary(購入額)の1軸セグメントを学びました。応用編では実務で本当に使われる R × M(Recency × Monetary)の2軸マトリクスに挑戦します。各軸で NTILE(3) を適用し、3×3=9 セルそれぞれにビジネス意味のあるラベルを付与します。
SELECT id_col, NTILE(3) OVER (ORDER BY col_a DESC) AS tile_a, -- 降順で3分割(大きい値が 1) NTILE(3) OVER (ORDER BY col_b) AS tile_b -- 昇順で3分割(小さい値が 1) FROM table_name;
ORDER BY last_purchase_date DESC で降順NTILE を適用し、得られたランクを 4 - r_rank で反転します。日付昇順だと古いユーザーが3になり意味が逆転するため要注意。customer_purchases テーブルから、各ユーザーの最終購入日(Recency)と合計購入額(Monetary)に NTILE(3) を適用し、3×3=9セルのマトリクスに分類してセグメントラベルを付与してください。取得列は user_id, last_purchase_date, total_amount, r_score, m_score, segment、r_score・m_score の降順で返してください。
| user_id | purchase_date | amount |
|---|---|---|
| 1 | 2024-03-25 | 15000 |
| 2 | 2024-03-20 | 8000 |
| 3 | 2024-03-28 | 2000 |
| 3 | 2024-03-30 | 2500 |
| 4 | 2024-02-15 | 12000 |
| 5 | 2024-02-10 | 5000 |
| 5 | 2024-02-25 | 2000 |
| 6 | 2024-02-20 | 3000 |
| 7 | 2024-01-05 | 20000 |
| 7 | 2024-01-20 | 10000 |
| 8 | 2024-01-10 | 5000 |
| 9 | 2024-01-15 | 1500 |
| user_id | last_purchase_date | total_amount | r_score | m_score | segment |
|---|---|---|---|---|---|
| 1 | 2024-03-25 | 15000 | 3 | 3 | VIP |
| 2 | 2024-03-20 | 8000 | 3 | 2 | 優良見込 |
| 3 | 2024-03-30 | 4500 | 3 | 1 | 新規/育成 |
| 4 | 2024-02-15 | 12000 | 2 | 3 | 離脱注意 |
| 5 | 2024-02-25 | 7000 | 2 | 2 | 標準 |
| 6 | 2024-02-20 | 3000 | 2 | 1 | 一般 |
| 7 | 2024-01-20 | 30000 | 1 | 3 | 失注リスク |
| 8 | 2024-01-10 | 5000 | 1 | 2 | 休眠 |
| 9 | 2024-01-15 | 1500 | 1 | 1 | 休眠 |