BigQuery ハンズオン教科書|2026-08 版
第 2 章 サンドボックスで完走可

データセット・テーブル・スキーマ

リソースモデルとロケーションを理解し、正しい置き場所にテーブルを作る

この章で学ぶこと

前提: サンドボックスで完走可・無料枠内(全ハンズオンの処理量は数十 MB 未満、DML は使用しません)。

リソースモデル — プロジェクト・データセット・テーブル

BigQuery のリソースは 3 層の入れ子で整理されています。一番外側が Google Cloud のプロジェクト、その中にデータセットという「トップレベルのコンテナ」があり、テーブルとビューはこのデータセットに属さなければなりません。データをロードする前に、必ず少なくとも 1 つのデータセットを作成しておく必要があります。

データセットにはテーブル(標準・パーティション・クラスタ化)のほか、ビュー(論理ビュー・マテリアライズドビュー)、ルーティン(ユーザー定義関数・ストアドプロシージャ)を格納できます。リソースを一意に指すための完全修飾名は、GoogleSQL では projectname.datasetname.tablename、bq コマンドラインツールでは projectname:datasetname.tablename という形式を取ります。データセット名はプロジェクト内で一意、テーブル名はデータセット内で一意である必要があります。

プロジェクト(myproject) データセット: mydataset ロケーションはここで固定・作成後に変更不可 テーブル ビュー ルーティン
図 2-1 データセットはプロジェクト内のコンテナで、テーブル・ビュー・ルーティンを格納する。ロケーションはデータセット単位で固定され、中のテーブルはすべて同じロケーションに属す。

ロケーション — 作成後には変更できない意思決定

ロケーションは、データセットを作成するときに必ず指定する項目で、「US」「EU」のようなマルチリージョンと、「asia-northeast1(東京)」のようなリージョンから選びます。マルチリージョンは広い地理的範囲を指しますが、実体としてのデータは単一のリージョンに保存される点に注意してください(マルチリージョンはリージョン冗長性を提供しません)。

最も重要なのは次の 1 点です。データセット作成後、ロケーションは変更できません。そして、クエリはデータと同じロケーションでしか実行できません。BigQuery のドキュメントは「リクエストに関わるすべてのデータセットのロケーションが、ジョブのロケーションと一致しなければならない」と明記しています。ロケーションが一致しない場合、BigQuery はエラーを返します。

本書の一般公開データセット bigquery-public-data は US マルチリージョンにあります。この制約があるため、本書の演習用データセットはすべて US ロケーションを既定とします。EU やリージョン単位のロケーションを選んでしまうと、公開データセットとの JOIN や比較演習がロケーション不一致で実行できなくなるためです。

ロケーション: US(同一) データセット クエリジョブ スキャン(成功) ロケーション: US データセット ロケーション: asia-northeast1 クエリジョブ 不一致(エラー)
図 2-2 クエリはデータと同じロケーションでしか実行できない。上段は US 同士で成功、下段は US のデータセットに対して asia-northeast1 でジョブを実行しようとして失敗する。
実務の視点: ロケーションはテーブルの中身やスキーマと違って、あとから直す手段がありません。修正する唯一の方法は「正しいロケーションでデータセットを作り直し、テーブルをコピーまたは再作成する」ことです。プロジェクト立ち上げの初日に、どのロケーションを既定にするかをチームで決めておく価値があります。本書は一般公開データセットとの往復を優先し、US マルチリージョンを一貫して使います。

テーブルの種類

BigQuery の「テーブル」という言葉は、性質の異なる複数の実体を指します。大きく分けると次の 3 グループです。

標準テーブル テーブル クローン スナップショット 外部テーブル BigLake テーブル オブジェクトテーブル ビュー 論理ビュー マテリアライズドビュー
図 2-3 テーブルの 3 グループ。標準テーブルは BigQuery ネイティブのストレージにデータを持ち、外部テーブルは他の場所のデータを参照し、ビューは SQL クエリで定義される論理的なテーブル。
種類データの実体詳しい章
標準テーブルBigQuery ストレージにカラムナ形式で保持本章・第 6 章
テーブルクローン / スナップショットある時点のコピー(差分課金・軽量)第 9 章
外部テーブル / BigLake テーブルCloud Storage 等、BigQuery の外に実体がある第 10 章
論理ビュー / マテリアライズドビューSQL クエリで定義。マテビューのみ結果を保持第 8 章

GoogleSQL のデータ型とスキーマ変更

スキーマは列名・データ型・モードの集合です。指定方法は Google Cloud コンソール、SQL の CREATE TABLE 文、bq CLI(インライン指定または JSON スキーマファイル)、REST API の 4 通りがあります。各列には次の 3 つのモードのいずれかを設定します。

Avro・Parquet・ORC・Firestore/Datastore エクスポートファイルをロードする場合は、自己記述的なフォーマットからスキーマが自動取得されるため、スキーマを手で書く必要はありません。

代表的なデータ型の使い分け

型特徴使いどころ
NUMERIC精度 38・スケール 9 の固定小数点。丸め誤差なし金額・会計計算
BIGNUMERIC精度約 76.8 桁・スケール 38 の固定小数点超高精度が要る金融計算
FLOAT64倍精度の近似浮動小数点。NaN/無限大あり科学技術計算・統計量
DATEタイムゾーン非依存の暦日(0001-01-01〜9999-12-31)誕生日・締め日など「日付だけ」
DATETIMEタイムゾーンなしの日付+時刻(腕時計の表示)ローカルなカレンダー予定
TIMESTAMPタイムゾーンを持たず、絶対時刻を UTC 基準で保持イベントの発生時刻・ログ
STRING / BYTESUnicode 文字列 / 可変長バイナリ。相互変換は UTF-8 経由テキスト全般 / 生バイナリ
JSONキー順序は非保証、配列順序は保持。ネストは最大 500 階層スキーマが可変・半構造化データ
小数を厳密に扱う? NUMERIC FLOAT64 はい いいえ 絶対時刻が必要? TIMESTAMP DATETIME / DATE はい いいえ
図 2-4 小数精度が必要なら NUMERIC 系、近似でよければ FLOAT64。グローバルな絶対時刻を記録するなら TIMESTAMP、時計表示どおりのローカル日時なら DATETIME、日付だけなら DATE を選ぶ。
実務の視点: 金額を FLOAT64 で持つと、丸め誤差が積み上がって合計が 1 円単位でずれることがあります。会計・請求金額は精度 38・スケール 9 の NUMERIC(必要ならスケール 38 の BIGNUMERIC)を既定にし、FLOAT64 は科学技術計算や近似で十分な指標に限定してください。同様に、ログの発生時刻は TIMESTAMP、「毎週月曜 9 時」のようなローカルなスケジュールは DATETIME、と使い分けると、タイムゾーンの変換ミスを構造的に防げます。

スキーマ変更で許可される操作

スキーマ変更は ALTER TABLE 文(または bq CLI・コンソール)で行います。ドキュメントで確認できる範囲を整理すると、次のとおりです。

✕ 逆方向の SET DATA TYPE は不可 INT64 NUMERIC BIGNUMERIC FLOAT64 変換可 変換可 変換可 変換可 変換可
図 2-5 SET DATA TYPE が許可する変換は「制限の少ない型」への一方向だけ。INT64 は NUMERIC・BIGNUMERIC・FLOAT64 のいずれにも直接変換でき、NUMERIC はさらに BIGNUMERIC や FLOAT64 に変換できるが、逆方向(例: BIGNUMERIC → NUMERIC)はできない。

データセット自体のメタデータ(説明・デフォルトのテーブル有効期限・デフォルトのパーティション有効期限・ラベル)は bq update で変更できます。テーブル/データセットの有効期限が切れると自動的に削除されるため、検証用データセットには default_table_expiration_days を短めに設定しておくと片付け忘れを防げます。なお、ストレージ課金モデル(論理/物理)を変更すると反映まで 24 時間かかり、再変更にはさらに 14 日待つ必要があります。

ハンズオン 2-1|US ロケーションで演習用データセットを作る

この章と今後の章で使う演習用データセット handson_ch02 を、US ロケーションに作成します。サンドボックスで完走可・無料枠内(データセット作成自体は課金対象になりません)。

  1. bq CLI でデータセットを作成します。ロケーションは必ず US を指定してください(あとから変更できません)。
bq mk \
  --dataset \
  --location=US \
  --description="第2章 データセット・テーブル・スキーマの演習用" \
  --default_table_expiration=86400 \
  --label=purpose:handson \
  PROJECT_ID:handson_ch02

PROJECT_ID は自分の Google Cloud プロジェクト ID に置き換えてください(gcloud config get-value project で確認できます)。--default_table_expiration は秒単位で、86400 秒 = 1 日です。作成したテーブルは 1 日で自動削除されるため、片付け忘れても安心です。

  1. メタデータを確認します。
bq show --format=prettyjson PROJECT_ID:handson_ch02

期待される出力: "location": "US" と、指定したラベル・説明・defaultTableExpirationMs が JSON に含まれています。

後片付け: このデータセットは本章の後続ハンズオンでも使うため、ここでは削除しません。ハンズオン 2-4 の後片付けでデータセットごと削除します。

ハンズオン 2-2|JSON スキーマファイルでテーブルを作る

JSON スキーマファイルを使い、複数のデータ型を持つテーブルを明示的なスキーマで作成します。サンドボックスで完走可・無料枠内(テーブル作成はスキャンを伴わないため課金対象外)。

  1. スキーマファイル orders_schema.json を作成します。
[
  { "name": "order_id",    "type": "STRING",    "mode": "REQUIRED" },
  { "name": "customer_id", "type": "STRING",    "mode": "NULLABLE" },
  { "name": "amount",      "type": "NUMERIC",   "mode": "NULLABLE" },
  { "name": "created_at",  "type": "TIMESTAMP", "mode": "NULLABLE" }
]
  1. スキーマファイルを指定してテーブルを作成します。
bq mk --table \
  PROJECT_ID:handson_ch02.orders \
  orders_schema.json
  1. INFORMATION_SCHEMA で型が意図どおりに反映されているか確認します。
-- 処理: メタデータ照会のため課金対象外
SELECT column_name, data_type, is_nullable
FROM `PROJECT_ID.handson_ch02.INFORMATION_SCHEMA.COLUMNS`
WHERE table_name = 'orders'
ORDER BY ordinal_position;

期待される出力: order_id が STRING / NO(REQUIRED)、amount が NUMERIC / YES、created_at が TIMESTAMP / YES という 4 行。

後片付け: テーブルはハンズオン 2-3 でスキーマ変更の題材として使うため、ここでは削除しません。

ハンズオン 2-3|スキーマ変更を一通り試す

ハンズオン 2-2 で作った orders テーブルに対して、列追加・列名変更・型変更・列削除を実際に実行し、可否のルールを体で確認します。サンドボックスで完走可・無料枠内(すべて DDL でスキャンを伴わないため課金対象外)。

  1. JSON 型の列を追加します(新規列は常に NULLABLE/REPEATED のみ可)。
ALTER TABLE `PROJECT_ID.handson_ch02.orders`
ADD COLUMN payload JSON;

ALTER TABLE `PROJECT_ID.handson_ch02.orders`
ADD COLUMN memo STRING;
  1. 列名を変更します。
ALTER TABLE `PROJECT_ID.handson_ch02.orders`
RENAME COLUMN customer_id TO client_id;
  1. 型を「制限の少ない方向」へ変更します(NUMERIC → BIGNUMERIC)。
ALTER TABLE `PROJECT_ID.handson_ch02.orders`
ALTER COLUMN amount SET DATA TYPE BIGNUMERIC;
  1. 不要になった列を削除します(メタデータ操作のみで、ストレージは即座には解放されません)。
ALTER TABLE `PROJECT_ID.handson_ch02.orders`
DROP COLUMN memo;
  1. 最終的なスキーマを確認します。
bq show --schema --format=prettyjson PROJECT_ID:handson_ch02.orders

期待される出力: order_id / client_id / amount(BIGNUMERIC)/ created_at / payload(JSON)の 5 列。memo は表示されません。

後片付け: テーブルはハンズオン 2-4 の後片付けでデータセットごと削除します。

ハンズオン 2-4|ロケーション不一致を実際に起こしてみる

US に作った handson_ch02 に対して、わざと別ロケーションでクエリを実行してエラーを確認し、続けて正しいロケーションで一般公開データセットへ小さくクエリします。サンドボックスで完走可・無料枠内(公開データセットへのスキャンは数 MB 程度)。

  1. ロケーションを asia-northeast1 に指定して、US にあるテーブルへクエリしてみます。
bq query --use_legacy_sql=false --location=asia-northeast1 \
  'SELECT COUNT(*) FROM `PROJECT_ID.handson_ch02.orders`'

期待される出力: ジョブのロケーションとデータセットのロケーションが一致しないため、エラーで失敗します。BigQuery は「リクエストに関わるすべてのデータセットのロケーションが、ジョブのロケーションと一致しなければならない」というルールでこれを弾きます。

  1. 今度は正しく US を指定し、一般公開データセットに小さくクエリします。列を絞り、dry run で処理量も確認します。
bq query --use_legacy_sql=false --location=US --dry_run \
  'SELECT word, word_count
   FROM `bigquery-public-data.samples.shakespeare`
   WHERE corpus = "hamlet"
   ORDER BY word_count DESC
   LIMIT 10'

bq query --use_legacy_sql=false --location=US \
  'SELECT word, word_count
   FROM `bigquery-public-data.samples.shakespeare`
   WHERE corpus = "hamlet"
   ORDER BY word_count DESC
   LIMIT 10'

期待される出力: dry run は数 MB 程度の処理量見積もりを返し、実行結果は "the" や "and" など頻出単語が上位に並ぶ 10 行です。bigquery-public-data は US マルチリージョンにあるため、こちらは --location=US のジョブと一致し、正常に実行できます。

後片付け: この章で作成したリソースをすべて削除します。

bq rm -r -f -d PROJECT_ID:handson_ch02

つまずきポイント

ロケーションはあとから直せない: データセットのロケーションを間違えて選んでしまった場合、変更する方法はありません。正しいロケーションでデータセットを作り直し、テーブルをコピーまたは再作成する必要があります。本書は一般公開データセットとの往復を前提に US を既定としているので、最初の作成時によく確認してください。
既存テーブルに REQUIRED 列を追加しようとしてエラーになる: ALTER TABLE ADD COLUMN で追加できる新しい列は NULLABLE または REPEATED だけです。「あとから必須にしたい」場合は、列を NULLABLE で追加してアプリケーション側で検証するか、テーブル自体を作り直す設計にしてください。
DROP COLUMN してもストレージ料金がすぐ減らない: 列の削除はメタデータのみの操作で、実際のストレージが解放されるのは次にテーブルへ書き込みが発生したときです。即座に容量を回収したいなら、SELECT * EXCEPT(dropped_column) でテーブルを上書きするか、Cloud Storage へエクスポートしてから正しいスキーマで再ロードしてください。
型変換の向きを間違える: ALTER COLUMN SET DATA TYPE は「制限の少ない型」への一方向にしか進めません。BIGNUMERIC から NUMERIC のような後戻りはできないため、精度を落としたい場合は新しい列を追加してデータをコピーし、元の列を削除する形で対応します。

理解度チェック

Q1. データセット作成後にロケーションを変更する方法は?

変更する方法はありません。正しいロケーションで新しいデータセットを作成し、テーブルをコピーまたは再作成する必要があります。ロケーションはデータセット作成時に一度だけ確定する値です。

Q2. 金額を格納する列に FLOAT64 ではなく NUMERIC を使うべき理由は?

FLOAT64 は近似浮動小数点のため、加減算を繰り返すと丸め誤差が蓄積します。NUMERIC は精度 38・スケール 9 の固定小数点で小数を厳密に表現できるため、会計・請求のような正確性が必要な金額計算に向いています。

Q3. 既存の標準テーブルに REQUIRED(必須)列を直接追加できますか?

できません。ALTER TABLE ADD COLUMN で追加できる新しい列は NULLABLE または REPEATED だけです。既存データにはその列の値が存在しないため、NOT NULL 制約を満たせないのが理由です。

Q4. ALTER TABLE DROP COLUMN で列を削除した直後、ストレージ課金はすぐに減りますか?

減りません。列の削除はメタデータのみの操作で、実データの解放は次にテーブルへ書き込みが行われるまで保留されます。すぐに容量を回収したい場合は SELECT * EXCEPT での上書きや、エクスポート後の再ロードが必要です。

Q5.(シナリオ)EU リージョンのプロジェクトにある顧客データセットと、US マルチリージョンにある bigquery-public-data の公開データセットを 1 つのクエリで JOIN したいという要望が来た。最も適切な対応は?
A. そのまま両方を参照する JOIN クエリを実行する B. どちらかのデータをコピーし、両方を同じロケーション(例えば US)に揃えてから JOIN する C. クエリジョブの --location フラグを US に設定するだけで解決する D. BigQuery が自動的にクロスリージョンでデータを転送して JOIN してくれる

B。 クエリはデータと同じロケーションでしか実行できないため、ロケーションが異なるデータセットをまたいだ JOIN はそのままでは失敗します(A は誤り)。--location フラグはジョブの実行場所を指定するだけでデータの物理的な位置は変えないため、フラグ変更だけでは解決しません(C は誤り)。BigQuery にそのような自動クロスリージョン転送機能はありません(D は誤り)。データを同一ロケーションに揃えるコピー・再作成が唯一の解決策です。

まとめと次章へ