ウェブログからSQLで指標を計算する8構文~GA4のBigQueryを題材に
概要
Googleアナリティクス4プロパティが登場し、誰でもBigQueryにログを出力できるようになった。ログ分析を始める環境は揃ったわけだが、ログ分析のノウハウはあまり世に出ていない。SQLを使ってこれらを分析する方法を少し紹介する。どんな高度なログ分析をするにしても、これが基本となる。
ウェブ分析の指標
ウェブ分析の基本は
- ページビュー数
- セッション数
- 人数
のカウントである。複雑な分析も、結局カウントしているのはこの3つの指標に集約されることが多い。Eコマースになると購入金額の合計なども入ってくることはある。
そしてこれに「○○した」という条件が付いて
- イベント○○が発生した回数
- ○○したページビュー数
- パラメータ△△の値が□□だったイベント○○が発生した回数(ページ□□のページビュー数)
- ○○したページビュー数
- ○○したセッション数
- ○○した人数
をひたすらカウントする。たとえば
- 資料ダウンロードボタンをクリックした回数
- 80%以上スクロールしたページビュー数
- 自然検索から流入したセッション数
- コンバージョン完了ページを見た人数
である。これらもページビュー数/セッション数/PV数のカウントであるのは同じで、「○○した」条件に絞り込んでカウントしているだけである。
そして絶対数だけでなく率の指標
- ○○したセッション率(セッション単位CVRなど)
- ○○した人数率(ユーザ単位CVRなど)
を計算して歩留まりを見る。これにさまざまなディメンションをかけて比較するだけで、見ている指標はほとんどこれだけなのである。
指標をBigQueryからSQLで計算する
無条件の単純なページビュー数/セッション数/人数のカウント
GA4に限らずログ分析の原則として、1行1ページビューになっていれば ページビュー数は行数のカウント
セッション数はユニークなセッションIDの数のカウント
ユーザ数はユニークなユーザIDの数のカウント
となる。
GA4の形式の場合、ページビュー数(ページビューイベントが発生した回数)
SELECT
COUNT(
CASE
WHEN event_name = 'page_view' THEN user_pseudo_id
ELSE NULL
END
) AS n_pageviews
FROM
`prj.analytics_999999999.events_*`;
event_name = 'page_view'である行数をカウントする。
総人数
ユーザを特定する列user_pseudo_id(明示的にSDK内でユーザーIDを設定している場合はuser_idを使う。いずれも文字列型)があるので、これをユニークカウントする。
総セッション数
SELECT
COUNT(DISTINCT
CONCAT(
user_pseudo_id,
'-',
CAST(
(SELECT ep.value.int_value FROM UNNEST(event_params) ep WHERE ep.key = 'ga_session_id') AS string
)
)
) AS n_sessions
FROM
`prj.analytics_999999999.events_*`;
GA4の仕様で、単体でユニークなセッションを特定するIDを指す列はない。ユーザごとのセッションを特定する(あくまでユーザ内なのでグローバルにセッションを特定できるわけではない)変数がページビューイベントのパラメータga_session_id(整数型)になる。
つまり**user_pseudo_id(またはuser_id)とga_session_idを組み合わせることで全体でユニークにセッションを特定**するIDになる。CONCAT()の区切り文字はuser_pseudo_idとga_session_idに現れない文字であれば何でも可。文字列しか対象にできないので、数値を文字列に変換しておく。
イベントパラメータの抽出方法
ここでイベントパラメータの値を取得したが、これの取得方法を説明する。 イベントパラメータはネストされており、列名だけ指定しても使えない。

これがBigQuery+Googleアナリティクスの厄介な形式なのだが、イベントパラメータの値を取得するには以下のイディオムで覚えておけばいい。
整数型のパラメータ値を取得する
SELECT
(SELECT ep.value.int_value FROM UNNEST(event_params) ep WHERE ep.key = 'ga_session_id')
FROM
`prj.analytics_999999999.events_*`;
文字列型のパラメータ値を取得する
SELECT
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'source')
FROM
`prj.analytics_999999999.events_*`;
目的のイベントパラメータ名「●●」に対して、
を一つの列と見立てるのである。
条件付きページビュー数/セッション数/人数のカウント
実際にはサイト全体のページビュー数や訪問者数をカウントするだけでなく、○○したPV数、セッション数、人数を求めるのが普通である。一般的なログ分析では「○○した」というのはCASE句を使って指定する。
条件に当てはまるページビュー数は
条件に当てはまるセッション数は
条件に当てはまるユーザ数は
となる。
GA4の場合、たとえば90%スクロールした数は
SELECT
COUNT(
CASE
WHEN
event_name = 'scroll'
AND (SELECT ep.value.int_value FROM UNNEST(event_params) ep WHERE ep.key = 'percent_scrolled') = 90
THEN user_pseudo_id
ELSE NULL END
) AS n_scrolls_90_percent
FROM
`prj.analytics_999999999.events_*`;
event_name = 'scroll'でpercent_scrolled = 90の行数をカウントする。
つまり「パラメータ△△の値が□□だったイベント○○が発生した回数」に一般化できる。
「ページ□□のページビュー数」も同じで、「https://example.com/thankyou/」のページビュー数
SELECT
COUNT(
CASE
WHEN
event_name = 'page_view'
AND (SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_location') = 'https://example.com/thankyou/'
THEN user_pseudo_id
ELSE NULL END
) AS n_pageviews_cv
FROM
`prj.analytics_999999999.events_*`;
event_name = 'page_view'かつpage_locationが「https://example.com/thankyou/」が条件になる。
ページビュー関連のパラメータには
- URL
page_location - ページタイトル
page_title - 参照元
page_referrer
などがある。
「https://example.com/thankyou/」を見たセッション数 特定のページ(購入完了など)を見たセッション数ということで、コンバージョンのカウントになる。
SELECT
COUNT(DISTINCT
CASE
WHEN
event_name = 'page_view'
AND (SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_location') = 'https://example.com/thankyou/'
THEN CONCAT(user_pseudo_id, '-', CAST((SELECT ep.value.int_value FROM UNNEST(event_params) ep WHERE ep.key = 'ga_session_id') AS string))
ELSE NULL END
) AS n_sessions_cv
FROM
`prj.analytics_999999999.events_*`;
条件の部分は上と同じだが、THENの後つまりカウントする対象が
CONCAT(user_pseudo_id, '-', CAST((SELECT ep.value.int_value FROM UNNEST(event_params) ep WHERE ep.key = 'ga_session_id') AS string))
つまり無条件の時に出てきたグローバルにセッションを特定するIDになっている。
「https://example.com/thankyou/」を見た人数 コンバージョン人数になる
SELECT
COUNT(DISTINCT
CASE
WHEN
event_name = 'page_view'
AND (SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_location') = 'https://example.com/thankyou/'
THEN user_pseudo_id
ELSE NULL END
) AS n_visitors_cv
FROM
`prj.analytics_999999999.events_*`;
○○した率を計算
コンバージョン数だけでなくコンバージョン率も見る。絶対数だけでなく率も見るのが普通である。
- 分子には条件に当てはまる○○
- 分母には総○○
なので、条件に当てはまるページビュー率は
条件に当てはまるセッション率は
条件に当てはまるユーザ率は
となる。
GA4では○○した率(CVR)
SELECT
COUNT(DISTINCT
CASE
WHEN
event_name = 'page_view'
AND (SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_location') = 'https://example.com/thankyou/'
THEN CONCAT(user_pseudo_id, '-', CAST((SELECT ep.value.int_value FROM UNNEST(event_params) ep WHERE ep.key = 'ga_session_id') AS string))
ELSE NULL END
) / COUNT(DISTINCT CONCAT(user_pseudo_id, '-', CAST((select ep.value.int_value from unnest(event_params) ep where ep.key = 'ga_session_id') AS string)))
AS r_sessions_cv
FROM
`prj.analytics_999999999.events_*`;
複雑に見えるが、先に計算した「○○したセッション数」を「総セッション数」で割っただけである。
○○した人率(ユーザ単位CVR)
SELECT
COUNT(DISTINCT
CASE
WHEN
event_name = 'page_view'
AND (SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_location') = 'https://example.com/thankyou/'
THEN user_pseudo_id
ELSE NULL END
) / COUNT(DISTINCT user_pseudo_id)
AS r_visitors_cv
FROM
`prj.analytics_999999999.events_*`;
まとめ:ログ分析で使う指標のSQL8構文
- 総PV数
- 総セッション数
- 総人数
- ○○したPV数
- ○○したセッション数
- ○○した人数
- ○○したセッション率
- ○○した人率
SELECT
-- 総PV数
COUNT(CASE WHEN event_name = 'page_view' THEN user_pseudo_id ELSE NULL END) n_pageviews,
-- 総セッション数
COUNT(DISTINCT CONCAT(user_pseudo_id, '-', CAST((select ep.value.int_value from unnest(event_params) ep where ep.key = 'ga_session_id') AS string))) n_sessions,
-- 総人数
COUNT(DISTINCT user_pseudo_id) n_visitors,
-- ○○したPV数
COUNT(CASE WHEN event_name = 'page_view' AND (select ep.value.string_value from unnest(event_params) ep where ep.key = 'page_location') = 'https://example.com/thankyou/' THEN user_pseudo_id ELSE NULL END) n_pageviews_cv,
-- ○○したセッション数
COUNT(DISTINCT CASE WHEN event_name = 'page_view' AND (select ep.value.string_value from unnest(event_params) ep where ep.key = 'page_location') = 'https://example.com/thankyou/' THEN CONCAT(user_pseudo_id, '-', CAST((select ep.value.int_value from unnest(event_params) ep where ep.key = 'ga_session_id') AS string)) ELSE NULL END) n_sessions_cv,
-- ○○した人数
COUNT(DISTINCT CASE WHEN event_name = 'page_view' AND (select ep.value.string_value from unnest(event_params) ep where ep.key = 'page_location') = 'https://example.com/thankyou/' THEN user_pseudo_id ELSE NULL END) n_visitors_cv,
-- ○○したセッション率
COUNT(DISTINCT CASE WHEN event_name = 'page_view' AND (select ep.value.string_value from unnest(event_params) ep where ep.key = 'page_location') = 'https://example.com/thankyou/' THEN CONCAT(user_pseudo_id, '-', CAST((select ep.value.int_value from unnest(event_params) ep where ep.key = 'ga_session_id') AS string)) ELSE NULL END) / COUNT(DISTINCT CONCAT(user_pseudo_id, '-', CAST((select ep.value.int_value from unnest(event_params) ep where ep.key = 'ga_session_id') AS string))) r_sessions_cv,
-- ○○した人率
COUNT(DISTINCT CASE WHEN event_name = 'page_view' AND (select ep.value.string_value from unnest(event_params) ep where ep.key = 'page_location') = 'https://example.com/thankyou/' THEN user_pseudo_id ELSE NULL END) / COUNT(DISTINCT user_pseudo_id) r_visitors_cv
FROM
`prj.analytics_999999999.events_*`;
これは公式として覚えておくといい。
なおGA4のデータをBigQueryに出力する方法はこちらの記事を参照。 /ga-app-web-property-to-bigquery/
また出力されたデータの形式、仕様はこちらの記事を参照。 /ga4-bigquery-export-spec/