はじめに
BigQueryといえばデータウェアハウス製品ですが、SQLの中で生成AIを直接使えるようになっています。
今回は2026-02-04時点で利用できる全ての生成AI関数を使ってみました。
使うのは簡単なのでぜひ試してみてください.
準備
AI.GENERATE_TEXT系の関数で利用するモデルを作っておきます。
CREATE MODEL `playground.gemini_model`
REMOTE WITH CONNECTION `us.vertex_ai_cocnnection`
OPTIONS (ENDPOINT = 'gemini-2.5-flash');
汎用①
AI.GENERATE
汎用的な関数です。要約や翻訳、抽出など様々なタスクができそうです。
以下の例でもまずまずうまくいっています。
ENDPOINT引数でモデル名を指定できます。デフォルトでは最新の安定版Geminiが選択されます。先述の準備で作成したモデルは使用せずに以下の記述だけで利用できます。
戻り値はresultが利用したいものになりますが、full_responseフィールドにscoreやtoken_countなどの情報を含むjsonが入っています。
SELECT
date,
umaban,
umaname,
comment,
AI.GENERATE(
CONCAT(
"競馬新聞用に4文字で表現してください。結果は4文字だけにしてください: ",
comment)).result AS output
FROM `playground.race_comments`
ORDER BY date DESC, umaban
output_schema引数
結果のスキーマを指定できます。
帳票から項目を抽出するようなユースケースに便利そうです。
SELECT
AI.GENERATE(
CONCAT(
"内から何頭目を通ったか(uchi_count)、直線入り口で前からどれくらいの割合の位置にいたか(position_ratio)、結果がどれくらいの割合か(result_ratio): ",
comment),
output_schema =>
'uchi_count INT64, position_ratio FLOAT64, result_ratio FLOAT64')
FROM `playground.race_comments`
AI.GENERATE_BOOL, AI.GENERATE_DOUBLE, AI.GENERATE_INT
GENERATEの戻り値の型を指定する関数です。GENERATE + output_schema引数で同じことができそうですが、よりシンプルに書けます。
SELECT
*,
AI.GENERATE_BOOL(
(
"このコメントの馬は馬券になったでしょうか: ",
comment)).result
FROM `playground.race_comments`
汎用②
AI.GENERATE_TEXT
AI.GENERATEと似ていますが、いくつか違いがあります。
- 第一引数でリモートモデルを指定する必要があります。AI.GENERATEではGeminiモデルのみ選択できますが、AI.GENERATE_TEXTはAnthropic Claudeなどのパートナーモデルやオープンモデルなども使用することができます
- 関数の入力がテーブル、出力もテーブルです
関数の第一引数でモデル、第二引数でテーブルを渡しています。テーブルにはpromptという列を持たせる必要があります。
SELECT *
FROM
AI.GENERATE_TEXT(
MODEL `playground.gemini_model`,
(
SELECT
*,
(
'競馬新聞用に4文字で表現してください。結果は4文字だけにしてください: ',
comment) AS prompt
FROM `playground.race_comments`
));
AI.GENERATE_TABLE
GENERATEでoutput_schemaを複数指定するのと同様に、テキストや画像から複数項目の抽出などの用途に利用するもののように見えました。第一引数・第二引数はAI.GENERATE_TEXTと同じで第三引数は生成するテーブルのスキーマです。
SELECT *
FROM
AI.GENERATE_TABLE(
MODEL `playground.gemini_model`,
(
SELECT
CONCAT(
"内から何頭目を通ったか(uchi_count)、直線入り口で前からどれくらいの割合の位置にいたか(position_ratio)、結果がどれくらいの割合か(result_ratio): ",
comment) AS prompt
FROM `playground.race_comments`
WHERE date = "2025-12-28"
),
STRUCT(
"uchi_count INT64, position_ratio FLOAT64, result_ratio FLOAT64"
AS output_schema,
8192 AS max_output_tokens));
マネージド
AI.IF
BOOL値のみを返すシンプルな関数です。
AI.GENERATE_BOOLはfull_responseフィールドからscoreなどが利用できますが、AI.IFは完全にBOOL値のみです。
WHEREやJOINの条件として軽量に使うならこちらを利用するべきかもしれません。
SELECT
*
FROM `playground.race_comments`
WHERE
date = "2025-12-28"
AND AI.IF(("次走期待できそうな内容だったか: ", comment))
AI.SCORE
プロンプトに基づいて評価し、FLOAT64型のスコアを返します。
BigQueryは、プロンプトを書き換えて結果の一貫性と品質を向上させているそうです。
レビューのネガティブ上位を抽出、求人にマッチする履歴書の上位を抽出などが例として挙げられていました。
(ちなみに以下の例では成績の良かった馬に加えて進路がなくなった馬を上位にスコアリングしていました。)
SELECT
*,
AI.SCORE(("次走期待できそうな内容だったか: ", comment)) AS score
FROM `playground.race_comments`
WHERE
date = "2025-12-28"
ORDER BY date DESC, score desc
AI.CLASSIFY
名前の通り指定したカテゴリに分類します。
レビューの感情分類や商品のカテゴリ分類、サポートチケットのトピック分類などがユースケースとして挙げられていました。
SELECT
*,
AI.CLASSIFY(("脚質は: ", comment), categories => ['逃', '先', '差', '追']) AS category
FROM `playground.race_comments`
エンべディング生成とセマンティック検索
AI.EMBED
テキストデータや画像データから埋め込みベクトルを生成することができます。
セマンティック検索やレコメンド、クラスタリング、外れ値検出などに利用できます。
SELECT
*,
AI.EMBED(comment, endpoint => 'text-embedding-005')
FROM `playground.race_comments`
あらかじめテーブルに設定しておくとエンべディングしてくれる自律型エンべディング生成も便利です。
以下の記事でも確認しましたが、短い日本語をそのまま利用すると使いづらいかもしれません。
AI.SIMILARITY
複数の入力の類似度をスコアにする関数です。
セマンティック検索やレコメンデーションがユースケースに挙げられています。
内部的にはAI.EMBEDが使われているようで、短い日本語に対してはうまく表現しきれていなさそうでした。
SELECT
*,
AI.SIMILARITY("後方から差し切ってファンの期待に応えた。", comment, endpoint => 'text-embedding-005')
FROM `playground.race_comments`
AI.SEARCH
自律型エンべディング生成が有効なテーブルを引数に渡してセマンティック検索を行う関数です。こちらの記事で試しました。
SELECT base.* EXCEPT (comment_embedding), distance
FROM
AI.SEARCH(
TABLE playground.race_comments,
'comment',
"Sample search comment.",
top_k => -1)
ORDER BY distance
AI.GENERATE_EMBEDDING
テーブルを引数にとるタイプの埋め込みベクトルを生成する関数です。
引数のテーブルはcontent列を持つ必要があります。
第一引数でテキスト埋め込みのモデルを指定します。
SELECT *
FROM
AI.GENERATE_EMBEDDING(
MODEL `playground.text_embedding`,
(
SELECT
* except(comment),
comment as content,
FROM `playground.race_comments`
)
);
予測と異常検出
内容的に生成AI関数とは異なりそうですがAI関数なので簡単に触れておきます。
BigQuery ML組み込みのTimesFMモデルを使って時系列予測・評価をしたり、異常値検出ができます。
citibikeデータセットのAI.FORECASTの例を試してみました。
WITH
citibike_trips AS (
SELECT EXTRACT(DATE FROM starttime) AS date, usertype, COUNT(*) AS num_trips
FROM `bigquery-public-data.new_york.citibike_trips`
GROUP BY date, usertype
)
SELECT *, date(forecast_timestamp) as date
FROM
AI.FORECAST(
TABLE citibike_trips,
data_col => 'num_trips',
timestamp_col => 'date',
id_cols => ['usertype'],
horizon => 30);
入力データと出力データは以下のようなイメージです。Customerのusertypeにしぼってプロットしています。
土日に増えて平日に減る特徴をとらえた予測になっています。
GCSバケットのファイルを入力にする
Cloud Storage外部テーブルを作成すれば、Cloud Storageに置いてある画像を入力としてクエリすることができます。OBJ.GET_ACCESS_URL関数を使います。
CREATE OR REPLACE EXTERNAL TABLE sample_images.paddock_images
WITH CONNECTION
DEFAULT OPTIONS (
object_metadata = 'SIMPLE',
uris = ['gs://sample_gcs_images/*.png']);
SELECT
uri,
STRING(OBJ.GET_ACCESS_URL(ref,'r').access_urls.read_url) AS signed_url,
AI.GENERATE(("適当なパドックコメントを作成してください。馬番、番号は含めずに馬体や毛艶などに注目してください。", OBJ.GET_ACCESS_URL(ref, 'r')))
FROM `sample_images.paddock_images`
コンソールで画像を確認しながら作業できます。(入力画像は適当なプロンプトで生成しました。)

コストを抑えるためのベストプラクティス
生成AI関数は基本的にGeminiなどのモデルへ呼び出しを行いVertex AIの料金が発生します。大量のデータに対して呼び出してコストが過剰となってしまう事故も起こるかもしれません。
以下の記事ではLIMITで件数を絞ったものをAI関数に渡すよりも、事前に対象のデータのみのテーブルを作成してからAI関数を利用するのがベストプラクティスだと言及されていました。
終わりに
はじめてBigQueryMLに触れたとき、ここでMLやるのか...!と衝撃を受けましたが、今回もそれに近い感覚で、ここで生成AI使えるのか!と感心しました。
パワフルなBigQueryなのでコストの事故だけは気をつけたいですね。
今回利用したデータ
以下のクエリで作成したテーブルを利用しました。
有馬記念のテキストコメントのデータです。
CREATE TABLE playground.race_comments(
date DATE,
place_name STRING,
racenum INT64,
umaban INT64,
umaname STRING,
comment STRING
);
INSERT INTO playground.race_comments (date, place_name, racenum, umaban, umaname, comment) VALUES
("2023-12-24", "中山", 11, 1, "ソールオリエンス", "後方からの競馬。直線では馬群を捌き切れず、進路が狭くなりジリジリとした脚色だった。"),
("2023-12-24", "中山", 11, 2, "シャフリヤール", "中団の内を馬なりで追走。直線では急坂で差を詰めたが5着まで。"),
("2023-12-24", "中山", 11, 3, "ホウオウエミーズ", "後方待機。直線大外から差を詰めるも前とは距離があった。"),
("2023-12-24", "中山", 11, 4, "タイトルホルダー", "押して先手を主張。1000m1分0秒4のペースで引き離した単騎逃げをした。直線粘るも3着まで。"),
("2023-12-24", "中山", 11, 5, "ドウデュース", "出遅れて後方。3, 4コーナーで外を通って他馬をごぼう抜き。直線でも伸びて差し切った。千両役者ここにあり。"),
("2023-12-24", "中山", 11, 6, "ディープボンド", "中団後方。ポジションを上げることができずに後方のまま。"),
("2023-12-24", "中山", 11, 7, "アイアンバローズ", "3コーナーあたりからついていくことができず後退。"),
("2023-12-24", "中山", 11, 8, "ライラック", "無理せず後方で脚を溜めた。大外を回った直線では伸びきれず."),
("2023-12-24", "中山", 11, 9, "ヒートオンビート", "中団の外を進行。直線入口で伸び脚目立たず少し挟まれた。"),
("2023-12-24", "中山", 11, 10, "ジャスティンパレス", "出負けして最後方から。4コーナーで大外を回し、直線では追い上げるも届かなかった。"),
("2023-12-24", "中山", 11, 11, "ハーパー", "好スタートを決め、離れた先団の好位につけたが、直線では脚色わるく後退。"),
("2023-12-24", "中山", 11, 12, "ウインマリリン", "中団前目を追走。4コーナーから脚色鈍く、前との差は詰まらなかった。"),
("2023-12-24", "中山", 11, 13, "タスティエーラ", "中団から直線入口で外目に持ち出すも、直線で挟まれてブレーキ。そのあと差を詰めているが6着まで。"),
("2023-12-24", "中山", 11, 14, "プラダリア", "押して先頭とは離れた3番手。直線入り口で後退していった。"),
("2023-12-24", "中山", 11, 15, "スルーセブンシーズ", "出遅れて後方から。徐々にポジションを上げた、直線では伸びず"),
("2023-12-24", "中山", 11, 16, "スターズオンアース", "一番いいスタートから押してうちの2番手を確保。3コーナー入り口で内ラチと軽く接触するも直線では逃げ馬を捉えて前に出た。"),
("2024-12-22", "中山", 11, 1, "ダノンデサイル", "最内枠から押して先頭へ。1000m1分2秒9で逃げた。徐々にペースが上がって最後は一杯となり差し馬に交わされた。"),
("2024-12-22", "中山", 11, 2, "ドウデュース", "右前肢跛行のため出走取消。"),
("2024-12-22", "中山", 11, 3, "アーバンシック", "出遅れて中団の内。ロスなく運んだが、最後は弾けなかった。"),
("2024-12-22", "中山", 11, 4, "ブローザホーン", "出足悪く後方。4コーナーで押しても進まず。"),
("2024-12-22", "中山", 11, 5, "ベラジオオペラ", "好スタートから2番手で折り合った。直線では前で粘ったが、最後は苦しくなった。"),
("2024-12-22", "中山", 11, 6, "ローシャムパーク", "すこし出遅れて後方。前半はペース遅くかかっていた。直線外に出したが弾けるほどではなくジリジリ。"),
("2024-12-22", "中山", 11, 7, "スターズオンアース", "内の3〜5番手を確保するも3, 4コーナーあたりでずるずる後退。"),
("2024-12-22", "中山", 11, 8, "レガレイラ", "スタートはあまり良くなかったが中団前にリカバリー。4コーナーではスムーズに進路が開き、直線では叩き合いを制した。"),
("2024-12-22", "中山", 11, 9, "ディープボンド", "押して3番手を確保。4コーナーでペースについていけず徐々に下がってしまった。"),
("2024-12-22", "中山", 11, 10, "プログノーシス", "出遅れて最後方。直線は進路開くまでに時間がかかりあまり伸びなかった。"),
("2024-12-22", "中山", 11, 11, "ジャスティンパレス", "二の脚きかず中団馬群。直線では最内から伸びるも前を捉えるまではできなかった。"),
("2024-12-22", "中山", 11, 12, "シュトルーヴェ", "後方の外。終始外を回り直線では伸び切れなかった。"),
("2024-12-22", "中山", 11, 13, "スタニングローズ", "中団前目の外、4コーナーで上がっていけず内へ。直線でもあまり伸びなかった。"),
("2024-12-22", "中山", 11, 14, "ダノンベルーガ", "出遅れて最後尾。早めに追い付けたが、道中は進まず終わった。"),
("2024-12-22", "中山", 11, 15, "ハヤヤッコ", "1周目スタンド前から掛かり気味に外から上がっていった。4コーナーでついていけず後退。"),
("2024-12-22", "中山", 11, 16, "シャフリヤール", "中団で脚をため。徐々に上がっていき4コーナーで外から一気に進出。直線差し切るかに見えたが叩き合い鼻差及ばず。ダービー馬の意地を見せた。"),
("2025-12-28", "中山", 11, 1, "エキサイトバイオ", "中団最内に位置。3コーナーから押して進出するが直線入口で進路が狭く、抜け出す足もなかった。"),
("2025-12-28", "中山", 11, 2, "シンエンペラー", "中団最内の前目につけていたが3コーナーでポジションが下がってしまい直線も窮屈になった。"),
("2025-12-28", "中山", 11, 3, "ジャスティンパレス", "最後方から3コーナーで大外からジリジリと進出するが、直線バテた馬をかわす程度だった。"),
("2025-12-28", "中山", 11, 4, "ミュージアムマイル", "中団後方待機。3コーナーから徐々に進出、直線しっかり伸びて差し切った。"),
("2025-12-28", "中山", 11, 5, "レガレイラ", "やや出遅れて後方。3コーナーで勝ち馬といっしょに上がっていった。直線では馬群を捌きながら他馬を交わすも4着まで。"),
("2025-12-28", "中山", 11, 6, "メイショウタバル", "まずまずのスタートも外の馬の勢いがよく4番手。外を上がっていき2周目で先頭に立つが4コーナーからずるずる下がっていった。"),
("2025-12-28", "中山", 11, 7, "サンライズジパング", "ポジションを取れず後方を追走。直線入口で最後方から外を追い上げて見せ場を作った。"),
("2025-12-28", "中山", 11, 8, "シュヴァリエローズ", "後方グループ。馬群の中から追い上げようとするが、進路もなかった。"),
("2025-12-28", "中山", 11, 9, "ダノンデサイル", "中団やや後方。4コーナーから徐々に上がって直線は外に出しやや内にもたれながら伸びるも前をかわせず。"),
("2025-12-28", "中山", 11, 10, "コスモキュランダ", "出脚良く2, 3番手。直線入口で先頭、最後は勝ち馬に1/2馬身交わされたがよく粘った。大健闘。"),
("2025-12-28", "中山", 11, 11, "ミステリーウェイ", "好スタートから先手をとるが単騎逃げとはいかず。2周目で交わされ、4コーナーでズルズル後退。"),
("2025-12-28", "中山", 11, 12, "マイネルエンペラー", "中団外。3コーナーで先団に取りつくが、直線ではのびず。"),
("2025-12-28", "中山", 11, 13, "アドマイヤテラ", "少し出遅れたが外を上がっていき2周目で好位の内へ。直線に向いたとき内の4, 5番手につけていたが伸びなかった。"),
("2025-12-28", "中山", 11, 14, "アラタ", "最後方の内。終始内を回ったが直線で進路狭く、伸びもなかった。"),
("2025-12-28", "中山", 11, 15, "エルトンバローズ", "好スタートから中団の内に収まった。4コーナーで馬群を割るも直線では弾けなかった。"),
("2025-12-28", "中山", 11, 16, "タスティエーラ", "好スタートから好位。4コーナーから上がっていき見せ場を作るも伸びきれなかった。")
参考


