BigQueryのワイルドカードテーブルで「スキーマ推論」によりNULLが発生するメカニズム
Photo by Patti Black on Unsplash
ウォンテッドリーでデータサイエンティストをしている右手です。
BigQueryにおいて、ログデータを log_20250101 のような日付別テーブル(シャーディング)で管理するケースは多く見られます。こうしたテーブル群を扱う際に必須となるのが「ワイルドカードテーブル」と「擬似列」です。
本記事では、これらの基本的な機能をおさらいしつつ、その仕様の裏側にある「スキーマ推論」の挙動と、実務で発生しやすいトラブルについて解説します。
目次
ワイルドカードテーブルとは
擬似列(Pseudo Column)とは
遭遇する問題:特定のカラムがすべてNULLになる
原因:BigQueryのクエリ処理順序
解決策とベストプラクティス
まとめ
ワイルドカードテーブルとは
ワイルドカードテーブルは、同一データセット内に存在する共通のプレフィックス(接頭辞)を持つ複数のテーブルを、あたかも1つの巨大なテーブルであるかのようにまとめてクエリできる機能です。
SELECT *
FROM `tmp.log_*`
このクエリにより、BigQueryは tmp データセット内の log_ で始まるすべてのテーブルをスキャン対象として認識します。
擬似列(Pseudo Column)とは
ワイルドカードテーブルとセットで使用されるのが _TABLE_SUFFIX という擬似列です。これはテーブル自体には保存されていないメタデータですが、クエリ内で通常のカラムと同様に参照可能です。
主に WHERE 句でスキャン対象を絞り込むために使用されます。
SELECT
*
FROM
`tmp.log_*`
WHERE
_table_suffix BETWEEN '20250101' AND '20250103'このフィルタリングにより、スキャン料金を抑えつつ必要な期間のデータのみを抽出することが可能です。
遭遇する問題:特定のカラムがすべてNULLになる
これらは便利な機能ですが、少し落とし穴があります。 運用上の理由で、「カラム構成が異なるテーブル」がワイルドカードの範囲内に混在した場合です。
例:
log_20250101(正規のテーブル。カラムAを持つ)log_20250101_b(log_20250101の後に作られたイレギュラーなテーブル。カラムBを持つ)
CREATE TABLE tmp.log_20250101 (
id STRING,
column_a STRING
);
CREATE TABLE tmp.log_20250101_b (
id STRING,
column_b STRING
);
INSERT INTO tmp.log_20250101 (id, column_a)
VALUES
('100', 'val_a_100'),
('200', 'val_a_200'),
('300', 'val_a_300');
INSERT INTO tmp.log_20250101_b (id, column_b)
VALUES
('100', 'val_b_100'),
('200', 'val_b_200'),
('400', 'val_b_400');
この状態で、log_20250101のみを取得する様にクエリを記述します。
select
*
from
`tmp.log_*`
where
_table_suffix = "20250101"
一見、このクエリでは log_20250101 のデータ(カラムA)を取得できると思われます。 しかし、実際にはカラムAではなくカラムBが出力され、すべてNULLになっています。
id, column_b
100, null
200, null
300, nullこれはなぜでしょうか?
原因:BigQueryのクエリ処理順序
この挙動は、BigQueryがクエリを解釈する順序に起因します。
- スキーマ推論(Schema Inference)
まず、log_*にマッチする全テーブルを確認し、クエリ全体で使用するスキーマを決定します。 この際、「最も作成日時が新しいテーブル」のスキーマが採用されます。(上記の例では、最新の_bテーブルのスキーマ) - フィルタリング
その後に、WHERE _table_suffix = ...の絞り込みが適用されます。
結果として、正規テーブルを読み込んでいるにも関わらず、適用されるスキーマがイレギュラーなもの(カラム B のみ)となり、実データとの不一致部分が NULL で埋められてしまいます。 「フィルタリングする前にスキーマが決まってしまう」という点が非常に重要です。
なお、この仕様は、公式ドキュメントにも記載されています
解決策とベストプラクティス
この問題を回避するための推奨策は以下の通りです。
- パーティション分割テーブルへの移行
日付ごとにテーブルを分割する運用(シャーディング)は、現在では推奨されません。単一テーブル内でのパーティション管理に移行することで、スキーマが統一され、本質的な解決となります。 - 命名規則の変更(プレフィックスを変える)
テストデータや再処理データを作成する場合、サフィックス(末尾)ではなくプレフィックス(先頭)を変更します。
- NG:
log_20250101_retry(log_*に巻き込まれる) - OK:
retry_log_20250101(log_*の対象外)
ワイルドカードテーブルを利用する際は、暗黙的なスキーマ推論の仕様を考慮した設計が必要です。
まとめ
BigQueryのワイルドカードテーブルには、マッチするテーブル群の中で「最も新しいテーブルのスキーマ」を全体の正解として採用するという仕様があります。そのため、_TABLE_SUFFIX で対象を絞り込んだとしても、その特定のテーブルのスキーマが必ずしも使われるわけではありません。
スキーマが変化する可能性がある運用において、こうした暗黙の挙動に依存するのはリスクを伴います。仕様を正しく理解した上で、パーティション分割テーブルへの移行や、プレフィックスによる厳格なテーブル管理など、安全な設計を選択することが大規模ログ分析基盤の運用では何より重要です。