本記事は DIN-SQL: Decomposed In-Context Learning of Text-to-SQL with Self-Correction の解説記事です。
論文概要(Abstract)
DIN-SQLは、複雑なNL→SQLタスクを「スキーマリンキング → 難易度分類 → サブタスク別SQL生成 → 自己修正」の4ステップに分解し、各ステップでそれぞれ独立したIn-Context Learning(Few-Shot)プロンプトを適用するフレームワークである。著者らは、GPT-4を使用してSpider開発セットでExecution Accuracy 82.8%を達成し、2023年5月時点でSpiderリーダーボードの上位に位置したと報告している。
この記事は Zenn記事: LangGraph×Claude Sonnet 4.6でSQL統合Agentic RAGを実装する の深掘りです。Zenn記事ではLangGraphのStateGraphでクエリルーティング(SQL/ベクトル検索/両方)を実装していますが、DIN-SQLはSQL生成自体をサブタスクに「分解」するアプローチであり、LangGraphのノード設計パターンと親和性が高い手法です。
情報源
- arXiv ID: 2305.11853
- URL: https://arxiv.org/abs/2305.11853
- 著者: Mohammadreza Pourreza, Davood Rafiei
- 発表年: 2023
- 分野: cs.CL
- コード: https://github.com/MohammadrezaPourreza/Few-shot-NL2SQL-with-prompting(MITライセンス)
背景と動機(Background & Motivation)
Text-to-SQLタスクでLLMを使用する際、単一のプロンプトで「自然言語 → SQL」の変換を行うと、特に複雑なクエリ(サブクエリ、複数テーブルJOIN、集約関数の組み合わせ等)で精度が低下する。著者らは、この問題がタスクの複雑さを1つのプロンプトに押し込めることに起因すると分析し、タスクを複数の独立したサブ問題に分解するアプローチを提案した。
従来手法であるファインチューニングベースのText-to-SQL(PICARD、RESDSQL等)は、特定のデータセットに対して高い精度を示すが、新しいDBスキーマへの汎化が難しく、モデル再訓練のコストが高い。DIN-SQLは「プロンプトエンジニアリングのみ」で高精度を達成する点で、実務への適用障壁が低い。
主要な貢献(Key Contributions)
- 貢献1: Text-to-SQLタスクの4ステップ分解アーキテクチャの提案。各ステップが独立したFew-Shotプロンプトで動作するため、個別のテスト・改善が容易
- 貢献2: SQL難易度の自動分類(Easy / Non-Nested Complex / Nested Complex)。クエリの複雑さに応じて異なる生成戦略を適用することで、特に複雑なクエリの精度を改善
- 貢献3: SQL実行結果のフィードバックによる自己修正メカニズム。GPT-4のChain-of-Thought能力を活用したエラー分析と修正
技術的詳細(Technical Details)
4ステップ分解アーキテクチャ
DIN-SQLのパイプラインは以下の4ステップで構成される。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ユーザークエリ + DBスキーマ
↓
[Step 1] Schema Linking(スキーマリンキング)
→ 関連テーブル・カラム・条件値の特定
↓
[Step 2] Classification & Decomposition(分類と分解)
→ Easy / Non-Nested / Nested に分類
→ Nestedの場合はサブクエリに分解
↓
[Step 3] SQL Generation(SQL生成)
→ 分類結果に応じたプロンプトでSQL生成
↓
[Step 4] Self-Correction(自己修正)
→ 生成SQLの検証・修正
↓
最終SQL
Step 1: Schema Linking
スキーマリンキングでは、ユーザークエリとDBスキーマを入力とし、クエリに関連するテーブル、カラム、および条件値を特定する。
著者らは以下のプロンプト構造を使用している:
1
2
3
4
5
6
7
8
9
10
Given the database schema:
{schema with FK/PK relationships}
Find the relevant tables and columns for:
"{user query}"
Output format:
- Tables: [table1, table2, ...]
- Columns: [table1.col1, table2.col2, ...]
- Conditions: [col = 'value', ...]
重要なポイント: プロンプトにFK(外部キー)/PK(主キー)関係を明示的に含めることで、LLMがJOIN条件を正確に把握できる。著者らの報告によれば、FK/PK情報を含めない場合、複数テーブルJOINを含むクエリの精度が約8%低下する(論文Section 5.2)。
Step 2: Classification & Decomposition
クエリの複雑さを3段階に分類し、各カテゴリに応じた生成戦略を選択する。
| カテゴリ | 定義 | SQL例 | 生成戦略 |
|---|---|---|---|
| Easy | 単一テーブル、サブクエリなし | SELECT name FROM emp WHERE dept='営業' | 直接生成 |
| Non-Nested | 複数テーブルJOIN、サブクエリなし | SELECT e.name FROM emp e JOIN dept d ON ... | JOIN条件を明示して生成 |
| Nested | サブクエリ含む | SELECT * FROM ... WHERE id IN (SELECT ...) | サブクエリに分解→個別生成→結合 |
この分類は以下の数式で形式化される。
\[c = f_{\text{classify}}(q, S) \in \{\text{easy}, \text{non-nested}, \text{nested}\}\]ここで、
- $q$: ユーザークエリ(自然言語)
- $S$: Step 1で特定されたスキーマリンキング結果
- $f_{\text{classify}}$: LLMによる分類関数(Few-Shot ICL)
Nestedに分類された場合、著者らはサブクエリへの分解を行う:
\[q \xrightarrow{f_{\text{decompose}}} \{q_1, q_2, \ldots, q_k\}\]各$q_i$は独立したサブ問題として扱われ、個別にSQL生成される。
Step 3: SQL Generation
分類結果に応じて、それぞれ異なるFew-Shot例を含むプロンプトでSQL文を生成する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-sonnet-4-6-20250929", temperature=0)
# 分類結果に応じたプロンプトテンプレート
PROMPTS = {
"easy": """以下のFew-Shot例に従い、SQLを1つ生成してください。
{few_shot_examples_easy}
スキーマ: {schema}
関連テーブル・カラム: {schema_links}
クエリ: {query}
SQL:""",
"non_nested": """以下のFew-Shot例に従い、JOIN条件を明示してSQLを生成してください。
{few_shot_examples_non_nested}
スキーマ: {schema}
関連テーブル・カラム: {schema_links}
FK/PK関係: {fk_pk}
クエリ: {query}
SQL:""",
"nested": """サブクエリを含むSQLを生成します。
まず、以下のサブ問題それぞれのSQLを生成し、最後に結合してください。
サブ問題:
{sub_questions}
{few_shot_examples_nested}
スキーマ: {schema}
SQL:""",
}
async def generate_sql(
query: str,
schema: str,
schema_links: dict,
classification: str,
few_shot_examples: str,
) -> str:
"""分類結果に応じたプロンプトでSQL生成"""
prompt = PROMPTS[classification].format(
query=query,
schema=schema,
schema_links=schema_links,
few_shot_examples=few_shot_examples,
)
response = await llm.ainvoke(prompt)
return response.content.strip()
Step 4: Self-Correction
生成されたSQLに対し、LLMが自己診断を行い、論理的な誤りを修正する。著者らは「Generic Error Prompting」と呼ばれる手法を使用しており、SQLを直接実行するのではなく、LLMに「このSQLに誤りがないか」を分析させる。
Self-Correctionのプロンプト構造:
1
2
3
4
5
6
7
8
9
10
11
12
13
以下のSQLにバグや誤りがないか確認してください。
スキーマ: {schema}
ユーザーの質問: {query}
生成されたSQL: {generated_sql}
特に以下の観点で確認:
1. JOIN条件は正しいか(FK/PK整合性)
2. WHERE句の条件は質問と一致するか
3. GROUP BY / HAVING の使い方は適切か
4. サブクエリの結果型は親クエリと整合しているか
修正が必要な場合は修正後のSQLを、問題ない場合はそのまま出力。
この自己修正は、Zenn記事で使用しているsql_db_query_checkerツールの設計思想と共通する。DIN-SQLでは実行前のLLMベース検証であり、CHESSの実行後エラーフィードバックとは補完的な関係にある。
実装のポイント(Implementation)
DIN-SQLをLangGraphに組み込む際の実装ポイントを整理する。
- ノード分割との親和性: DIN-SQLの4ステップは、LangGraphのStateGraphにおける4つのノードにそのまま対応する。各ステップが独立したプロンプトで動作するため、ノード単位のテスト・差し替えが容易
1
2
3
4
5
6
# DIN-SQLの4ステップをLangGraphノードとして実装
graph = StateGraph(DINSQLState)
graph.add_node("schema_linking", schema_linking_node)
graph.add_node("classification", classification_node)
graph.add_node("sql_generation", sql_generation_node)
graph.add_node("self_correction", self_correction_node)
Few-Shot例の選定: 著者らはSpiderのtraining setから各カテゴリ(Easy/Non-Nested/Nested)ごとに5-8例を人手で選定している。社内DBに適用する場合は、自社のQ&Aログからカテゴリ別に例を収集する必要がある
FK/PK情報の管理: スキーマリンキングの精度はFK/PK情報に大きく依存する。SQLAlchemy等のORMからメタデータを自動取得する仕組みの実装が推奨される
分類の閾値: 分類ステップはLLMの判断に依存するため、判定が微妙なケースが発生する。論文では分類ミスがSQL生成精度に与える影響を分析しており、Easyに誤分類された複雑クエリの精度低下が顕著であると報告されている
実験結果(Results)
論文Table 1およびTable 2より、主要なベンチマーク結果を示す。
| モデル | 手法 | Spider dev EX | Spider test EM | BIRD dev EX |
|---|---|---|---|---|
| GPT-4 | DIN-SQL(4ステップフル) | 82.8% | 60.1% | 55.9% |
| GPT-4 | Schema Linkingなし | 76.2% | - | - |
| GPT-4 | 分類なし(全て同一プロンプト) | 78.5% | - | - |
| GPT-4 | Self-Correctionなし | 80.1% | - | - |
| Codex | DIN-SQL | 74.2% | - | - |
| GPT-3.5 | DIN-SQL | 60.1% | - | - |
分析ポイント(論文Section 5より):
- Schema Linkingの除外による6.6%の精度低下は、LLMが無関係なテーブル・カラムをSQL中に含めてしまうことが主因と著者らは分析している
- 分類ステップの除外による4.3%の低下は、複雑なNestedクエリに対して単純なプロンプトが適用されることで発生する
- Self-Correctionによる改善は2.7%。著者らは主にJOIN条件の誤りとWHERE句の不整合が修正されるケースが多いと報告している
- GPT-3.5では全ステップを適用しても60.1%にとどまり、ベースモデルの推論能力への依存度が示されている
実運用への応用(Practical Applications)
Zenn記事のSQL統合Agentic RAGにDIN-SQLの知見を適用する場合、以下が参考になる。
ルーティングの精緻化: Zenn記事ではクエリを「SQL/ベクトル検索/両方」の3分類でルーティングしているが、DIN-SQLの分類(Easy/Non-Nested/Nested)をSQL検索パス内のサブルーティングに適用することで、SQL生成の精度向上が期待できる
Schema Linkingの事前処理: Zenn記事の
SQLDatabaseToolkitはlist_tables→schemaの順で情報を取得するが、DIN-SQLのSchema Linkingを追加することで、LLMに渡すスキーマ情報をクエリに関連するテーブル・カラムに絞り込めるSelf-Correctionの多段構成: Zenn記事の
sql_db_query_checker(事前検証)とDIN-SQLの自己修正(LLMベース論理検証)を組み合わせることで、より堅牢なSQL生成パイプラインが構築できる
制約: DIN-SQLはSpiderベンチマーク(英語・単一DB構成)に最適化されており、日本語クエリや複数DBを横断するクエリへの適用には追加のプロンプト設計が必要である。また、4ステップの逐次実行によりレイテンシが増加する点にも留意が必要である。
関連研究(Related Work)
- C3 (Dong et al., 2023): Zero-Shot Text-to-SQLの代表的手法。DIN-SQLと異なりFew-Shot例を使用しないため、新規DB適用時の初期コストが低いが、精度はDIN-SQLに劣る
- DAIL-SQL (Gao et al., 2023, arXiv:2309.00867): Few-Shot例の選択戦略を体系的に分析。DIN-SQLのFew-Shot例選定の改善に直接応用可能
- CHESS (Talaei et al., 2024, arXiv:2405.16755): DIN-SQLの分解戦略をEntity Retrieval(BM25+FAISS)で拡張し、BIRD devで65.0%を達成
まとめと今後の展望
DIN-SQLは、Text-to-SQLタスクの4ステップ分解という明快なアーキテクチャにより、LLMのプロンプトエンジニアリングのみで高い精度を達成した先駆的な研究である。著者らの分解アプローチは、LangGraphのノードベース設計と直接対応し、各ステップの独立したテスト・改善を可能にする。
MITライセンスのOSSとして公開されており、Text-to-SQLパイプライン構築の出発点として適している。Zenn記事のアーキテクチャにDIN-SQLの分類・分解ロジックを組み込むことで、特に複雑なSQLクエリの生成精度向上が期待できる。
参考文献
- arXiv: https://arxiv.org/abs/2305.11853
- Code: https://github.com/MohammadrezaPourreza/Few-shot-NL2SQL-with-prompting
- Related Zenn article: https://zenn.dev/0h_n0/articles/58dc3076d2ffba
- Spider benchmark: https://yale-lily.github.io/spider
- BIRD benchmark: https://bird-bench.github.io/