- プロジェクト → データセット → テーブル / ビュー / ルーティンという BigQuery のリソースモデル
- ロケーションがなぜ「作成後に変更できない、最重要の決定」なのか、そして本書の演習が US を既定とする理由
- 標準テーブル・外部テーブル・ビュー・マテリアライズドビュー・スナップショットなど、テーブル種類の全体像
- NUMERIC / FLOAT64、TIMESTAMP / DATETIME / DATE など GoogleSQL データ型の使い分け
- スキーマ変更で許可される操作と禁止される操作(列追加・型変更・列名変更・列削除)
前提: サンドボックスで完走可・無料枠内(全ハンズオンの処理量は数十 MB 未満、DML は使用しません)。
リソースモデル — プロジェクト・データセット・テーブル
BigQuery のリソースは 3 層の入れ子で整理されています。一番外側が Google Cloud のプロジェクト、その中にデータセットという「トップレベルのコンテナ」があり、テーブルとビューはこのデータセットに属さなければなりません。データをロードする前に、必ず少なくとも 1 つのデータセットを作成しておく必要があります。
データセットにはテーブル(標準・パーティション・クラスタ化)のほか、ビュー(論理ビュー・マテリアライズドビュー)、ルーティン(ユーザー定義関数・ストアドプロシージャ)を格納できます。リソースを一意に指すための完全修飾名は、GoogleSQL では projectname.datasetname.tablename、bq コマンドラインツールでは projectname:datasetname.tablename という形式を取ります。データセット名はプロジェクト内で一意、テーブル名はデータセット内で一意である必要があります。
ロケーション — 作成後には変更できない意思決定
ロケーションは、データセットを作成するときに必ず指定する項目で、「US」「EU」のようなマルチリージョンと、「asia-northeast1(東京)」のようなリージョンから選びます。マルチリージョンは広い地理的範囲を指しますが、実体としてのデータは単一のリージョンに保存される点に注意してください(マルチリージョンはリージョン冗長性を提供しません)。
最も重要なのは次の 1 点です。データセット作成後、ロケーションは変更できません。そして、クエリはデータと同じロケーションでしか実行できません。BigQuery のドキュメントは「リクエストに関わるすべてのデータセットのロケーションが、ジョブのロケーションと一致しなければならない」と明記しています。ロケーションが一致しない場合、BigQuery はエラーを返します。
本書の一般公開データセット bigquery-public-data は US マルチリージョンにあります。この制約があるため、本書の演習用データセットはすべて US ロケーションを既定とします。EU やリージョン単位のロケーションを選んでしまうと、公開データセットとの JOIN や比較演習がロケーション不一致で実行できなくなるためです。
テーブルの種類
BigQuery の「テーブル」という言葉は、性質の異なる複数の実体を指します。大きく分けると次の 3 グループです。
| 種類 | データの実体 | 詳しい章 |
|---|---|---|
| 標準テーブル | BigQuery ストレージにカラムナ形式で保持 | 本章・第 6 章 |
| テーブルクローン / スナップショット | ある時点のコピー(差分課金・軽量) | 第 9 章 |
| 外部テーブル / BigLake テーブル | Cloud Storage 等、BigQuery の外に実体がある | 第 10 章 |
| 論理ビュー / マテリアライズドビュー | SQL クエリで定義。マテビューのみ結果を保持 | 第 8 章 |
GoogleSQL のデータ型とスキーマ変更
スキーマは列名・データ型・モードの集合です。指定方法は Google Cloud コンソール、SQL の CREATE TABLE 文、bq CLI(インライン指定または JSON スキーマファイル)、REST API の 4 通りがあります。各列には次の 3 つのモードのいずれかを設定します。
- NULLABLE(既定) —
NULLを許可する - REQUIRED —
NULLを許可しない - REPEATED — 値の配列を格納する
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 / BYTES | Unicode 文字列 / 可変長バイナリ。相互変換は UTF-8 経由 | テキスト全般 / 生バイナリ |
JSON | キー順序は非保証、配列順序は保持。ネストは最大 500 階層 | スキーマが可変・半構造化データ |
FLOAT64 で持つと、丸め誤差が積み上がって合計が 1 円単位でずれることがあります。会計・請求金額は精度 38・スケール 9 の NUMERIC(必要ならスケール 38 の BIGNUMERIC)を既定にし、FLOAT64 は科学技術計算や近似で十分な指標に限定してください。同様に、ログの発生時刻は TIMESTAMP、「毎週月曜 9 時」のようなローカルなスケジュールは DATETIME、と使い分けると、タイムゾーンの変換ミスを構造的に防げます。スキーマ変更で許可される操作
スキーマ変更は ALTER TABLE 文(または bq CLI・コンソール)で行います。ドキュメントで確認できる範囲を整理すると、次のとおりです。
- 列の追加:
ALTER TABLE ... ADD COLUMN。新しい列は必ずNULLABLEまたはREPEATEDでなければならず、既存テーブルにREQUIRED列を直接追加することはできません。新しい列は常に末尾に追加され、途中への挿入はできません。 - 列名の変更:
ALTER TABLE ... RENAME COLUMN A TO B。パーティション列・クラスタリング列・主キー/外部キー制約の列・行アクセスポリシーがあるテーブルの列は変更できません。 - 列の削除:
ALTER TABLE ... DROP COLUMN。パーティション列・クラスタリング列・ネストされた RECORD の内側のフィールドなどは削除できません。これはメタデータのみの操作で、ストレージは即座には解放されません。解放は次にテーブルへ書き込みが行われたとき(DML やバックグラウンドの最適化)です。 - 型の変更:
ALTER TABLE ... ALTER COLUMN ... SET DATA TYPE。ただし「制限の少ない型」への一方向にしか変換できません。 - NOT NULL 制約の緩和:
ALTER TABLE ... ALTER COLUMN ... DROP NOT NULL。
データセット自体のメタデータ(説明・デフォルトのテーブル有効期限・デフォルトのパーティション有効期限・ラベル)は bq update で変更できます。テーブル/データセットの有効期限が切れると自動的に削除されるため、検証用データセットには default_table_expiration_days を短めに設定しておくと片付け忘れを防げます。なお、ストレージ課金モデル(論理/物理)を変更すると反映まで 24 時間かかり、再変更にはさらに 14 日待つ必要があります。
この章と今後の章で使う演習用データセット handson_ch02 を、US ロケーションに作成します。サンドボックスで完走可・無料枠内(データセット作成自体は課金対象になりません)。
- 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 日で自動削除されるため、片付け忘れても安心です。
- メタデータを確認します。
bq show --format=prettyjson PROJECT_ID:handson_ch02
期待される出力: "location": "US" と、指定したラベル・説明・defaultTableExpirationMs が JSON に含まれています。
後片付け: このデータセットは本章の後続ハンズオンでも使うため、ここでは削除しません。ハンズオン 2-4 の後片付けでデータセットごと削除します。
JSON スキーマファイルを使い、複数のデータ型を持つテーブルを明示的なスキーマで作成します。サンドボックスで完走可・無料枠内(テーブル作成はスキャンを伴わないため課金対象外)。
- スキーマファイル
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" }
]
- スキーマファイルを指定してテーブルを作成します。
bq mk --table \
PROJECT_ID:handson_ch02.orders \
orders_schema.json
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-2 で作った orders テーブルに対して、列追加・列名変更・型変更・列削除を実際に実行し、可否のルールを体で確認します。サンドボックスで完走可・無料枠内(すべて DDL でスキャンを伴わないため課金対象外)。
- 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;
- 列名を変更します。
ALTER TABLE `PROJECT_ID.handson_ch02.orders`
RENAME COLUMN customer_id TO client_id;
- 型を「制限の少ない方向」へ変更します(
NUMERIC→BIGNUMERIC)。
ALTER TABLE `PROJECT_ID.handson_ch02.orders`
ALTER COLUMN amount SET DATA TYPE BIGNUMERIC;
- 不要になった列を削除します(メタデータ操作のみで、ストレージは即座には解放されません)。
ALTER TABLE `PROJECT_ID.handson_ch02.orders`
DROP COLUMN memo;
- 最終的なスキーマを確認します。
bq show --schema --format=prettyjson PROJECT_ID:handson_ch02.orders
期待される出力: order_id / client_id / amount(BIGNUMERIC)/ created_at / payload(JSON)の 5 列。memo は表示されません。
後片付け: テーブルはハンズオン 2-4 の後片付けでデータセットごと削除します。
US に作った handson_ch02 に対して、わざと別ロケーションでクエリを実行してエラーを確認し、続けて正しいロケーションで一般公開データセットへ小さくクエリします。サンドボックスで完走可・無料枠内(公開データセットへのスキャンは数 MB 程度)。
- ロケーションを
asia-northeast1に指定して、US にあるテーブルへクエリしてみます。
bq query --use_legacy_sql=false --location=asia-northeast1 \
'SELECT COUNT(*) FROM `PROJECT_ID.handson_ch02.orders`'
期待される出力: ジョブのロケーションとデータセットのロケーションが一致しないため、エラーで失敗します。BigQuery は「リクエストに関わるすべてのデータセットのロケーションが、ジョブのロケーションと一致しなければならない」というルールでこれを弾きます。
- 今度は正しく
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
つまずきポイント
ALTER TABLE ADD COLUMN で追加できる新しい列は NULLABLE または REPEATED だけです。「あとから必須にしたい」場合は、列を NULLABLE で追加してアプリケーション側で検証するか、テーブル自体を作り直す設計にしてください。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 は誤り)。データを同一ロケーションに揃えるコピー・再作成が唯一の解決策です。
まとめと次章へ
- BigQuery のリソースはプロジェクト → データセット → テーブル / ビュー / ルーティンの階層で整理される
- ロケーションはデータセット作成時に一度だけ決まり、あとから変更できない。クエリはデータと同じロケーションでしか実行できないため、本書は US を既定にする
- テーブルは標準テーブル・外部テーブル・ビューの 3 グループに大別され、それぞれの中にさらに種類がある
- 金額には
NUMERIC、絶対時刻にはTIMESTAMP、ローカルなカレンダー表示にはDATETIME/DATEを使う - スキーマ変更は「列の追加(末尾のみ・
NULLABLE/REPEATEDのみ)」「列名変更」「制限の少ない方向への型変更」「列削除(メタデータのみ・ストレージは即時解放されない)」というルールの範囲で行う