GA4のBigQueryエクスポートの仕様とデータの扱い方を説明する。GA4のログはネストされたレコードなど特殊な形式になっており、そのままでは扱いにくいのでいくつかテクニックがある。
目次
BigQueryへのエクスポート設定
GA4の管理画面で設定するが、手順はこちらの記事を参照。
テーブルの場所
- プロジェクト:GA4のBigQueryエクスポート設定で指定したプロジェクト
- データセット「
analytics_999999999
」(「999999999
」の部分はプロパティID) - テーブル名
- 前日までのデータ(日付別に)「
events_20210101
」- 「
20210101
」の部分は日付 - GA4のBigQueryエクスポート設定画面の「エクスポートタイプ」で「毎日」のチェックを入れている場合に出力される
- 「
- 今日のデータ、前日未集計分のデータ「
events_intraday_20210102
」- 「
20210102
」の部分は日付 - エクスポートタイプ設定で「ストリーミング」のチェックを入れている場合に出力される
- 「ストリーミング」と「毎日」を両方オンにしている場合は毎日のテーブルが生成されるとこのテーブルが削除される
- 「
- 前日までのデータ(日付別に)「
出力時刻は午前7-9時ごろ。レポートのタイムゾーンに依存するため、米国のままにしていると夜11時ごろに出力される。GA4ではなくFirebase Analyticsの場合はUTC。タイムゾーン設定がない。
日付でシャーディングされたテーブル
GA4のBigQueryエクスポートでは日単位でテーブルが分かれている。
- 前日まで:events_20210101, events_20210102, …, , events_20210127
- 当日:events_intraday_20210128
このように一つの内容(エンティティ)を表すテーブルを実体としては異なる複数のテーブルに分けて分散格納することをシャーディング(sharding)という。BigQueryでは共通の接頭辞で、末尾が_YYYYMMDD
形式の日付で異なる名前のシャーディングテーブルはウェブ画面上では一つのテーブルであるかのようにまとめて表示される。
シャーディングされたテーブル群ではクエリをかける際、日単位で対象のテーブルを絞り込めばメモリの節約ができる
またクエリでも以下のようにfrom句でテーブル名を指定する際にワイルドカード*
を付けると、そのワイルドカード条件にマッチするテーブル名、つまり前方一致でマッチするテーブル名のすべてを対象にできる。
select
...
from
`prj.analytics_999999999.events_*`
そしてwhere句で_table_suffix
を使ってワイルドカードの部分の文字列を指定することができる。
select
...
from
`prj.analytics_999999999.events_*`
where
_table_suffix between '20210101' and '20210110';
上はテーブル名末尾の日付文字列部分が20210101
から20210110
の間(2021年1月1日から2021年1月10日までのログ)を対象にしたクエリだが、結果は以下と同じ。
select ... from `prj.analytics_999999999.events_20210101`
union all
select ... from `prj.analytics_999999999.events_20210102`
union all
:
select ... from `prj.analytics_999999999.events_20210110`
_table_suffix
はアスタリスクの部分にマッチする文字列を表す。つまり下の2つのfromは同じものを意味する。
from `prj.analytics_999999999.events_*` where _TABLE_SUFFIX = '20210101'
from `prj.analytics_999999999.events_20210101`
_table_suffix
を指定してクエリ対象のテーブルを絞り込むことによって、処理するデータ量つまりクエリコストの節約ができる。単純にfrom
でワイルドカードを指定したまま_table_suffix
を使った絞り込みを行わないと全期間のテーブルが対象がクエリの対象になり、そのデータサイズに対して課金される。クエリを実行する際は必ず_table_suffix
で期間を絞り込もう。一方でパーティショニングも同様にデータを分けて格納しているのですが、テーブルの実体としては一つである。シャーディングもパーティショニングもクエリを実行する際、日付単位で対象のテーブルを絞り込めばメモリの節約ができる。
なおシャーディングテーブルは基本的に同じ列構成になるのだが、GA4の場合、同じシャーディングテーブル群であってもある日を境に列が追加されることがある。その場合select *
をすると列が一致しないというエラーが発生するので、列名もワイルドカードではなく個別に指定しなければならない。
主な項目
GA4のログは1行1イベントを表す履歴データである。1行の中にネストされたデータ構造を持つ列もある。
- ユーザー
- ユーザー識別子
- ユーザープロパティ:
user_properties.***
- イベント
- イベント名:
event_name
- タイムスタンプ:
event_timestamp
- バッチ処理関連
- ページビューの識別子:
batch_page_id
- 同一ページ内での何番目の計測パケット送信なのか:
batch_ordering_id
- 同じ計測パケットの中でのイベントの順序:
batch_event_index
- ページビューの識別子:
- イベントパラメータ:
event_params.***
- イベント名:
- トラフィック
- ユーザーの最初の流入元:
traffic_source.***
- 各イベント行における流入元:
collected_traffic_source.***
- セッションの流入元:
session_traffic_source_last_click.***
- ユーザーの最初の流入元:
- デバイス関連:
device.***
- 地域:
geo.***
- eコマース関連変数
- 取引に関する情報:
ecommerce.***
- 商品に関する情報:
items.***
- 取引に関する情報:
- 同意関連:
privacy_info.***
- ストリームID:
stream_id
(ウェブ、アプリなど1つのGA4プロパティで複数のストリームがある場合に識別する)
ユーザー
ユーザの識別子として2種類の列がある。
user_pseudo_id
: 自動計測される端末/ブラウザのID(アプリの場合Instance ID、ウェブの場合はcookieに記録されるクライアントID)user_id
: JavaScriptのタグ/Firebase SDKで明示的に送ったユーザID(計測者が指定した値)
user_pseudo_id
は必ず値が入る(null
にはならない)のに対して、user_id
はSDK側で明示的に取得設定をしない限り値が入らない(null
として格納される)。
計測者が任意に指定したユーザ属性であるユーザープロパティはuser_properties.***
に入る。ネストされた構造になっている。ユーザープロパティは最大で36文字しか格納できない。37文字以降は削除されて出力されるので、長い文字列のユーザー属性値を扱う場合は気を付けよう。たとえばSHA256でハッシュ化した16進文字列は64文字となるので、格納できません。
イベント
イベントに関連する変数としてイベント名はevent_name
に、イベントの発生したタイムスタンプはUnix timeのマイクロ秒として(timestamp型ではなくint64型)でevent_timestamp
に入る。
ページビューを特定するIDがbatch_page_id
である。同じページビューで発生したイベントはこの値が同じになる。次にページの読み込み処理が発生するまでは同一の値が保持される。同じページビューで発生したイベントをグルーピングするのに使うことができるので、「あるページでバナーを閲覧→そのバナーをクリックした」などを把握することができる。
GA4では計測パケットを1イベントずつ送信するとは限らず、同一ページビュー内で発生した複数のイベントを後でまとめて送信することがある。これをGA4での「バッチ処理」とはこのまとめて送信することを表す。一つのページビューの中で何回目のパケット送信なのかを示す数値がbatch_ordering_id
である。複数のイベントをまとめて送信したパケットでは、一つのbatch_ordering_id
の中に複数のイベントレコードが含まれる。この一つのバッチの複数のイベントの中で何番目に発生したイベントなのかを表す数値がbatch_event_index
である。
batch_page_id
、batch_ordering_id
、batch_event_index
の関係は以下の図のようになっている。
イベントとともに送られるイベント属性はイベントパラメータevent_params
に入る。パラメータには明示的に設定せずに設定で自動的に計測されるものと、計測者によるタグ設定で送られるものがある。主なイベントパラメータには
page_location
: ページのURLpage_title
: ページのタイトルpage_referrer
: リファラga_session_id
: セッションIDga_session_number
: 通算訪問回数
などがある。セッションIDはこれ単体でセッションをユニークに識別することを意図したものではなく、ユーザの識別子(user_id
またはuser_pesudo_id
)と組み合わせてセッションをユニークに特定する。
分析時に使いたい多くの変数がevent_params
に入るはずである。event_params
もuser_properties
と同様のネストされた構造になっている。
トラフィック
GA4にはトラフィック関連のディメンションが多数ある。探索機能のディメンション一覧の「トラフィック ソース」の欄には多数の項目があるのを確認できるだろう。数は多いが、
- ユニバーサルアナリティクス時代からあるUTMパラメータベースの参照元(
source
)/メディア(medium
)/キャンペーン(campaign
)/キーワード(term
)/広告コンテンツ(content
)の種類 - イベント行単位での流入元/セッション単位の流入元/ユーザー単位の流入元という粒度の違い
- Google広告や検索広告360などと自動タグ連携をしている場合、連携元ツールの種類
というさまざまな切り口での分類になる。BigQueryエクスポートにもトラフィック変数の列が複数あり、画面のディメンションと対応しているものとしていないものがある。BigQueryエクスポートの列とGA4ディメンションの対応関係は以下のようになっている(1はsource
、medium
、campaign_name
のみ掲載)。
traffic_source.***
ユーザの初回訪問時の流入元を表す列がtraffic_source.***
である。URLにUTMパラメータが含まれる場合は
utm_source
の値がtraffic_source.source
utm_medium
の値がtraffic_source.medium
utm_campaign
の値がtraffic_source.name
(traffic_source.campaign
ではない)
に入る。これ以外のUTMパラメータであるutm_term
とutm_content
は対象外となる。URLにUTMパラメータが含まれない場合はリファラに基づいて自動判別される。これらの列はセッションの流入元を表すものではない。なおストリーミング出力する場合のevents_intraday_***
ではこれらの列の値は出力されない(すべてnullになる)。
collected_traffic_source.manual_***
各イベント行における流入元を表す列がcollected_traffic_source.manual_***
である。
- URLにUTMパラメータが含まれればその値が採用される
- URLにUTMパラメータが含まれなければリファラに基づいて自動判定された値が入る
サイト内からの遷移であればすべてnullになる。
utm_source
の値がcollected_traffic_source.manual_source
utm_medium
の値がcollected_traffic_source.manual_medium
utm_campaign
の値がcollected_traffic_source.manual_campaign_name
(ollected_traffic_source.manual_campaign
ではない)
になる。
アプリからの流入を見る場合など、実際にトラフィックについて調べる際はUTMパラメータの値に加えてリファラも同時に見るケースがあるが、リファラはイベントパラメータpage_referrer
という違うところに入る点は留意しておこう。
collected_traffic_source.***id
各イベント行においてURLにGoogle広告やGoogleマーケティングプラットフォームから流入した際の連携用クリックIDが含まれる場合、それらの値を格納するのがcollected_traffic_source.***id
になる。
- Google広告の場合、
collected_traffic_source.gclid
に広告のgclid - キャンペーン マネージャー360やディスプレイ&ビデオ360の場合、
collected_traffic_source.dclid
にDoubleClickのクリックID - Googleマーチャントセンターの場合、
collected_traffic_source.srsltid
にマーチャントセンターのクリックID
が格納される。
session_traffic_source_last_click.***
当該セッションの流入元を表す列がsession_traffic_source_last_click.***
である。ここにはsession_traffic_source_last_click.manual_campaign.***
とsession_traffic_source_last_click.google_ads_campaign.***
があり、それぞれ流入元判定における手動タグ連携と自動タグ連携の変数が格納される。なおストリーミング出力する場合のevents_intraday_***
ではこれらの列の値は出力されない(すべてnullになる)。
session_traffic_source_last_click.manual_campaign.***
当該セッションの流入時に
- URLにUTMパラメータが含まれればその値が採用される
- URLにUTMパラメータが含まれなければリファラに基づいて自動判定された値が入る
Googleアナリティクス画面のディメンション「セッションの手動参照元」「セッションの手動メディア」などが対応する。Googleアナリティクスの仕様としてノーリファラの場合は直近の流入にさかのぼって流入元が適用され、さかのぼる流入元がない場合にのみ参照元に(direct)
、メディアに(none)
が入るが、BigQueryエクスポートでも同様の動作になる。
session_traffic_source_last_click.google_ads_campaign.***
Google広告との自動タグ連携に基づく広告側の情報が入る。重要な列として
- 広告アカウントのお客様ID(ディメンション「セッションのGoogle広告お客様ID」に該当)は
session_traffic_source_last_click.google_ads_campaign.customer_id
- キャンペーン名(ディメンション「セッション – Google 広告キャンペーン」に該当)が
session_traffic_source_last_click.google_ads_campaign.campaign_name
- 広告グループ名(ディメンション「セッションの Google 広告グループ名」に該当)が
session_traffic_source_last_click.google_ads_campaign.ad_group_name
に入る。Google広告に対してクリックコンバージョンをアップロードする場合は、session_traffic_source_last_click.google_ads_campaign.customer_id
からお客様IDを取得し、collected_traffic_source.gclid
をキーとしたコンバージョンデータを作成することになる。
Google広告の自動タグ連携と手動タグ連携を併用している場合(Googleアナリティクスと広告の管理画面で自動タグ連携をオンにしつつ、URLにUTMパラメータも付けている状態)にはsession_traffic_source_last_click.manual_campaign.***
にはUTMパラメータの値が入り、session_traffic_source_last_click.google_ads_campaign.***
には連携している広告アカウントの情報が入る。Google広告で手動タグ連携を使わず(URLにUTMパラメータを付けず)自動タグ連携のみの場合は、session_traffic_source_last_click.manual_campaign.***
にはリファラに基づく値が入り、session_traffic_source_last_click.google_ads_campaign.***
には連携している広告アカウントの情報が入る。したがってGoogle広告流入なのにsession_traffic_source_last_click.manual_campaign.source
ではyoutube
、session_traffic_source_last_click.manual_campaign.medium
ではreferral
などということもある。この後の自動タグ連携の列も見ないと真の流入はわからない。
session_traffic_source_last_click.sa360_campaign.***
検索広告360との自動タグ連携に基づく検索広告側の情報が入る。重要な列として
- 参照元(ディメンション「セッションの SA360 参照元」に該当)が
session_traffic_source_last_click.sa360_campaign.source
- キャンペーン名(ディメンション「セッションの SA360 キャンペーン」に該当)が
session_traffic_source_last_click.sa360_campaign.campaign_name
に入る。
session_traffic_source_last_click.cm360_campaign.***
キャンペーン マネージャー 360との自動タグ連携に基づくキャンペーン マネージャー 360側の情報が入る。
session_traffic_source_last_click.dv360_campaign.***
ディスプレイ&ビデオ 360との自動タグ連携に基づくディスプレイ&ビデオ 360側の情報が入る。
session_traffic_source_last_click.cross_channel_campaign.***
これらの自動タグ連携・手動タグ連携に基づいた最終的な「セッションの参照元」などの情報が入る。参照元、メディア、キャンペーンに加えてデフォルトチャネルグループの値も入ります。term
とcontent
は入らない。デフォルトチャネルグループでは、Google広告の自動タグ連携における検索広告のPaid Search
とP-MAXキャンペーンなどのCross-network
の違いも反映される。
このようにGA4にはトラフィック関連の列が多数あるが、基本的にはsession_traffic_source_last_click.cross_channel_campaign.***
を参照しておくといい。ただし前述したとおりノーリファラ流入が一部置き換えられるので、その点は留意する必要がある。分析の目的によってはcollected_traffic_source.manual_***
と併用するのがいい。
デバイス・ブラウザ
ブラウザ情報はFirebase(アプリ)とウェブで違う列に入る
- デバイスカテゴリ:
device.category
(アプリ/ウェブ) - OS名:
device.operating_system
(アプリ/ウェブ) - OSのバージョン:
device.operating_system_version
(アプリ/ウェブ) - ブラウザ名
device.browser
(アプリ)device.web_info.browser
(ウェブ)
- ブラウザのバージョン
device.browser_version
(アプリ)device.web_info.browser_version
(ウェブ)
デバイスカテゴリdevice.category
では、モバイルはmobile
、PCはノートPCも含めてdesktop
、タブレットはtablet
という値になる。まれにコネクテッドTVがsmart tv
として入る。
eコマース
eコマースイベントの場合のイベント情報はecommerce.***
、商品情報はitems.***
に入る。1回のeコマースイベントで複数の商品が操作された(表示された、購入されたなど)場合は、items
がネストされた構造になる。さらに各商品に対して任意の商品属性をカスタムディメンションとして指定することができるのだがそれらはitems
の下のitems.item_params.***
に入る。item_params
もevent_params
やuser_properties
と同様のネストされた構造になっている。
公式のスキーマ情報(随時更新されているので)
https://support.google.com/analytics/answer/7029846
ネスト(入れ子)されたデータ形式
イベントパラメータevent_params
、ユーザープロパティuser_properties
、eコマースの商品items
はネストされているレコードになっている。BigQueryのテーブルスキーマでは種類が「RECORD」、モードが「REPEATED」になっている。
BigQuery UIではこのようにデータが表示されている。
ネストされている、入れ子になっているというのは、データの親子構造をそのまま一つのテーブルに格納した形である。一般的なRDBでは1個のセルには1個の値しか入らない。
ネストされたデータはセルにテーブルが入ったような形式になっている。
一般的なRDBでは別テーブルにして正規化するところを、BigQueryではテーブルをテーブルの中に入れて済ませてしまう。入れ子になるテーブルは1行の場合もあれば、複数行になる場合もある(REPEATED)。BigQueryなど一部の分析用DBに固有の形式で、一般的なRDBにはあまりない。
このまま列名を指定してSELECTしても欲しい変数を取れない。これがGoogleアナリティクス+BigQueryの厄介な形式。ユニバーサルアナリティクスのGA360でも似た構造だった。
GA4の場合
イベントパラメータ、ユーザープロパティ、eコマースの商品などがネストされている
このままでは使えないので、以下のように変換すると列名を指定して変数を取得できる
→ネストの部分を展開して通常のレコードにする:UNNEST(階層構造のフラット化)
UNNEST
たとえばページのURL(page_location
)を扱う際、以下のようにクエリを書きたい
SELECT
event_name,
page_location
FROM
`prj.analytics_999999999.events_20210127`;
SELECT
user_pseudo_id,
FROM
`prj.analytics_999999999.events_20210127`
WHERE
page_location = 'https://example.com/products.php';
しかしこれはできない
ページのURLをSELECTしたい、もしくはURLを条件にユーザを抽出したい場合
クエリ中の
page_location
の部分を
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_location')
に置き換える。
ep.value.***_value
パラメータによって型が異なり、取得する列が異なるep.value.string_value
: 文字列型ep.value.int_value
: 整数型ep.value.double_value
: 浮動小数点小数型
ep.key = '***'
イベントパラメータの値を取得する(SELECT
)
SELECT
event_name, -- 通常の列
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_location') -- ネストされた列
FROM
`prj.analytics_999999999.events_20210127`
イベントパラメータの値で絞り込む(WHERE
)
SELECT
user_pseudo_id
FROM
`prj.analytics_999999999.events_20210127`
WHERE
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_location')
= 'https://example.com/products.php'
ユーザープロパティの場合は以下のように書き換えればいい
UNNEST(event_params) ep
→UNNEST(user_properties) up
ep.
→up.
イディオムで覚えておけばいい。
(例題)event_name
が「first_visit」「session_start」以外のイベントについて
user_pseudo_id
event_name
とともに
page_title
ga_session_number
link_url
を抽出
SELECT
user_pseudo_id,
event_name,
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_title' ) page_title,
(SELECT ep.value.int_value FROM UNNEST(event_params) ep WHERE ep.key = 'ga_session_id') ga_session_id,
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'link_url' ) link_url
FROM
`prj.analytics_999999999.events_20210127`
WHERE
event_name NOT IN ('first_visit', 'session_start');
UNNESTのもう一つのパターン(たまに使う)
セルの結合を解除して単純な行列にするイメージ。赤字は上の行の繰り返し
(例)各イベントヒットに対して計測されたパラメータ名の一覧を取得する
こんなテーブルを作る
1行目から5行目はパラメータ名以外同じ(前ページの赤字部分)
SELECT
event_name,
event_timestamp,
ep.key,
FROM
`prj.analytics_9999.events_20210127`, UNNEST(event_params) ep
FROM
の中でテーブル, UNNEST(展開する対象)
- 展開する対象は
event_params
user_properties
items
UNNEST(event_params) ep
のep
は展開した部分の仮の名前
カンマはCROSS JOINという珍しいJOINの種類
(課題)当日データについて、以下の形式のテーブルを作る
- すべてのパラメータ名と型を取得
- それに対して以下の書式で列を作る
(SELECT ep.value.***_value FROM UNNEST(event_params) ep WHERE ep.key = '***')
- デバイスやOSの列名は?
取得しているパラメータ
SELECT
ep.key,
SUM(case when ep.value.string_value is not null then 1 else 0 end) type_str,
SUM(case when ep.value.int_value is not null then 1 else 0 end) type_int,
SUM(case when ep.value.double_value is not null then 1 else 0 end) type_double
FROM
`prj.analytics_999999999.events_intraday_20210128`, UNNEST(event_params) ep
GROUP BY
key
event_params.value.string_value
に値があれば文字列型、int_value
に値があれば整数型、double_value
に値があれば浮動小数点小数型→その数をカウント
答え
SELECT
user_pseudo_id,
event_timestamp,
event_name,
device.category,
device.operating_system,
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_location' ) page_location,
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_title' ) page_title,
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'page_referrer' ) page_referrer,
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'source' ) source,
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'medium' ) medium,
(SELECT ep.value.string_value FROM UNNEST(event_params) ep WHERE ep.key = 'campaign' ) campaign,
(SELECT ep.value.int_value FROM UNNEST(event_params) ep WHERE ep.key = 'ga_session_id' ) ga_session_id,
:
FROM
`prj.analytics_999999999.events_intraday_20210128`;
実際にログを使った分析はこちらの記事を参照。
[公開日:2021年3月11日]GCP/Firebase の記事一覧