- raw / staging / mart という 3 層データセット設計を、本書のどの章の技術で埋めるかを一気通貫で組み立てる方法
- GCS バッチロードと生成データという 2 つの経路で raw 層を作り、ネストを展開して staging 層に整える方法
- PARTITION BY / CLUSTER BY で作った mart テーブルを、スケジュールクエリまたは Dataform で日次自動化する設計判断
- マテリアライズドビューでの高速化と、BigQuery のクエリ結果から直接ダッシュボードへ繋ぐ可視化の入口
- INFORMATION_SCHEMA でのコスト・品質レビューと、承認済みビューによる権限設計を最初から組み込む方法
前提: ハンズオン 18-1・18-2・18-3 の大半はサンドボックスで完走可・無料枠内。日次自動化(スケジュールクエリ)だけは課金プロジェクト必須(概算 1 円未満)。
この章のゴールと全体アーキテクチャ
ここまでの章で、BigQuery の取り込み・変換・最適化・可視化・運用の技術を個別に学んできました。この章ではそれらを 1 つのプロジェクトとして繋ぎ直します。題材は「疑似的なイベントログを溜め、州別・プラットフォーム別に集計してダッシュボードにする」という小さな分析基盤です。
設計の軸は raw / staging / mart という 3 層のデータセット分割です。
| 層 | 役割 | 主な操作 | 対応する章 |
|---|---|---|---|
| capstone_raw | 取り込んだそのままの形を保持し、再現性を確保する | GCS バッチロード・生成データの CTAS | 第 2 章・第 3 章 |
| capstone_staging | 型・表記を整え、ネストされた構造を扱いやすくする | JOIN・STRUCT フィールドアクセス・型キャスト | 第 4 章・第 5 章 |
| capstone_mart | 分析・BI 向けに集計し、スキャン量とレイテンシを削る | PARTITION BY・CLUSTER BY・マテリアライズドビュー | 第 6 章・第 8 章 |
データは 2 つの経路で raw 層に入ります。1 つは Cloud Storage 上の公開 CSV を bq load でそのまま取り込む経路(第 3 章のバッチロード)、もう 1 つは GENERATE_TIMESTAMP_ARRAY で疑似的なイベントログをその場で生成する経路です。後者は本番のストリーミング取り込み(第 12 章)を模した、サンドボックスだけで完結する代替手段だと考えてください。
capstone_ という接頭辞)はプロジェクトの最初に固定し、途中で変えないようにしてください。変換の自動化と可視化の位置づけ
staging → mart の再集計を毎日走らせる手段は 2 つあります。テーブル数が少なく依存関係も単純ならスケジュールクエリで十分ですが、テーブルが増えて依存グラフが複雑になると Dataform の ref() による依存解決が効いてきます。どちらの作り方もすでに第 11 章で確認済みなので、この章では設計判断だけを扱います。
mart 層のさらに先には、頻出の集計をあらかじめ計算しておくマテリアライズドビューがあります。インクリメンタル更新が効いている間、BigQuery はベーステーブル(staging)宛のクエリを自動的にマテリアライズドビューへ書き換えます(スマートチューニング)。ベーステーブルの変更がビューを無効化しうる場合は、ビューではなくベーステーブルを直接読みに行きます。マテリアライズドビューは他のマテリアライズドビューの上にネストできず、外部テーブル・ワイルドカードテーブル・論理ビュー・テーブルスナップショットの上にも作れません。
可視化は BigQuery のクエリ結果から直接 BI ツールへ渡します。本書執筆時点(2026-08)の BigQuery 公式ドキュメントでは、以前 Looker Studio と呼ばれていたこの製品は Data Studio という表記に戻っています。ダッシュボードはクエリ結果に基づく一時テーブルを介して作られ、この一時テーブルは最大 24 時間保持されます。Data Studio のチャートは最大 5,000 行までしか可視化できず、BigQuery クエリ自体も 1 回の結果として返せるのは最大 20 MB という制限があるため、mart 層のような集計済みテーブルを接続先に選ぶことが重要です。
コストと権限をどう組み込むか
集計基盤は作って終わりではありません。INFORMATION_SCHEMA.JOBS_BY_PROJECT でどのクエリがどれだけのバイト数・スロットを消費したかを確認できる状態にしておくと、想定外の高コストクエリにすぐ気づけます。権限面では、分析者に raw / staging 層への直接アクセスを与えず、mart 層の承認済みビューだけを見せる設計にします。
3 つのデータセットを作り、raw 層に 2 経路でデータを供給します。サンドボックスで完走可・無料枠内(スキャン量は数百 KB 程度)。
- raw / staging / mart の 3 データセットを作成します(ロケーションは US)。
bq mk --location=US --dataset PROJECT_ID:capstone_raw
bq mk --location=US --dataset PROJECT_ID:capstone_staging
bq mk --location=US --dataset PROJECT_ID:capstone_mart
bq load --location=US --autodetect --source_format=CSV \
capstone_raw.dim_states \
gs://cloud-samples-data/bigquery/us-states/us-states.csv
-- 処理: 数十 KB 程度
SELECT * FROM `PROJECT_ID.capstone_raw.dim_states` LIMIT 5;
期待される出力: name / post_abbr の 2 列で州名の行が返ります。
GENERATE_TIMESTAMP_ARRAYで 1 週間・1 時間刻みの疑似イベントログを生成します。実行前に dry run で処理バイト量を確認する習慣を忘れずに(このクエリは定数からの生成なのでスキャン量はごくわずかです)。
-- 処理: 数百 KB 未満(168 行の生成データ、無料枠内)
CREATE OR REPLACE TABLE `PROJECT_ID.capstone_raw.events_raw` AS
WITH seed AS (
SELECT ts, seq
FROM UNNEST(
GENERATE_TIMESTAMP_ARRAY('2026-07-01 00:00:00', '2026-07-07 23:00:00', INTERVAL 1 HOUR)
) AS ts WITH OFFSET seq
)
SELECT
GENERATE_UUID() AS event_id,
ts AS event_ts,
['CA', 'TX', 'NY', 'WA', 'FL'][OFFSET(MOD(seq, 5))] AS state_abbr,
['signup', 'view_item', 'add_to_cart', 'purchase'][OFFSET(MOD(seq, 4))] AS event_name,
STRUCT(
['web', 'ios', 'android'][OFFSET(MOD(seq, 3))] AS platform,
ARRAY(
SELECT tag FROM UNNEST(['sale', 'new_user', 'repeat']) AS tag WITH OFFSET o
WHERE MOD(seq + o, 2) = 0
) AS tags
) AS client,
ROUND(RAND() * 200, 2) AS amount_usd
FROM seed;
期待される出力: 168 行(7 日 × 24 時間)。client 列が platform(STRING)と tags(ARRAY<STRING>)を持つ STRUCT になっていることを、スキーマタブで確認してください。この入れ子構造が次のハンズオンで展開する対象です。
後片付け: 3 つのデータセットは次のハンズオンでも使うため、ここでは削除しません。
ネストを展開して staging 層を作り、パーティション・クラスタ化した mart テーブルへ集計します。テーブル作成自体はサンドボックスで完走可・無料枠内。自動化(手順 3)だけは課金プロジェクト必須(概算 1 円未満)。
- staging 層でネストを展開し、州名を結合します(第 4 章・第 5 章)。
client.platformは STRUCT フィールドへのドット表記で取り出し、tags配列はそのまま列として残します(後でIN UNNESTで判定するため、ここで展開して行を増やしません)。
-- 処理: 数百 KB 未満
CREATE OR REPLACE TABLE `PROJECT_ID.capstone_staging.events_flat` AS
SELECT
e.event_id,
DATE(e.event_ts) AS event_date,
e.event_ts,
e.state_abbr,
s.name AS state_name,
e.event_name,
e.client.platform AS platform,
e.client.tags AS tags,
e.amount_usd
FROM `PROJECT_ID.capstone_raw.events_raw` AS e
LEFT JOIN `PROJECT_ID.capstone_raw.dim_states` AS s
ON e.state_abbr = s.post_abbr;
期待される出力: 168 行(raw と 1 対 1)。platform がフラットな STRING 列になり、state_name が結合できていることを確認してください。
- mart 層をパーティション + クラスタ化して作ります(第 6 章)。配列の中身は
IN UNNESTで判定し、行を増やさずに集計します。
-- 処理: 数百 KB 未満
CREATE OR REPLACE TABLE `PROJECT_ID.capstone_mart.daily_state_events`
PARTITION BY event_date
CLUSTER BY state_abbr, event_name
AS
SELECT
event_date,
state_abbr,
state_name,
event_name,
COUNT(*) AS event_count,
COUNTIF('repeat' IN UNNEST(tags)) AS repeat_tagged_count,
ROUND(SUM(amount_usd), 2) AS total_amount_usd
FROM `PROJECT_ID.capstone_staging.events_flat`
GROUP BY event_date, state_abbr, state_name, event_name;
- この再集計を自動化します。テーブル数は 3 つ、依存も raw → staging → mart の一直線なので、図 18-3 の判断基準に従いスケジュールクエリで十分です(第 11 章)。
bq query \
--use_legacy_sql=false \
--display_name="capstone_daily_mart_refresh" \
--schedule="every 24 hours" \
--destination_table=PROJECT_ID:capstone_mart.daily_state_events \
--replace=true \
'SELECT
event_date, state_abbr, state_name, event_name,
COUNT(*) AS event_count,
COUNTIF("repeat" IN UNNEST(tags)) AS repeat_tagged_count,
ROUND(SUM(amount_usd), 2) AS total_amount_usd
FROM `PROJECT_ID.capstone_staging.events_flat`
GROUP BY event_date, state_abbr, state_name, event_name'
サンドボックスで試す場合は、手順 3 のスケジュール登録の代わりに、手順 2 の CREATE OR REPLACE TABLE をそのままクエリエディタで手動実行する運用に置き換えてください(スケジュールクエリは BigQuery Data Transfer Service の機能であり、サンドボックスでは利用できません)。
後片付け: 3 つのデータセットは次のハンズオンでも使うため削除しません。スケジュールクエリを登録して確認が済んだら、BigQuery Studio の Scheduled queries 画面から削除してください。
mart をさらに高速化するマテリアライズドビューを作り、クエリ結果を Data Studio に接続します。いずれもサンドボックスで完走可・無料枠内。
- プラットフォーム別・日別の集計をマテリアライズドビューにします。
-- 処理: 初回のみ数百 KB 未満。以降はベーステーブルの差分のみ
CREATE MATERIALIZED VIEW `PROJECT_ID.capstone_mart.mv_platform_daily`
AS
SELECT
event_date,
platform,
COUNT(*) AS event_count,
ROUND(SUM(amount_usd), 2) AS total_amount_usd
FROM `PROJECT_ID.capstone_staging.events_flat`
GROUP BY event_date, platform;
mv_platform_daily を参照していることが分かります。SELECT platform, COUNT(*) AS event_count
FROM `PROJECT_ID.capstone_staging.events_flat`
GROUP BY platform;
SELECT event_date, state_abbr, state_name, event_count, total_amount_usd
FROM `PROJECT_ID.capstone_mart.daily_state_events`
ORDER BY event_date;
後片付け: 3 つのデータセットは次のハンズオンでも使うため削除しません。作成した Data Studio のレポートは、他者と共有していなければ残しておいても課金は発生しません(BigQuery 側のクエリ実行分のみが課金対象です)。
INFORMATION_SCHEMA でコストを確認し、承認済みビューで権限を設計してから、作った 3 データセットをすべて削除します。サンドボックスで完走可・無料枠内(メタデータ照会とビュー作成のみ)。
- 直近 1 日のジョブをコスト順に確認します。
-- 処理: メタデータのみ(課金対象バイト数はごくわずか)
SELECT
user_email,
job_type,
statement_type,
ROUND(total_bytes_processed / POW(1024, 3), 4) AS gb_processed,
ROUND(total_slot_ms / 1000, 1) AS slot_seconds,
query
FROM `PROJECT_ID`.`region-us`.INFORMATION_SCHEMA.JOBS_BY_PROJECT
WHERE
creation_time >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 1 DAY)
AND state = 'DONE'
AND REGEXP_CONTAINS(query, r'capstone_')
ORDER BY total_bytes_processed DESC
LIMIT 20;
期待される出力: このハンズオンで実行してきたクエリが一覧され、どのクエリが最もバイト数・スロットを消費したかがひと目で分かります。Data Studio 経由のジョブには looker_studio_report_id / looker_studio_datasource_id という labels が付くため、BI 由来のコストだけを切り分けたいときはこの labels で絞り込めます。
CREATE VIEW `PROJECT_ID.capstone_mart.events_flat_view` AS
SELECT event_date, state_abbr, state_name, event_name, platform, amount_usd
FROM `PROJECT_ID.capstone_staging.events_flat`;
capstone_staging データセットを開き、Details ペインの「Sharing」→「Authorize views」から、上記ビューの完全修飾名(PROJECT_ID.capstone_mart.events_flat_view)を入力して「Add authorization」をクリックします。capstone_mart データセットへの BigQuery Data Viewer とプロジェクトの Job User 権限だけを付与し、capstone_raw / capstone_staging への権限は一切付与しません。後片付け(この章の最終後片付け):
bq rm -r -f -d PROJECT_ID:capstone_mart
bq rm -r -f -d PROJECT_ID:capstone_staging
bq rm -r -f -d PROJECT_ID:capstone_raw
スケジュールクエリを登録したままの場合は、BigQuery Studio の Scheduled queries 画面からも削除してください。マテリアライズドビューと承認済みビューは、データセットの削除と同時に削除されます。
つまずきポイント
CREATE OR REPLACE TABLE を手動で再実行する運用に置き換えてください。capstone_staging)と必ず別でなければなりません。また、ビュー側の承認だけでは不十分で、分析者自身にもビューを含むデータセットへの閲覧権限とジョブ実行権限(roles/bigquery.jobUser)を別途付与する必要があります。理解度チェック
Q1. raw / staging / mart の 3 層にデータセットを分ける狙いは何ですか?
raw は取り込んだそのままの形を保持して再現性を確保し、staging で型・表記・ネスト構造を整え、mart で分析・BI 向けに集計してクエリを高速化するためです。層ごとに役割を分けることで、どこかの層で問題が起きても手前の層から作り直せます。
Q2. マテリアライズドビューがクエリを自動的に書き換える(スマートチューニング)ための前提条件は何ですか?
インクリメンタル更新が有効なマテリアライズドビューであり、かつベーステーブルの変更がまだビューを無効化していない状態であることです。ベーステーブルの変更がビューを無効化しうる場合、BigQuery はビューではなくベーステーブルを直接読みに行きます。
Q3. 承認済みビューを作るとき、ビューを保存するデータセットをソースデータのデータセットと分ける理由は何ですか?
承認済みビューは「ビューには見せるがソースデータセットには直接アクセスさせない」ことが目的だからです。同じデータセットに保存すると、ビューへのアクセス権がそのままソースデータへの直接アクセス権にもなってしまい、権限を分離できません。
Q4.(シナリオ)サンドボックスのまま staging → mart の再集計を毎日自動化したい。最も適切な対処は?
A. DML で自動更新するトリガーを書く B. 再集計クエリを手動で定期的に再実行する運用に切り替える C. Dataform のワークフロースケジュールを使う D. Storage Write API でストリーミング挿入する
B。 サンドボックスはストリーミングデータ・DML・BigQuery Data Transfer Service を明示的にサポートしておらず、スケジュールクエリも Dataform のスケジュール実行もこの Data Transfer Service の機能に依存するため使えません。A はサンドボックスで禁止されている DML そのものです。C はスケジュールクエリと同様に課金プロジェクトが必要です。D はストリーミングデータ自体がサンドボックス非対応です。
Q5.(シナリオ)分析チームが増え、raw と staging には一切アクセスさせず、mart 層の日次集計だけを見せたい。最も適切な権限設計は?
A. 分析者全員にプロジェクトの BigQuery Data Viewer ロールを付与する B. capstone_mart に閲覧用ビューを作り、Authorize views で capstone_staging への読み取りをビューに委任した上で、分析者には capstone_mart のデータセットアクセス権とジョブ実行権限だけを付与する C. 分析者ごとにサービスアカウントの鍵ファイルを配布する D. raw テーブルの内容をアクセス制御なしの別プロジェクトにコピーする
B。 プロジェクト全体への Data Viewer を配る A は raw / staging も見えてしまい層の切り分けが機能しません。C は鍵管理の負担とセキュリティリスクが増すだけで権限設計の解決になりません。D はガバナンスを放棄しています。B が承認済みビューの標準的な使い方で、raw と staging への直接アクセス権を与えずに mart の集計だけを見せられます。
まとめと次章へ
- raw / staging / mart の 3 層設計は、取り込みの再現性・変換の整備・分析の高速化という役割分担そのもの
- staging → mart の日次自動化は、依存関係の複雑さでスケジュールクエリと Dataform を使い分ける(第 11 章)
- マテリアライズドビューは、ベーステーブル未変更時の自動クエリ書き換えで mart をさらに高速化する
- Data Studio への接続は「Open in」からワンクリックで、一時テーブルは 24 時間、チャートは最大 5,000 行という制限を踏まえて mart 層を接続先に選ぶ
- コストと権限は後付けにせず、INFORMATION_SCHEMA での監視と承認済みビューでの層分離を最初から組み込む
発展課題として、raw 層を Storage Write API によるストリーミング取り込みに置き換える(第 12 章)、mart 層に AI 関数を組み合わせてイベントの要約や異常検知を加える(第 14 章)といった拡張が考えられます。本書はここで一区切りですが、この章で組んだ 3 層構造はどの章の技術を差し替えても壊れない骨格として、そのまま実務に持ち出せます。