はじめに:GA4のテーブルを開いて戸惑う

GA4のBigQueryエクスポートを初めて開いた人の多くが、最初にぶつかる壁があります。それは「1行が、ふつうの表のようになっていない」という点です。

たとえば event_params というカラムを覗いてみると、その中にさらに keyvalue のセットが何個も入っています。items というカラムには、その購入で買われた商品が複数ぶら下がっています。SQLに慣れている人ほど「これは何だ」と固まってしまうのではないでしょうか。一般的なリレーショナルデータベースでは、1セルに1つの値が入っているのが当たり前だからです。

これはGA4が雑なわけではなく、BigQueryが持っている「ネスト構造(入れ子)」をきちんと使った設計になっているからです。具体的には、STRUCT型とARRAY型という2つの型が組み合わさっています。この仕組みを理解しないままクエリを書こうとすると、「event_params.value が取れない」「items がうまく展開できない」といった壁に何度もぶつかります。

逆に言えば、STRUCTとARRAYの考え方さえ腹に落ちてしまえば、GA4のデータは驚くほど素直に扱えるようになります。この記事では、まず2つの型の正体を整理し、GA4でよく使う event_paramsitems の展開方法を見たうえで、中小ECの分析現場で使いやすいデータマートをどう設計するかまで順番に解説します。

なお、自分のテーブルにどんなフィールドが存在するのかまだ把握できていない場合は、先にINFORMATION_SCHEMAでGA4のネストされたスキーマを効率的に探索するでパスの一覧を出してから、この記事でUNNESTの使い方を確認するとスムーズです。

STRUCT型とARRAY型とは何か

最初に、用語そのものを押さえておきます。難しそうな名前ですが、考え方はとてもシンプルです。

STRUCT:関連する値をひとまとめにした「箱」

STRUCT型は、複数のフィールドを1つにまとめた「箱」のようなものです。プログラミングをやる人ならオブジェクト、Excelに例えるなら「いくつかの列をグループにしたもの」と考えると近いです。

たとえば商品情報なら、商品ID・商品名・価格という3つの値を、それぞれ別のカラムにせず1つのSTRUCTにまとめられます。アクセスするときは item.item_id item.item_name のようにドットでつないで、箱の中のフィールドを指定します。GA4でいうと、後で触れる items の中身の1件1件がSTRUCTです。

ARRAY:同じ型の値を順番に並べた「リスト」

ARRAY型は、同じ型の値を複数並べたリストです。1つの注文に複数の商品があるように、「1行に対して複数の値がぶら下がる」状況を表現するために使います。

GA4の event_params は「STRUCTのARRAY」、つまり「箱のリスト」になっています。ひとつのイベントに page_locationga_session_id などのパラメータが何個もぶら下がっているので、それぞれのパラメータをSTRUCT(key と value の箱)として持ち、それをARRAYで並べているわけです。

なぜネストするのか

ネスト構造には、地味ですが効いてくる利点があります。1つのイベントに紐づく情報を1行にまとめて持てるので、テーブルを分けてJOINで結合し直す必要がありません。読み込むデータ量を抑えやすく、スキャン課金のBigQueryとは相性が良い設計です。「最初はとっつきにくいが、慣れると合理的」というのがネスト構造の正体です。

もしGA4が従来のリレーショナルな形を取っていたら、イベント本体のテーブルとパラメータのテーブル、商品のテーブルを別々に持ち、分析のたびにキーでJOINし直す必要があったはずです。ネスト構造はその結合をあらかじめ済ませた状態でデータを持っている、と考えると分かりやすくなります。その分、展開のひと手間が必要になるのがUNNESTというわけです。

event_params と items をUNNESTで展開する

ネストされたARRAYを通常の行として扱うには、UNNEST を使ってARRAYを「行方向に開く」操作が必要です。ここがGA4クエリの最大の山場なので、丁寧に見ていきます。

event_params から値を1つ取り出す

たとえば「各イベントのページURL(page_location)を取り出したい」とします。page_locationevent_params というARRAYの中に、key が page_location のSTRUCTとして入っています。次のように書きます。

SELECT
  event_timestamp,
  event_name,
  (
    SELECT value.string_value
    FROM UNNEST(event_params)
    WHERE key = 'page_location'
  ) AS page_location
FROM `your_project.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX = '20260801'
  AND event_name = 'page_view'

ポイントは、サブクエリの中で UNNEST(event_params) してARRAYを行に開き、WHERE key = 'page_location' で目的のパラメータだけに絞っている点です。value 自体もSTRUCTになっていて、文字列なら string_value、整数なら int_value と、型ごとにフィールドが分かれています。ここを取り違えると NULL ばかりになるので注意してください。

複数のパラメータをまとめて取り出す

パラメータを何個も取りたい場合、サブクエリを並べると読みにくくなります。よく使うパラメータが多いときは、横に並べる書き方のほうが見通しが良くなります。

SELECT
  event_timestamp,
  event_name,
  MAX(IF(key = 'page_location', value.string_value, NULL)) AS page_location,
  MAX(IF(key = 'ga_session_id', value.int_value, NULL)) AS ga_session_id,
  MAX(IF(key = 'page_title',    value.string_value, NULL)) AS page_title
FROM `your_project.analytics_123456789.events_*`,
  UNNEST(event_params)
WHERE _TABLE_SUFFIX = '20260801'
GROUP BY event_timestamp, event_name

FROM の直後にカンマで UNNEST(event_params) を置くと、イベント1行ごとにパラメータの数だけ行が増えます。その状態で IFMAX を組み合わせ、GROUP BY で1イベント1行に畳み直す書き方です。条件分岐で横持ちに変換する、いわゆるピボットの考え方になります。

items を展開して商品単位にする

ECの分析では items(その取引で扱われた商品のARRAY)の展開が欠かせません。purchase イベントを商品1件ごとの行に開くには、次のように書きます。

SELECT
  event_timestamp,
  (
    SELECT value.string_value
    FROM UNNEST(event_params)
    WHERE key = 'transaction_id'
  ) AS transaction_id,
  item.item_id,
  item.item_name,
  item.quantity,
  item.price
FROM `your_project.analytics_123456789.events_*`,
  UNNEST(items) AS item
WHERE _TABLE_SUFFIX = '20260801'
  AND event_name = 'purchase'

UNNEST(items) AS item で商品のARRAYを行に開き、item という別名を付けています。あとは item.item_id のようにドットでSTRUCTの中身を取り出すだけです。これで「1注文=複数行(商品ごと)」という、売れ筋分析やカテゴリ集計に使いやすい形になります。

自作martでSTRUCT・ARRAYを活かす設計

UNNESTの書き方を覚えると、つい何でもフラットに開きたくなります。ですが、データマートを設計するときは「どこまで開いて、どこを残すか」を意識すると、後々ずっと扱いやすくなります。

フラットな「イベントmart」を1枚持つ

まず土台として、よく使うパラメータを横持ちに展開した「イベントmart」を1枚用意するのがおすすめです。page_location ga_session_id user_pseudo_id など、分析で毎回使う値を最初から列にしておけば、毎回UNNESTを書く手間がなくなり、SQLに不慣れなメンバーでもクエリを書けるようになります。

このmartは「1イベント=1行」の素直な表にしておきます。ネストを残さないことで、Looker StudioなどのBIツールからも直接つなぎやすくなります。

商品系はSTRUCT・ARRAYを残したmartも併用する

一方で、商品まわりは無理にフラット化せず、STRUCT・ARRAYを残したmartを別に持つ判断も有効です。たとえば「1注文=1行、商品リストはARRAY」の形なら、注文単位の指標(合計金額・商品点数)をそのまま1行で持ちつつ、必要なときだけ UNNEST(items) で商品単位に開けます。

注文単位と商品単位を1枚のフラットなテーブルで兼ねようとすると、注文金額が商品の数だけ重複して合計がずれる、という事故が起きがちです。ネストを残しておけば、この二重計上を構造的に防げます。「集計の粒度が違うものは、無理に同じ表に押し込めない」というのが設計の勘所です。

役割で2層に分ける

整理すると、(1) 毎日更新するフラットなイベントmart、(2) 注文・商品のようにネストを残したほうが安全なmart、という2層構成にしておくと、用途に応じて使い分けられます。

変換ロジックを管理する仕組みも考える

martの設計が固まってくると、次に課題になるのが「変換用のSQLをどう管理するか」です。UNNESTやピボットを含むクエリは長くなりがちで、エクスポートの生データから中間テーブル、最終的なmartへと段階的に積み上がっていきます。こうした依存関係を手書きのスケジュールドクエリだけで管理しようとすると、どのクエリがどのテーブルに依存しているのか追えなくなりがちです。

このあたりはdbtのような変換管理の仕組みと組み合わせると、ぐっと見通しが良くなります。中間テーブルとmartの依存関係をツール側が把握してくれるので、ネストを残す層と開く層を意図どおりに積み上げられます。具体的な始め方は「dbtで作るGA4×BigQueryのデータパイプライン入門」で触れているので、本格的に運用する段階になったら参照してみてください。

フラット化との使い分けとコストの考え方

最後に、「全部フラットにすればいいのでは」という疑問に答えておきます。結論から言うと、フラット化には明確な利点とコストの両方があり、場面で使い分けるのが現実的です。

フラット化のメリット

フラットなテーブルは、とにかく分かりやすいのが最大の利点です。UNNESTを知らなくてもクエリが書けますし、BIツールとの相性も良く、チーム内で共有しやすくなります。よく使う指標を事前に展開しておけば、毎回の集計が速く、ミスも減ります。

フラット化のコスト

一方で、items のような1対多の関係を無理に1枚のフラットテーブルに開くと、行が大きく膨らみます。1注文に商品が5件あれば行数は5倍になり、注文に紐づく情報(注文日・顧客ID・合計金額など)が商品の数だけ繰り返しコピーされます。ストレージが増えるだけでなく、注文金額を単純に SUM すると重複して水増しされる、といった集計ミスの温床にもなります。

BigQueryはスキャンしたデータ量に応じて課金される仕組みなので、重複して膨らんだテーブルを毎回フルスキャンすると、見えないところで費用がじわじわ増えます。フラット化は「速くて分かりやすい」反面、「データ量と二重計上のリスク」を引き受ける選択でもあるわけです。

使い分けの目安

目安として、「1対1で素直に開けるもの(イベント単位のパラメータ)」は積極的にフラット化し、「1対多のもの(注文に対する商品)」はネストを残すか、粒度を決めてから開く、と考えると判断しやすくなります。また、巨大なエクスポートテーブルを毎回スキャンしてmartを作り直すのは非効率なので、パーティションで処理範囲を絞る工夫も欠かせません。この点は「GA4×BigQueryのテーブル肥大化を防ぐパーティション有効期限の設定」と合わせて読むと、コスト面の全体像がつかめます。

まとめ

GA4のBigQueryエクスポートが独特に見えるのは、STRUCT(関連する値をまとめた箱)とARRAY(同じ型の値を並べたリスト)というネスト構造を使っているからでした。この2つさえ理解すれば、event_paramsitemsUNNEST で素直に展開できます。

そのうえでデータマートを設計するときは、「全部フラットにする」か「全部ネストのまま」かの二択ではなく、用途に応じて使い分けるのが現実的です。1対1で開けるイベント単位のパラメータはフラットなmartにまとめ、1対多になる注文と商品は二重計上を避けるためにネストを残す、という2層構成が扱いやすくなります。

ネスト構造は最初こそ戸惑いますが、慣れるとスキャン量を抑えながら必要な情報を1行に持てる、合理的な仕組みです。中小ECの分析でも、この設計を土台にしておくと、後から指標を増やしたいときに無理なく拡張できます。まずは手元の event_params を1つ UNNEST してみるところから始めてみてください。

STRUCT・ARRAYを踏まえたデータマート設計や、注文・商品単位の粒度を整理したデータ基盤づくりを相談したいという場合は、お問い合わせからご連絡ください。