Text-to-SQLを業務で使う設計|デモは動くのに本番で当たらない理由

「先月の関東エリアの解約率を出して」と日本語で書くと、SQLが生成されて表が返ってくる。Text-to-SQLのデモは、たいてい気持ちよく動きます。ところが同じ仕組みを本番の社内DBに向けた瞬間、様子が変わります。テーブルが数百、列が数千あり、名前はt_urg_mst_2のような略号で、退会日は3つのテーブルに別々の定義で入っている。生成されたSQLはエラーなく通るのに、返ってきた数字が経理の資料と合わない。この記事では、Text-to-SQLを「たまに当たる便利ツール」から「業務で使える仕組み」に持っていくための設計を、着手する順序として整理します。
デモと本番の差はどこにあるのか
ベンチマークの世代交代が語っていること
Text-to-SQLの標準ベンチマークだったSpider 1.0は、10,181件の質問と200個のデータベース(138ドメイン)から成ります。公式サイトのリーダーボードでは、Execution with Values(値を含めた実行精度)の首位が91.2%(MiniSeek、2023年11月)に達し、2024年2月5日をもって投稿受付を終了しています。数字だけ見れば「解けた」ように見えます。
その後継として公開されたSpider 2.0は、様相がまったく違います。公式サイトによれば、632件の課題は実際の企業データ活用から採られており、データベースは1,000列を超えることも珍しくなく、BigQueryやSnowflakeといった複数のシステム・方言にまたがります。100行を超えるSQLを複数書く必要がある課題も含まれます。そして公式サイトは、o1-previewでもSpider 2.0の課題を17.1%しか解けず、GPT-4oに至っては10.1%(Spider 1.0では86.6%)だと明記しています。
この落差が、そのまま「デモは動くのに本番で当たらない」の正体です。難しくなったのはSQLの構文ではありません。難しくなったのは、どのテーブルのどの列が答えに関係するかを特定すること、方言ごとの関数差を吸収すること、そして業務上の定義を読み取ることです。
メモ
上位スコアは「人が用意した知識」の上に乗っている
もう1つのベンチマークBIRDは、実データ特有の汚れに正面から向き合っています。公式サイトによれば、12,751件の質問とSQLのペア、95個の大規模データベース(合計33.4GB)、37以上の専門ドメインを含み、値が「汚い」ままであること、質問と値のあいだに外部知識が要ること、SQLの実行効率も問われることを特徴に挙げています。
ここで注目したいのは、リーダーボードの「Oracle Knowledge」という列です。BIRDでは質問ごとに、たとえば「口座タイプのOWNERは名義人本人を指す」といった知識文(evidence)が添えられます。2026年8月3日時点のリーダーボード上位は、いずれもこの知識を与えた条件での結果で、テスト集合の首位は81.95%(AskData + GPT-4o)です。同じ条件で測った人間(データエンジニアとDB専攻の学生)は92.96%とされています。
つまり、公開ベンチマークの好成績は「用語の意味を誰かが書いておいてくれた世界」での成績です。自社に持ち込むとき、その誰かは自分たちです。実際、BIRD側も2025年11月13日の告知で、曖昧性の扱いを整理するために対話型のトラックを新設し、Oracle Evidenceを外す方針を示しています。知識を与えない条件がいかに難しいかを、ベンチマークの作り手自身が課題として置いている状況です。
質問そのものが曖昧である
もう1つ、デモでは表面化しない問題があります。業務の質問は、たいてい言葉足らずです。「先月の売上」は税込か税抜か、返品を含むか、計上基準は受注日か検収日か。人間の担当者なら聞き返すところを、モデルは黙って1つの解釈を選びます。
この点を測るベンチマークもすでにあります。BIRD-Interactは、対話モード(c-Interact)と能動的なエージェントモード(a-Interact)で、曖昧な依頼に対して質問を返しながら解く能力を評価する設計で、ICLR 2026にOralとして採択されています。BIRD公式サイトの2025年10月9日の告知によれば、GPT-5(Med)のフルタスクでの成功率はc-Interactで8.67%、a-Interactで17.00%とされています。曖昧さを含んだ現実的な設定では、まだこの水準だという前提で設計する必要があります。
Spider 1.0・Spider 2.0の問題数とスコア、BIRDのデータ規模・リーダーボードの順位・人間の正答率、BIRD-Interactの成功率は、いずれも各ベンチマークの公式サイトとリーダーボード(2026年8月3日時点)および対応する論文の記載に基づきます。リーダーボードの数値は投稿のたびに更新され、評価設定ごとに集計対象も異なるため、参照する際は必ず時点と設定を確認してください。
着手する順序を間違えない
Text-to-SQLの導入が失敗するとき、原因はたいてい着手順序です。多くのチームはプロンプトとモデル選定から始めますが、そこは最後です。次の順で進めると、後戻りが小さくなります。
- 1
評価セットを作る
現場の質問を集め、期待する数値と一緒に固定します。ここが無いと、以降のどの改善も「良くなった気がする」で終わります。 - 2
セマンティックレイヤを用意する
生テーブルを直接触らせず、業務用語で読めるビューと指標定義、用語集を先に作ります。ここが精度に最も効きます。 - 3
権限とコストをDB側で縛る
読み取り専用ロール、行・列レベルの制御、タイムアウト、コスト上限を、LLMの外側で強制します。 - 4
生成側の工夫を足す
スキーマの絞り込み、few-shot例、実行して自己修正するループ、候補生成と選択。ここは1から3が済んでから効いてきます。
まず評価セットを作る
評価が無い状態でプロンプトを触るのは、目隠しでネジを回すのと同じです。Text-to-SQLの評価は、幸いなことに比較的作りやすい部類です。SQLの文字列を比べるのではなく、実行した結果が期待どおりかで判定する実行結果一致(execution accuracy)を使えば、書き方の違う正しい別解を不正解にせずに済みます。
Text-to-SQL評価セットに入れる項目
- 現場から実際に来た質問文(言葉足らずなまま、整形しない)
- その質問に対する正しいSQL、または正しい結果テーブル(小規模なら値そのもの)
- 答えが揺れる指標なら、採用した定義とその根拠(誰が決めたか)
- 答えられないはずの質問(権限外・データが無い・定義が未確定)と、期待する断り方
- 同じ意味の言い換え(解約/退会/チャーン)を含むペア
判定は「行と列の集合として一致するか」を基本にします。並び順を問わない、浮動小数点は丸めて比べる、といった細かい取り決めを先に決めておくと、比較が安定します。最初は20から50件で十分です。運用で見つかった誤りを足していけば、そのセットは自社の業務定義そのものを表す資産になります。評価の作り方全般は別記事で扱っています。
あわせて読みたい
LLMアプリの評価(eval)の作り方|「動いた気がする」で止めないための実務手順
セマンティックレイヤを先に作る
ここが本題です。生の基幹テーブルは、Text-to-SQLにとって最悪の入力です。命名が略号で、正規化のために結合が深く、履歴テーブルには有効期間の列があり、論理削除フラグが立った行が混ざっています。人間の新人でも半年かかる理解を、モデルに文脈だけで求めるのは無理があります。
対策は、モデルに見せる面を作り替えることです。
- 業務単位のビューを用意する:
mart_sales_dailyのように、粒度・期間・単位が名前から分かるビューを作り、そこだけを参照させます。結合と論理削除の除外はビューの中に閉じ込めます。 - 指標を1か所で定義する: 「解約率」「稼働ユーザー数」といった指標は、SQLの断片としてではなく、定義済みの列や関数として提供します。定義が2つある指標は、まず人間側で1つに決めるか、明示的に2つの名前を付けます。
- 用語集をモデルに渡す: 列コメントやデータカタログに、業務語との対応(「churn = 解約」「region_cd=13は関東」)を書き、プロンプトへ機械的に差し込みます。BIRDのevidenceに相当するものを、自社で用意する作業です。
- 事前集計を持つ: 月次の集計テーブルを用意しておくと、モデルが書くべきSQLが短くなり、間違える余地とスキャン量が同時に減ります。
この工程は、モデルの性能向上を待っても代替されません。「解約日の定義」は世の中のどこにも書いていない自社固有の知識だからです。逆に言えば、ここを整えることは、Text-to-SQLを使わなくなっても残る投資になります。
ヒント
権限はDBとアプリの層で強制する
Text-to-SQLの怖さは、生成物が実行可能なコードである点です。「テーブルを消さないでください」とプロンプトに書く設計は、いずれ破られます。制御はモデルの外側に置きます。
読み取り専用を担保する本丸は、ロールに与える権限そのものです。PostgreSQLであれば、参照専用のロールを作り、公開用スキーマのビューにだけSELECTを付与します。
-- 参照専用ロールを用意し、公開用スキーマだけを見せる
CREATE ROLE llm_reader LOGIN PASSWORD 'xxxxxxxx';
GRANT CONNECT ON DATABASE analytics TO llm_reader;
GRANT USAGE ON SCHEMA mart TO llm_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA mart TO llm_reader;
-- 今後mart配下に作られるテーブル・ビューにもSELECTが及ぶようにする
-- (ビューを作成するロールで実行する。別ロールが作るならFOR ROLEで指定)
ALTER DEFAULT PRIVILEGES IN SCHEMA mart GRANT SELECT ON TABLES TO llm_reader;
-- 事故防止のための既定値(セッション側から外せるため、権限の代わりにはならない)
ALTER ROLE llm_reader SET default_transaction_read_only = on;
ALTER ROLE llm_reader SET statement_timeout = '20s';
GRANT SELECT ON ALL TABLES IN SCHEMAは、実行した時点で存在するテーブル・ビューにしか効きません。セマンティックレイヤはビューを足しながら育てていくものなので、これだけで運用すると「昨日追加したビューが参照できない」という事故が定期的に起きます。公式ドキュメントによればALTER DEFAULT PRIVILEGESは将来作成されるオブジェクトに適用される権限を設定するもので、対象にはテーブル(ビューと外部テーブルを含む)が含まれます。ただし適用範囲に注意が必要です。同ドキュメントは、オブジェクト作成時に効くのは現在のロールのデフォルト権限だけで、そのロールが所属する他のロールからは継承されないと明記しています。つまりビューを作るロールが複数あるなら、FOR ROLEでそれぞれ指定するか、作成ロールを1つに寄せる必要があります。
一方、ALTER ROLE ... SETで与えた設定は、そのロールがログインしたときのセッション既定値になるだけです。statement_timeoutとdefault_transaction_read_onlyは、PostgreSQLのソース上PGC_USERSETとして定義されたパラメータ(pg_settingsのcontextがuser)で、公式ドキュメントが説明するとおり、この種のパラメータは一般ユーザーがSETで自分のセッションの値を変更できます。接続したセッションからSET statement_timeout = 0やSET default_transaction_read_only = offを実行すれば、いずれも外れます。しかもGRANT ... ON PARAMETERのSET権限は「本来スーパーユーザー権限を要するパラメータ以外では意味を持たない」と公式ドキュメントに明記されているため、権限側からこの2つを縛ることもできません。
したがってこの2つは、うっかり重いクエリを流した・うっかり書き込んだを防ぐための既定値であって、防壁ではありません。読み取り専用の強制は、ロールにSELECT以外の権限をそもそも与えないこと(GRANTで縛ること)で担保します。PUBLICに付与されている権限や、他スキーマ・他ロールの継承経由で書き込み権限が回り込んでいないかも併せて確認してください。
default_transaction_read_onlyの既定値はoffで、読み取り専用トランザクションは一時テーブル以外を変更できません。statement_timeoutの既定値は0(無効)で、単位なしの指定はミリ秒として解釈されます。既定のままでは効かないので、事故防止の層としては明示的に設定しておく価値があります。実行時間の上限を本当に強制したい場合は、DB側の設定に頼らず、アプリやコネクションプール側でクエリをキャンセルする仕組みを併用します。
行や列の見せ方を利用者ごとに変えたい場合は、行レベルセキュリティ(RLS)が使えます。公式ドキュメントでは、ALTER TABLE ... ENABLE ROW LEVEL SECURITYを実行した時点でポリシーに許可されないアクセスは既定で拒否されること、ポリシーの条件がユーザー側の条件より先に評価されることが説明されています。SQLをどう書かれても、DBが行を絞ります。ここが「権限をLLMの外で強制する」の実体です。
注意
BYPASSRLS属性を持つロール、そしてFORCE ROW LEVEL SECURITYを指定していないテーブル所有者は、RLSを常にバイパスすると明記しています。接続に使うロールがこれらに該当していないかは、設定後に必ず確認してください。ポリシーを書いたのに効いていない、という事故はここで起きます。コストの上限も同じ考え方です。BigQueryの公式ドキュメントでは、maximum bytes billedを設定すると、実行前の推定がその上限を超えた場合に課金なしでクエリが失敗すること、事前のドライランで処理バイト数の見積もりが得られること、そしてクラスタ化されていないテーブルではLIMIT句を付けても読み取られるデータ量は変わらないことが説明されています。最後の点は重要で、「LIMIT 100を付けさせているから安全」という思い込みは、従量課金の環境では成立しません。プロジェクト単位・ユーザー単位のカスタム割り当ても用意されています。
権限付与とデフォルト権限、行レベルセキュリティ、クライアント接続の既定値(statement_timeout・default_transaction_read_only)の挙動は、PostgreSQL公式ドキュメントのGRANT、ALTER DEFAULT PRIVILEGES、Row Security Policies、Client Connection Defaults、Setting Parametersの各ページで確認しています。スキャン量と課金の制御は、Google Cloud公式のBigQueryコスト管理ドキュメントの記載に基づきます。
実行系に置くガードレール
- 接続ロールは参照専用(DDL・DMLの権限を与えない)。読み取り専用の強制は権限設計で担保する
- 公開用スキーマ・ビューのみにSELECT権限を付与し、生テーブルは見せない
- 後から追加するビューにも権限が及ぶようデフォルト権限を設定する(PostgreSQLならALTER DEFAULT PRIVILEGES)
- 文の実行時間に上限を設ける(PostgreSQLならstatement_timeout。セッション側から外せるため、アプリ側のキャンセルと併用する)
- スキャン量・課金額の上限を設ける(BigQueryならmaximum bytes billed、事前のドライラン)
- 利用者ごとの行・列の可視範囲はRLSやビューで強制する
- 生成SQLに複数文・コメント・DDLが含まれていないかをアプリ側で検査する
- 誰がどの質問でどのSQLを実行し、何行返したかを監査ログに残す
生成側の工夫は最後に効く
土台ができたうえで、生成の精度を上げる打ち手が意味を持ちます。研究側で効果が確認されている代表的なものを挙げます。
| 手法 | 内容 | 位置づけ |
|---|---|---|
| スキーマリンキング(絞り込み) | 質問に関係するテーブル・列だけを選んでプロンプトに載せる | 大規模スキーマでは必須。文脈量とコストも下がる |
| 用語集・evidenceの注入 | 業務語と列・値の対応をプロンプトに添える | 自社知識を渡す唯一の経路。整備した用語集がここで効く |
| few-shot例 | 類似質問と正解SQLの組を数件添える | 方言の癖や社内の書き方の統一に効く |
| 実行して自己修正 | エラーや空結果を読んで書き直す | 構文・列名の誤りに有効。無限ループの上限を決める |
| 候補生成と選択 | 複数のSQL候補を作り、検証して1つ選ぶ | 精度は上がるがコストと待ち時間が増える |
スキーマの絞り込みについては、CHESS(arXiv:2405.16755)が、大規模スキーマを扱いやすい部分スキーマへ刈り込むスキーマセレクタによって精度が約2%向上し、LLMに渡すトークンが5分の1になったと報告しています。同論文は、候補生成と、自然言語で書かれた単体テストによる検証を組み合わせる構成で、BIRDのテスト集合で71.10%を達成したとしています。精度そのものより、「絞り込みが効く」「実行して検証する」という設計の方向性が実務では参考になります。
生成物をアプリ側で扱う際は、SQL文字列だけでなく、使用したテーブル・想定行数・前提とした定義をまとめて構造化して返させると、検査とログ記録が楽になります。
あわせて読みたい
構造化出力(Structured Outputs)とは何か|LLMの返答をプログラムで安全に扱うための仕組み
曖昧なら聞き返す設計にする
前述のとおり、業務の質問は曖昧です。ここでの正解は「賢く推測すること」ではなく「確認すること」です。指標の定義が複数ある、期間の指定が無い、対象範囲が読み取れない、といった条件を検出したら、SQLを出す前に選択肢を提示して選ばせます。
メモ
向かない場面
Text-to-SQLが向かない領域を、先に線引きしておくことも設計の一部です。
定義が揺れている指標は、そもそも自動化の対象になりません。人間同士でも合意していない数字を、モデルが正しく出すことはありません。まず定義を決める会議が先です。
履歴の持ち方が複雑なテーブルも苦手です。有効期間を持つ行、締め後の修正が別レコードで入る会計データ、遅延して到着するログ。これらは正しいSQLの条件が長く、暗黙の前提が多く、間違っても結果が自然に見えます。集計済みのビューを用意して、そこだけを見せるほうが安全です。
そして、集計の正しさが業務判断や外部提出に直結する場面での無検証利用は避けてください。取締役会資料、決算数値、監督官庁への提出物といった用途では、生成されたSQLと結果を人が確認する工程を必ず挟みます。数字は、間違っていても自然に見えるのが厄介なところです。
さらに、そもそもSQLで表現できない質問もあります。TAG(arXiv:2408.14717)は、Text2SQLが関係代数で表現できる質問だけを、RAGが少数レコードの参照で答えられる質問だけを扱っている点を指摘し、実際に自分たちで作った評価では標準的な手法の正答率が20%を超えなかったと報告しています。「この施策のレビューは好意的だったか」のような、集計と言語理解が混ざる問いは、Text-to-SQL単体の守備範囲外です。SQLで数えられる部分と、そうでない部分を分けて設計します。
あわせて読みたい
ハルシネーション対策の実務|検証フローの作り方と運用のコツ
情報漏えいのリスクを見落とさない
最後に、セキュリティの観点を1つ補います。エージェント型の構成では、モデルがクエリ結果を読んで次のSQLを組み立てます。このとき、DBの中のテキスト列に指示文が仕込まれていると、それを読んだモデルの挙動が変わる可能性があります。OWASP Top 10 for LLM Applicationsは、外部ソースの内容を処理した際に振る舞いが変わるものを間接的プロンプトインジェクションとして整理し、対策としてモデルに機能を直接持たせずアプリ側のトークンで扱う最小権限の徹底と、特権的な操作への人の介在を挙げています。
問い合わせフォームやコメント欄の内容が入るテーブルは、外部からの入力がそのまま格納される場所です。ここを参照する設計では、権限をDB側で絞っておくことが、そのまま被害の上限になります。
あわせて読みたい
プロンプトインジェクションとは|LLMアプリを狙う攻撃と実務でできる多層防御
よくある質問
モデルを最新の高性能なものに替えれば精度は上がりますか
評価はSQLの文字列一致で判定してよいですか
生成されたSQLをそのまま実行させてよいですか
LIMIT句を付けさせればコストは抑えられますか
用語集やビューの整備にどこから手を付ければよいですか
曖昧な質問にはどう対応させるべきですか
まとめ
Text-to-SQL導入のチェックリスト
- 現場の質問と期待する数値で、実行結果一致の評価セットを20から50件用意した
- 生テーブルではなく、業務語で読めるビュー・事前集計を参照させる構成にした
- 指標の定義を1か所に決め、用語集(業務語と列・値の対応)を文章として用意した
- 接続ロールを参照専用にし、実行時間・スキャン量・行の可視範囲をDB側で強制した
- 生成SQLの検査と、質問・SQL・結果行数の監査ログを組み込んだ
- スキーマ絞り込み・few-shot・自己修正といった生成側の工夫は、土台が整ってから足した
- 曖昧な質問を聞き返す、または採用した定義を明示する動線を用意した
- 定義が揺れる指標・複雑な履歴テーブル・無検証で使う重要数値は対象外と決めた
Text-to-SQLは、うまく設計すれば「データ抽出の依頼待ち」という組織のボトルネックを直接ほぐせる、数少ない用途です。一方で、デモの手応えがそのまま本番の精度になることはまずありません。ベンチマークの世代交代が示しているのは、難しさが構文からスキーマと業務定義の理解へ移ったということです。そして業務定義は、モデルの外にしか存在しません。
だからこそ、順序が結果を決めます。評価セットを作り、業務語で読める面を用意し、権限をDB側で縛る。生成側の工夫はその後です。この順で進めれば、途中でText-to-SQLをやめる判断をしたとしても、整えたビューと用語集と評価セットは組織に残ります。
あわせて読みたい
RAGとは何か|仕組み・向き不向き・導入判断の考え方
出典・参考
- Spider 2.0: Evaluating Language Models on Real-World Enterprise Text-to-SQL Workflows(公式サイト・リーダーボード)
- Spider 2.0: Evaluating Language Models on Real-World Enterprise Text-to-SQL Workflows(arXiv:2411.07763)
- Spider 1.0: Yale Semantic Parsing and Text-to-SQL Challenge(公式サイト)
- BIRD-SQL(公式サイト・リーダーボード)
- Can LLM Already Serve as A Database Interface? A BIg Bench for Large-Scale Database Grounded Text-to-SQLs(arXiv:2305.03111)
- CHESS: Contextual Harnessing for Efficient SQL Synthesis(arXiv:2405.16755)
- Text2SQL is Not Enough: Unifying AI and Databases with TAG(arXiv:2408.14717)
- BIRD-Interact(公式サイト)
- PostgreSQL Documentation: Row Security Policies
- PostgreSQL Documentation: Client Connection Defaults(statement_timeout / default_transaction_read_only)
- PostgreSQL Documentation: GRANT
- PostgreSQL Documentation: ALTER DEFAULT PRIVILEGES
- PostgreSQL Documentation: Setting Parameters(SETで変更できるパラメータの範囲)
- PostgreSQL Documentation: ALTER ROLE
- Estimate and control costs - BigQuery(Google Cloud公式ドキュメント)
- LLM01:2025 Prompt Injection - OWASP Top 10 for LLM Applications
関連する記事
LLMアプリの評価(eval)の作り方|「動いた気がする」で止めないための実務手順
LLMアプリの品質を測る評価(eval)の作り方を、実務目線で整理します。実運用に似せた評価データの用意、タスク型に応じた指標設計、自動評価と人手レビューの併用、LLM-as-a-judgeの使いどころと落とし穴、失敗トレースをテストへ戻す継続的な評価まで、小さく始める手順にまとめました。
構造化出力(Structured Outputs)とは何か|LLMの返答をプログラムで安全に扱うための仕組み
構造化出力(Structured Outputs)は、LLMの返答をあらかじめ決めたJSONスキーマに沿わせる仕組みです。JSONモードとの違い、なぜスキーマ準拠まで保証したいのか、OpenAI・Anthropic・Googleの対応の考え方、スキーマ設計や検証・リトライといった実装の勘所、そして「構造が正しくても中身が正しいとは限らない」という落とし穴までを、LLMアプリ開発の実務目線で整理します。
プロンプトインジェクションとは|LLMアプリを狙う攻撃と実務でできる多層防御
LLMアプリで最重要とされる脆弱性プロンプトインジェクション。直接・間接の違い、RAGやエージェントで顕在化する攻撃シナリオ、OWASPが挙げる緩和策を業務目線で整理し、完全には防げない前提での多層防御の組み方を解説します。


