✅ このSQLは【GA4アトリビューション分析】をするためのものです

つまり、

  • どのチャネルで最初に流入したか(ファーストタッチ)

  • どのチャネルで最終的にCVしたか(ラストタッチ)

  • 中間でどれくらい接触したか(中間タッチ回数) をすべてBigQuery単体で再現・可視化するために組まれています!
    →1以下の場合、ほとんどのユーザーが、ファースト→ラストの間に、追加でほぼ接触していない= 「ファーストタッチした後、すぐにCVしているユーザーが多い」


📖 SQLをパートごとに説明


① session_list:まず全セッションをリストアップする

ここではGA4のsession_startイベントだけを取り出し、

  • 参照元(source)

  • メディア(medium)

  • キャンペーン(campaign)

  • イベント発生時刻(event_timestamp) を引っ張ってきます。

さらに、ユーザー単位でイベント順に並べて
このセッションは何番目か?(session_order)」を付けています。

🔵 要するに
👉「ユーザーのセッション履歴を時系列順に整理する」ステップ!


② cv_sessions:コンバージョン(CV)だけを取り出す

GA4のデータから、

  • CVイベント(ここでは'GA4_6')

  • その発生セッション(ga_session_id)
    を抽出しています。

もし1ユーザーに複数CVがあった場合は、最初のCVだけを記録します。

🔵 要するに
👉「どのセッションがCVだったか特定する」ステップ!


③ session_with_cv_flag:セッションに「CVしたかどうか」のフラグを付ける

①で作ったセッションリストに、②で特定したCVセッションを結合して、

  • CVだったら is_cv_session = 1

  • そうじゃなければ is_cv_session = 0 というフラグを付けます。

🔵 要するに
👉「どのセッションでCVが起きたかを明示する」ステップ!


④ attribution_points:ファーストタッチとラストタッチを決定する

ここで重要!

ユーザー単位で

  • 最初(session_order=1)のsource/medium/campaign → ファーストタッチ

  • is_cv_session=1のsource/medium/campaign → ラストタッチ をそれぞれFIRST_VALUEで取得します。

さらに、

  • ファーストとラストの間に何回タッチがあったか(mid_touch_count)もカウントしています。

🔵 要するに
👉「ユーザーごとに、最初と最後と中間接触の情報をまとめる」ステップ!


⑤ 最終出力:ファースト・ラスト単位で集計して出力

最後に、

  • ファーストタッチ時のsource/medium/campaign

  • ラストタッチ時のsource/medium/campaign ごとに

  • CVユーザー数(user_count)

  • 中間タッチ平均回数(avg_mid_touch_points) を集計して並べています!

🔵 要するに
👉「ファースト・ラスト別の成果を一覧にする」ステップ!


🧠 まとめるとこのSQLは…

ステップ役割session_listユーザーのセッション履歴を時系列で並べるcv_sessionsCVしたセッションを特定するsession_with_cv_flagセッションにCVフラグを付けるattribution_pointsファーストタッチ・ラストタッチ・中間タッチ回数を定義する最終SELECTファーストとラスト別にCV件数・中間タッチ回数を集計する


✅ そしてこのSQLの強みは…

  • ファーストとラストでチャネルのズレ(例:広告で流入→自然検索でCV)も見える!

  • リードタイムが長い商材でも、中間タッチ回数を見て検討期間の長さを推測できる!

  • GA4レポートよりもカスタマイズ自由に見られる!

-- ==========================
-- 【GA4アトリビューション再現SQL(ファースト・中間・ラスト+参照元・キャンペーン版)】
-- テンプレート版
-- ==========================

-- ① セッションリストを作成
WITH session_list AS (
  SELECT
    e.user_pseudo_id,
    (SELECT ep.value.int_value FROM UNNEST(e.event_params) ep WHERE ep.key = 'ga_session_id') AS ga_session_id,
    e.traffic_source.source AS source,
    e.traffic_source.medium AS medium,
    e.traffic_source.name AS campaign,
    e.event_timestamp,
    ROW_NUMBER() OVER (PARTITION BY e.user_pseudo_id ORDER BY e.event_timestamp ASC) AS session_order
  FROM
    -- ★【入力1】対象テーブルを指定
    `idemitsu-447809.analytics_328712105.events_202504*` e
  WHERE
    e.event_name = 'session_start'
    AND _TABLE_SUFFIX BETWEEN '01' AND '30'  -- ★【入力2】対象期間を指定(例:4月なら01~30)
),

-- ② CVセッションを特定
cv_sessions AS (
  SELECT
    e.user_pseudo_id,
    (SELECT ep.value.int_value FROM UNNEST(e.event_params) ep WHERE ep.key = 'ga_session_id') AS ga_session_id,
    MIN(e.event_timestamp) AS cv_event_timestamp
  FROM
    -- ★【入力1】対象テーブルを指定(同じ)
    `idemitsu-447809.analytics_328712105.events_202504*` e
  WHERE
    -- ★【入力3】CVイベント名を指定
    e.event_name = 'GA4_6'
    AND _TABLE_SUFFIX BETWEEN '01' AND '30'  -- ★【入力2】対象期間を指定
  GROUP BY
    user_pseudo_id,
    ga_session_id
),

-- ③ セッションにCVフラグを付与
session_with_cv_flag AS (
  SELECT
    sl.*,
    CASE WHEN cv.ga_session_id IS NOT NULL THEN 1 ELSE 0 END AS is_cv_session
  FROM
    session_list sl
  LEFT JOIN
    cv_sessions cv
  ON
    sl.user_pseudo_id = cv.user_pseudo_id
    AND sl.ga_session_id = cv.ga_session_id
),

-- ④ ファーストタッチ・ラストタッチ定義
attribution_points AS (
  SELECT
    user_pseudo_id,

    -- ファーストタッチ(最初のセッション)
    FIRST_VALUE(source) OVER (PARTITION BY user_pseudo_id ORDER BY session_order ASC) AS first_source,
    FIRST_VALUE(medium) OVER (PARTITION BY user_pseudo_id ORDER BY session_order ASC) AS first_medium,
    FIRST_VALUE(campaign) OVER (PARTITION BY user_pseudo_id ORDER BY session_order ASC) AS first_campaign,

    -- ラストタッチ(CV時)
    FIRST_VALUE(source) OVER (PARTITION BY user_pseudo_id ORDER BY CASE WHEN is_cv_session = 1 THEN session_order ELSE NULL END ASC) AS last_source,
    FIRST_VALUE(medium) OVER (PARTITION BY user_pseudo_id ORDER BY CASE WHEN is_cv_session = 1 THEN session_order ELSE NULL END ASC) AS last_medium,
    FIRST_VALUE(campaign) OVER (PARTITION BY user_pseudo_id ORDER BY CASE WHEN is_cv_session = 1 THEN session_order ELSE NULL END ASC) AS last_campaign,

    -- 中間タッチ回数
    COUNTIF(is_cv_session = 0 AND session_order > 1) OVER (PARTITION BY user_pseudo_id) AS mid_touch_count

  FROM
    session_with_cv_flag
)

-- ⑤ ファースト・ラスト・キャンペーン別に集計
SELECT
  first_source,
  first_medium,
  first_campaign,
  last_source,
  last_medium,
  last_campaign,
  COUNT(DISTINCT user_pseudo_id) AS user_count,
  AVG(mid_touch_count) AS avg_mid_touch_points
FROM
  attribution_points
WHERE
  first_medium IS NOT NULL
  AND last_medium IS NOT NULL
GROUP BY
  first_source,
  first_medium,
  first_campaign,
  last_source,
  last_medium,
  last_campaign
ORDER BY
  user_count DESC
;
おすすめの記事