この記事について
今回はGoogle CloudのBigQueryを触ってみます。
普段は9年ほどPHP/Laravelで実務をやってきた身なので、「MySQLと何が違うのか」「どこでハマるのか」を実体験ベースでまとめます。データ分析基盤やDWHの知識はほぼゼロからのスタートです。
対象読者:
- BigQueryを触ったことがない、名前だけ知っているエンジニア
- MySQLなど行指向DBとの違いを実感で掴みたい人
- 「なんとなく高額請求が怖い」と思っている人(自分がそうでした)
1. 事前準備:Google Cloudアカウント
Google Cloudアカウントと請求先アカウントを用意します。新規登録の場合、無料クレジット($300)が付与されます(付与条件はcloud.google.com/free経由での登録など、公式サイトで要確認)。
1-1. プロジェクトの用意
既存プロジェクトを使い回すか、新規作成するか選べます。今回は新規プロジェクトを作りました。
gcloud init
gcloud initを実行すると、ログイン→プロジェクト選択(または新規作成)の対話フローが始まります。プロジェクトIDは後から変更できないので注意。
1-2. 請求先アカウントの紐付け
新規プロジェクトを作ると、デフォルトでは請求先アカウントが紐付いていません。このままだとBigQuery APIの有効化やクエリ実行で弾かれます。
これはGoogle Cloudの正規の仕様です。コンソールの以下のページから、請求先アカウント(無料クレジットが乗っているもの)を選んで紐付けます。
https://console.cloud.google.com/billing/linkedaccount?project=<プロジェクトID>
新規プロジェクトを作ったら真っ先に請求先を確認する、は鉄則にしたいと思います。
1-3. BigQuery特有の無料枠
BigQueryには以下の無料枠があります(2026年時点、web検索で確認済み):
- クエリ:月1TiBのスキャンまで無料
- ストレージ:月10GiBまで無料
- コンソール上にはクレカ登録不要のサンドボックスモードも用意されている
今回はPython SDK経由での利用なので通常の課金プロジェクトを使いますが、「まずは触ってみたいだけ」という人にはサンドボックスモードも選択肢として紹介しておきます。
2. ローカル環境構築(gcloud CLI + Python SDK)
今回はブラウザのBigQuery Studioではなく、ローカルのPythonからgoogle-cloud-bigquery経由で叩く方針にしました。
2-1. gcloud CLIのインストール
Windows環境の場合、公式サイトからインストーラーを落として実行します。
https://cloud.google.com/sdk/docs/install
インストール後、新しいPowerShellを開いてバージョン確認:
gcloud --version
2-2. 初期セットアップ
gcloud init
アカウントログイン、プロジェクト選択(または新規作成)を対話形式で行います。
2-3. BigQuery APIの有効化
gcloud services enable bigquery.googleapis.com
2-4. ADC(Application Default Credentials)の設定
ここがPython SDKを使う上でのポイントです。gcloud initのログインとは別に、アプリケーションコード用の認証情報を設定する必要があります。
gcloud auth application-default login
ブラウザが開いてログイン→許可すると、ローカルに認証情報ファイルが保存されます(Windowsなら%APPDATA%\gcloud\application_default_credentials.json)。以降、Pythonコード側では認証情報を一切意識する必要がありません。
2-5. Python SDKのインストール
pip install google-cloud-bigquery
2-6. 疎通確認
公開データセット(bigquery-public-data)を使うので、自分でデータを用意する必要は一切ありません。プロジェクトIDだけ自分のものに差し替えればすぐ動きます。
from google.cloud import bigquery
client = bigquery.Client(project="<自分のプロジェクトID>")
query = """
SELECT word, SUM(word_count) AS total_count
FROM `bigquery-public-data.samples.shakespeare`
GROUP BY word
ORDER BY total_count DESC
LIMIT 10
"""
results = client.query(query).result()
for row in results:
print(row.word, row.total_count)
実行結果:
the 25568
I 21028
and 19649
to 17361
of 16438
a 13409
you 12527
my 11291
in 10589
is 8735
シェイクスピア全作品の中で最も使われている単語トップ10が一発で出てきます。SQLの書き味自体はMySQLとほぼ変わりません。
3. MySQLとの比較
普段触っているMySQLとの違いを整理します。
| 項目 | MySQL | BigQuery |
|---|---|---|
| データ構造 | 行指向 | 列指向 |
| インフラ管理 | 必要(自分でサーバー管理) | 不要(サーバーレス) |
| 課金 | サーバー稼働時間 | スキャンしたデータ量 |
| 得意分野 | トランザクション処理 | 大量データの集計・分析 |
| JOIN | 得意 | 可能だが大規模データでは設計に注意 |
一番の違いは課金モデルだと実感しました。MySQLは「サーバーを立てている時間」に対して課金されますが、BigQueryは「クエリが実際にスキャンしたデータ量」に対して課金されます。この違いが次章の話に直結します。
4. SELECT * の危険性を実測する
MySQL脳のままSELECT *を書くと、BigQueryでは思わぬ額を払うことになりかねません。実際に処理量を比較してみます。
4-1. とりあえずSELECT *(実行はせず見積もりだけ)
query_all = """
SELECT * FROM `bigquery-public-data.covid19_open_data.covid19_open_data`
"""
job_config = bigquery.QueryJobConfig(dry_run=True, use_query_cache=False)
job = client.query(query_all, job_config=job_config)
print(f"処理見込み: {job.total_bytes_processed / 1e9:.2f} GB")
実行結果:
処理見込み: 12.21 GB
SELECT *だけで、シェイクスピアのデータセットとは桁が違う12GB超のスキャンが見込まれることが分かりました。実行せずにこの見積もりが事前に分かるのがBigQueryの安心なところです。
4-2. 必要な列だけに絞る
query_narrow = """
SELECT date, country_name, new_confirmed
FROM `bigquery-public-data.covid19_open_data.covid19_open_data`
WHERE country_name = 'Japan'
"""
job_config = bigquery.QueryJobConfig(dry_run=True, use_query_cache=False)
job = client.query(query_narrow, job_config=job_config)
print(f"処理見込み: {job.total_bytes_processed / 1e9:.2f} GB")
実行結果:
処理見込み: 0.53 GB
列を絞るだけで、②の12.21 GBから約96%削減(0.53 GB、元の4.3%程度)されました。これが列指向データベースならではの特性です。行指向のMySQLだとSELECT *とSELECT col1, col2でディスクI/O自体はそこまで変わらない(インデックスが効けば別ですが)ので、この感覚差は新鮮でした。
5. パーティションテーブルでコスト削減を実測する
日付でパーティション分割されたテーブルなら、WHEREで日付を絞るだけでスキャン範囲そのものを大きく減らせます。今回は公式ドキュメントにも例として載っているbigquery-public-data.wikipedia.pageviews_2025(datehour列でパーティション分割済み、Wikipedia全言語・全ページの時間別アクセスログ)を使いました。
5-1. まず日付条件なしで投げてみたら弾かれた
最初、単純に列を絞っただけのクエリを投げてみたところ、実行すらできませんでした。
query_no_filter = """
SELECT datehour, wiki, title, views
FROM `bigquery-public-data.wikipedia.pageviews_2025`
WHERE wiki = 'en'
"""
job_config = bigquery.QueryJobConfig(dry_run=True, use_query_cache=False)
job = client.query(query_no_filter, job_config=job_config)
エラー:
google.api_core.exceptions.BadRequest: 400 POST https://bigquery.googleapis.com/bigquery/v2/projects/<project-id>/jobs?prettyPrint=false:
Cannot query over table 'bigquery-public-data.wikipedia.pageviews_2025' without a filter over column(s) 'datehour' that can be used for partition elimination
このテーブルには**「パーティション列の絞り込み条件が無いとそもそもクエリを実行させない」**という設定(require partition filter)が入っていました。covid19のデータセットでは「絞り込んでも減らないだけ」でしたが、こちらは「絞り込まないとエラーで弾かれる」という、より厳格な安全機構です。誤ってフルスキャンして高額請求、という事故をテーブル側で未然に防ぐ仕組みがある、というのは実務的に重要な発見でした。
5-2. 広い範囲 vs 1日だけで処理量を比較
エラーの指示通り、datehourに条件を追加して比較しました。
# ③相当:2025年全体(広い範囲)
query3 = """
SELECT datehour, wiki, title, views
FROM `bigquery-public-data.wikipedia.pageviews_2025`
WHERE wiki = 'en'
AND datehour BETWEEN TIMESTAMP('2025-01-01') AND TIMESTAMP('2025-12-31')
"""
job_config = bigquery.QueryJobConfig(dry_run=True, use_query_cache=False)
job = client.query(query3, job_config=job_config)
print(f"③ 処理見込み: {job.total_bytes_processed / 1e9:.2f} GB")
# ⑤相当:1日だけに絞る
query5 = """
SELECT datehour, wiki, title, views
FROM `bigquery-public-data.wikipedia.pageviews_2025`
WHERE wiki = 'en'
AND datehour BETWEEN TIMESTAMP('2025-01-01') AND TIMESTAMP('2025-01-02')
"""
job_config = bigquery.QueryJobConfig(dry_run=True, use_query_cache=False)
job = client.query(query5, job_config=job_config)
print(f"⑤ 処理見込み: {job.total_bytes_processed / 1e9:.2f} GB")
実行結果:
③ 処理見込み: 511.43 GB
⑤ 処理見込み: 1.88 GB
期間を「1年分」から「1日分」に絞っただけで、処理量は511.43 GB → 1.88 GBへ約99.6%削減(実に272倍の差)されました。covid19のデータセットで「絞り込んでも意味がなかった」経験をした直後だったので、パーティションが正しく効いた時の削減幅の大きさが余計に印象に残りました。
MySQLでインデックスを貼る感覚に近いですが、BigQueryの場合は「スキャン量=お金」に直結するので、そのテーブルが実際にパーティション分割されているか、パーティション列に条件を入れているかを事前に確認するワンステップが、MySQLのインデックス設計以上にシビアだと実感しました。
3パターンの処理量比較
| クエリ | 内容 | 処理見込み |
|---|---|---|
| ② |
SELECT *(covid19、全列・全期間) |
12.21 GB |
| ③ | wikipedia、2025年全体(列は絞り済み) | 511.43 GB |
| ⑤ | wikipedia、2025年1月1日1日分のみ | 1.88 GB |
②と③は別テーブル・別スケールの数値なので単純比較はできませんが、「同じテーブル内で期間を絞るだけでここまで変わる」という③→⑤の落差こそが、この章で伝えたかったことです。
6. ウィンドウ関数でMySQLとの書き味を比較
集計だけでなく、「グループごとの上位N件」のような処理もSQL1本で書けます。
query_window = """
SELECT corpus, word, word_count
FROM (
SELECT corpus, word, word_count,
ROW_NUMBER() OVER (PARTITION BY corpus ORDER BY word_count DESC) AS rn
FROM `bigquery-public-data.samples.shakespeare`
)
WHERE rn <= 3
ORDER BY corpus, rn
LIMIT 30
"""
results = client.query(query_window).result()
for row in results:
print(row.corpus, row.word, row.word_count)
実行結果(冒頭抜粋、全30行のうち先頭10作品分):
1kinghenryiv the 762
1kinghenryiv and 660
1kinghenryiv I 631
1kinghenryvi the 634
1kinghenryvi of 554
1kinghenryvi and 459
2kinghenryiv the 894
2kinghenryiv and 689
2kinghenryiv I 614
2kinghenryvi the 841
2kinghenryvi and 616
2kinghenryvi I 542
3kinghenryvi the 718
3kinghenryvi and 541
3kinghenryvi to 516
allswellthatendswell I 694
allswellthatendswell the 639
allswellthatendswell and 515
antonyandcleopatra the 680
antonyandcleopatra I 530
antonyandcleopatra and 506
asyoulikeit I 665
asyoulikeit the 633
asyoulikeit and 541
comedyoferrors I 469
comedyoferrors the 399
comedyoferrors OF 355
coriolanus the 942
coriolanus to 593
coriolanus and 575
作品ごとに頻出単語トップ3が綺麗に取れています。面白いのは、全体トップ10(前述)では"the"が1位でしたが、作品によっては"I"が1位になるケースもあること。台詞劇である戯曲の特性が、単語頻度にも表れているようです。
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)はMySQL 8.0以降でも使えるので、書き方自体は違和感なく移植できます。ただ、BigQuery側はこの手のウィンドウ関数を列指向ストレージの強みを活かして高速に処理できる、という点が設計思想として違います。
7. つまずいたポイント
- 請求先アカウントの紐付け忘れ: 新規プロジェクトを作った直後は請求先アカウントが紐付いておらず、API有効化やクエリ実行で弾かれました。新規プロジェクトを作ったら真っ先に確認するべき項目だと学びました。
-
パーティション列の条件なしでクエリが弾かれた:
bigquery-public-data.wikipedia.pageviews_2025は「パーティション列に絞り込み条件がないとそもそも実行させない」設定になっており、BadRequestエラーで実行を止められました。誤フルスキャンをテーブル側で防ぐ仕組みがあると初めて知りました。
8. まとめ
- BigQueryはMySQLと違って列指向、課金はスキャンしたデータ量ベース
-
SELECT *は列指向DBだと相性が悪く、実測でも12.21 GB→0.53 GBと約96%の削減効果があった - パーティション分割されたテーブルなら、期間を絞るだけで処理量を桁違いに減らせる(実測で511.43 GB→1.88 GBの99.6%削減)。テーブルによってはパーティション条件なしのクエリをエラーで弾く安全機構もある
- ウィンドウ関数などSQLの書き味自体はMySQLからの移行障壁は低い
- 公開データセット(
bigquery-public-data)のおかげで、自前のデータ準備なしにすぐ試せる


