0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BigQuery の LOAD DATA INTO に裏切られた日

Last updated at Posted at 2025-11-11

なぜ LOAD DATA INTO は失敗するのに、Python の Load Job は成功するのか

背景

Google Cloud Storage(GCS)に置いた CSV を BigQuery にロードしたいとき、
BigQuery UI、bq load コマンドだと普通に成功するのに、
SQL の LOAD DATA INTO を使うと突然こんなエラーが出ることになった。

Invalid schema update. Field xxxxx has changed type from STRING to INTEGER

もしくは、パーティション付きテーブルに対してこう。

Cannot replace a table with a different partitioning spec.

「え、UI からは成功したのに、SQL だとエラー?」
そんな体験をした人、結構いるはずです。

現場で発生した問題例

以前、Load 処理を SQL で再現しようとした際、
TRUNCATEINSERT INTO の手順で全量入れ替えを行っていました。

一見同じ「上書き」に見えますが、
TRUNCATE は実際にテーブルのデータを削除してから INSERT を実行するため、
その間に別のワークフローやビューが同じテーブルを参照すると、一時的に空データを返す状態 になります。

この「空白期間」が原因で、一部の処理でデータ欠損 が発生しました。

bq load / Load Job API の WRITE_TRUNCATE はこの問題を解決します。
BigQuery が内部的に一時テーブルを使い、ロード完了後に メタデータレベルで一括スワップ するため、
ロード中にテーブルが空になることはありません。

SQL レイヤーの制約

LOAD DATA INTO は BigQuery の SQL エンジン が直接動かす命令です。
つまり、クエリエンジンの中で “完全にスキーマが一致していること” が前提。

特徴
機能 挙動
スキーマ自動推定 ❌ なし(既存テーブルに完全一致が必要)
型不一致 ❌ エラーになる
分区・クラスタリング ❌ OVERWRITE は DROP + CREATE 扱いで失敗しやすい
スキーマ変更 ❌ 許されない
処理目的 データクエリ向け(厳格・安全)

BigQuery は「SQL はデータ定義を壊さない」という設計思想を持っています。
だから、CSV の列が INT に見えた瞬間、「STRING 型の列を勝手に変えようとしてる!」と怒られるわけです。

API レイヤー(Load Job)の挙動

一方で、bq load コマンド、Python クライアントなどは
BigQuery Load Job API を呼び出しています。
こちらは「データをロードするための専用エンジン」。

特徴

機能 挙動
スキーマ自動推定 (autodetect=True) ✅ あり
型不一致 ✅ 自動変換 or スキップ
分区・クラスタリング ✅ 保持したまま上書き可
書き込みモード (WRITE_TRUNCATE, WRITE_APPEND) ✅ 柔軟に設定可能
処理目的 データ投入・ETL 向け(柔軟・現実的)

つまり テーブルを削除せず、中身だけ空にして再ロード。
分区情報もクラスタリングもそのまま残ります。

挙動比較まとめ

項目 SQL (LOAD DATA INTO) API (bq load, Python)
スキーマ自動推定 ×
型不一致許容 ×
分区上書き ×
クラスタリング保持 ×
実行層 Query Engine Load Job API
主な用途 クエリ・定義操作 データロード・ETL

Python でのロード例

from google.cloud import bigquery
client = bigquery.Client(project="xxxxxxx")
job_config = bigquery.LoadJobConfig(
    source_format=bigquery.SourceFormat.CSV,
    field_delimiter=",",
    skip_leading_rows=1,
    quote_character='"',
    allow_quoted_newlines=False,
    max_bad_records=0,
    ignore_unknown_values=False,
    autodetect=True,
    write_disposition="WRITE_TRUNCATE",  # 「上書き」モード
)

uri = "gs://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*.csv"
table_id = "xxxxxxx.xxxxxxx.xxxxxxxx"

load_job = client.load_table_from_uri(uri, table_id, job_config=job_config)
load_job.result()

print(f"Loaded {load_job.output_rows} rows to {table_id}.")

これで、スキーマの自動検出もされ、分区設定も壊れません。

まとめ

  • LOAD DATA INTO は SQL 層。スキーマ厳格・安全重視。
  • bq load / Python は API 層。ETL 向け・柔軟性重視。
  • 同じ「ロード」でも目的と制約が全く違う。
  • データパイプラインで安定してロードしたいなら、API(Python / CLI)を使うのが正解。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?