LLMはSQLが得意…ただし、注意事項あり
数日前、自然言語からSQLへの変換におけるLLMのパフォーマンスを評価するBIRDベンチマークに出会いました。5位にランクインしているXiYan-SQLというモデルは、論文、重み、コードがすべて利用可能なオープンソースです。32BパラメータモデルのXiYan-SQLは、複数のベンチマークにおいてGPT-4oを上回るパフォーマンスを示しています。
この記事では、Xiyan-SQLの3B(8ビットに量子化)モデルと32Bパラメータモデル、そしてgpt-4oの自然言語からSQLへの変換(NLToSQL)生成パフォーマンスを比較します。
その前に、論文で使われているスキーマ表現のためのcontext engineering(コンテキストエンジニアリング)手法を紹介したいと思います。この手法は、さまざまなLLMのパフォーマンスを2パーセントポイント以上向上させています(論文の表6)。
より賢いSQLスキーマの構築:DDLを凌駕するcontext、M-Schemaの紹介
平易な英語をSQLに変換しようと格闘したことがあるなら、静かなるキラー(厄介な問題)がコンテキストであることをご存知でしょう。ユーザーが「飛べるヒーローは誰?」と尋ねても、AIはhero_idが整数であること、superpower.idが答えを解き明かす鍵であること、そして「飛ぶ」という能力がpower_nameカラムに隠されていることを知りません。
そこで登場するのがM-Schemaです。これは、言語モデルが必要とするすべて(データ型、キー、サンプル値、さらには正確な結合情報まで)を、単一のプロンプトに貼り付けられるほど短いフォーマットで提供する、軽量で一行ずつのレシピです。
以下では、元の論文で使われたスーパーヒーローデータベースを例に、4つの小さなステップが、単なるテーブルのリストを、従来のDDLに比べて2〜3%の精度向上をもたらす方法を見ていきます。
ステップ1:目印を立てる
すべてのスキーマを、「ここはスーパーヒーローデータベースです」と宣言する一行から始めます。
[DB_ID] superhero
これだけです。角括弧も、余計な句読点もありません。AIは即座に、後続のすべてのトークンをこの単一の信頼できる情報源(source of truth)にスコープします。
ステップ2:テーブルに名前を付ける
各テーブルには、人間が読める単一のヘッダーを与えます。
# Table: hero_power
人間にも読め、機械にもパース可能。他に覚えることはありません。
ステップ3:カラムをカンニングペーパーに変える
ここがM-Schemaの腕の見せ所です。通常の「hero_id INT PK」といった簡略表現の代わりに、各カラムを5つの情報を持つ1つのタプルに圧縮します。
(hero_id:INTEGER, Primary Key, the id of the hero Maps to superhero(id), Examples: [1, 2, 3])
分解してみましょう:
-
カラム名 –
hero_id -
データ型 –
INTEGER(推測不要、キャストのサプライズもなし) -
キーのステータス –
Primary Key(結合が明確になる) -
セマンティックヒント –
the id of the hero Maps to superhero(id)(DB自身のメタデータからライブで取得) -
ライブサンプル –
[1, 2, 3](モデルがデータの形を理解できるよう、3つの実在する値)
これらすべてが一行に収まっています。これが置き換える、ノイズの多い3〜4行のDDLと比較してみてください。
ステップ4:テーブル同士を繋ぐ
すべてのテーブルを記述した後、外部キーの関係を分かりやすくリストアップします。
[Foreign keys]
hero_power.power_id = superpower.id
難解なREFERENCES構文は不要です。「左のテーブルのドットカラム = 右のテーブルのドットカラム」というだけ。SQLを知らない読者でも、結合のパスをたどることができます。
M-Schemaが優れている理由
- ノイズを減らし、シグナルを増やす – データ型、キー、そしてサンプル値が、Text-to-SQLシステムを悩ませる曖昧さの90%を取り除きます。
- 小さなフットプリント – かつては肥大化したDDL文1つしか収まらなかったコンテキストウィンドウに、スキーマ全体が収まることがよくあります。
- 実証済みの効果 – GPT-4oは、従来のDDLに比べて平均2.03%の精度向上を達成し、より小さなモデルではさらに大きな飛躍が見られます。
自然言語によるSQL機能を製品に搭載するなら、M-Schemaをプロンプトにコピー&ペーストすることが、今日実装できる最速かつ最も安価なアップグレードです。
NLToSQL: Xiyan SQL vs GPT-4o:
以下のタスクでは、SQLite database、Xiyan mcp server、そしてMCP Inspectorを使用しました。Xiyan-mcpの使用方法についてはREADME.mdを、ローカルモデルのデプロイについてはXiyanのllama-cpp READMEを確認してください。XiyanSQL-32BモデルはModelScopeから利用できます。
質問1:史上最も売れている商品を、サプライヤー名を含めてトップ10を検索してください。
| モデル | SQLコマンド |
|---|---|
| Xiyan-SQL32B | SELECT T1.ProductName , T3.CompanyName FROM Products AS T1 JOIN Order Details AS T2 ON T1.ProductID = T2.ProductID JOIN Suppliers AS T3 ON T1.SupplierID = T3.SupplierID GROUP BY T1.ProductID ORDER BY sum(T2.Quantity) DESC LIMIT 10 |
| GPT-4o | SELECT p.ProductName, s.CompanyName AS SupplierName, SUM(od.Quantity) AS TotalQuantitySold FROM [Order Details] od JOIN Products p ON od.ProductID = p.ProductID JOIN Suppliers s ON p.SupplierID = s.SupplierID GROUP BY p.ProductName, s.CompanyName ORDER BY TotalQuantitySold DESC |
| Xiyan-SQL3B | SELECT T1.ProductName, T2.CompanyName FROM Products AS T1 JOIN Suppliers AS T2 ON T1.SupplierID = T2.SupplierID GROUP BY T1.ProductName, T2.CompanyName ORDER BY SUM(T1.UnitsInStock) DESC LIMIT 10 |
ステップ1:評価
| モデル | スコア | 正しいカラム | 正しいテーブル | 正しい結合 | 正しいフィルタリング | 正しい集計 |
|---|---|---|---|---|---|---|
| Xiyan-SQL32B | 9 | ✅ | ✅ | ✅ | ✅ | ✅ |
| GPT-4o | 7 | ✅ | ✅ | ✅ | ❌ | ✅ |
| Xiyan-SQL3B | 1 | ✅ | ❌ | ❌ | ✅ | ❌ |
📊 ステップ2:ランキング
ランキング:
1. XiyanSQL-32B – スコア: 9
2. GPT-4o – スコア: 7
3. XiyanSQL-3B – スコア: 1
🧠 ステップ3:論理的根拠
- XiyanSQL-32B: スコア9 – このクエリはほぼ完璧です。必要な3つのテーブル(Products, Order Details, Suppliers)を正しく結合し、Quantityを合計して売上を集計し、降順で正しく並べ替え、結果をトップ10に適切に制限しています。唯一の軽微な欠点は、GROUP BY句にT1.ProductIDしか含まれていないことです。標準的なSQLでは、T1.ProductNameとT3.CompanyNameもGROUP BYに含める必要があります。しかし、MySQLのような多くのSQL方言では、これは正しく処理されます。
- GPT-4o: スコア7 – このクエリは、結合と集計のロジックは正しいです。GROUP BY句はモデル1よりも堅牢に書かれています。しかし、質問の重要な要件であった「トップ10」というLIMIT 10句が完全に抜けています。そのため、売上順にソートされた全製品を返してしまい、トップ10だけではありません。
- XiyanSQL-3B: スコア1 – このクエリは根本的に間違っています。売上データ(Quantity)を含むOrder Detailsテーブルの使用に失敗しています。その代わりに、在庫レベルを反映するUnitsInStockを合計して製品をランク付けしようとしており、これは売上実績とは関係ありません。これは、「トップセラー」製品を見つけたいというユーザーの意図を完全に誤解しています。
❌ ステップ4:エラー分類
- GPT-4o: フィルタリングの誤り – LIMIT句を省略したため、結果セットをトップ10のレコードに絞り込むことに失敗しています。
- XiyanSQL-3B: 集計またはフィルタリングの誤り – 主なエラーは、ランキングにSUM(T1.UnitsInStock)を使用していることで、これは「販売」製品を見つける上で論理的に不正確です。これは、必要なOrder Detailsテーブルが存在しないため、結合の欠落エラーでもあります。
質問2:各サプライヤーが供給する製品から生み出された総収益を計算してください。
| モデル | SQLコマンド |
|---|---|
| GPT-4o | SELECT s.CompanyName AS SupplierName, SUM(od.Quantity * od.UnitPrice * (1 - od.Discount)) AS TotalRevenue FROM [Order Details] od JOIN Products p ON od.ProductID = p.ProductID JOIN Suppliers s ON p.SupplierID = s.SupplierID GROUP BY s.CompanyName ORDER BY TotalRevenue DESC; |
| XiyanSQL-32B | SELECT S.CompanyName AS SupplierName, SUM(OD.UnitPrice * OD.Quantity) AS TotalRevenue FROM Suppliers S JOIN Products P ON S.SupplierID = P.SupplierID JOIN Order Details OD ON P.ProductID = OD.ProductID GROUP BY S.CompanyName ORDER BY TotalRevenue DESC; |
| XiyanSQL-3B | SELECT T1.CompanyName , SUM(T2.UnitPrice * T2.Quantity * (1 - T2.Discount)) FROM Suppliers AS T1 JOIN Order Details AS T2 ON T1.SupplierID = T2.ProductID GROUP BY T1.CompanyName |
ステップ1:評価
| モデル | スコア | 正しいカラム | 正しいテーブル | 正しい結合 | 正しいフィルタリング | 正しい集計 |
|---|---|---|---|---|---|---|
| GPT-4o | 10 | ✅ | ✅ | ✅ | ✅ | ✅ |
| XiyanSQL-32B | 6 | ✅ | ✅ | ✅ | ✅ | ❌ |
| XiyanSQL-3B | 0 | ✅ | ❌ | ❌ | N/A | ✅ |
📊 ステップ2:ランキング
ランキング:
1. GPT-4o – スコア: 10
2. XiyanSQL-32B – スコア: 6
3. XiyanSQL-3B – スコア: 0
🧠 ステップ3:論理的根拠
- GPT-4o: スコア10 – このクエリは完璧です。Suppliers、Products、Order Detailsを正しく結合しています。最も重要なのは、収益計算SUM(od.Quantity * od.UnitPrice * (1 - od.Discount))が最も正確である点です。注文に適用された割引を適切に考慮しています。グループ化と順序付けも正しいです。
- XiyanSQL-32B: スコア6 – このクエリは、テーブルの結合とグループ化は正しいです。しかし、収益計算SUM(OD.UnitPrice * OD.Quantity)は、注文のDiscountを完全に無視しているため不正確です。これにより、収益額が過大に計算され、不正確な結果となります。
- XiyanSQL-3B: スコア0 – このクエリは無効であり、失敗します。T1.SupplierID = T2.ProductIDという無意味な結合条件を使用してSuppliersをOrder Detailsに直接結合しようとしています。サプライヤーを注文で販売された製品にリンクするために不可欠なProductsテーブルを完全に省略しています。
❌ ステップ4:エラー分類
- XiyanSQL-32B: 集計またはフィルタリングの誤り – SUM(...)集計は、Discountを考慮に入れていないため不正確であり、不正確な収益計算につながっています。
- XiyanSQL-3B: 結合の欠落 – クエリはProductsテーブルを省略しているため、根本的に壊れています。したがって、SuppliersをOrder Detailsに正しくリンクできません。使用されている結合条件も不正確です。
質問3:全製品の平均単価よりも単価が高い製品を表示してください。
| モデル | SQLコマンド |
|---|---|
| GPT-4o | SELECT * FROM Products WHERE UnitPrice > (SELECT avg(UnitPrice) FROM Products) |
| XiyanSQL-32B | SELECT * FROM Products WHERE UnitPrice > (SELECT AVG(UnitPrice) FROM Products); |
| XiyanSQL-3B | SELECT ProductName FROM Products WHERE UnitPrice > (SELECT avg(UnitPrice) FROM Products) |
✅ ステップ1:評価
| モデル | スコア | 正しいカラム | 正しいテーブル | 正しい結合 | 正しいフィルタリング | 曖昧さの処理 |
|---|---|---|---|---|---|---|
| GPT-4o | 10 | ✅ | ✅ | ✅ | ✅ | ✅ |
| XiyanSQL-32B | 10 | ✅ | ✅ | ✅ | ✅ | ✅ |
| XiyanSQL-3B | 10 | ✅ | ✅ | ✅ | ✅ | ✅ |
📊 ステップ2:ランキング
ランキング:
1. GPT-4o – スコア: 10
2. XiyanSQL-32B – スコア: 10
3. XiyanSQL-3B – スコア: 10
3つのモデルはすべて、質問に答えるための完全に有効で正しい方法であるため、同等にランク付けされます。
注意点
これらに加えて、私はSpider2のSQLiteデータベースから、より複雑なクエリを複数テストしました。California_Traffic_Collision SQLiteデータベースに対して、Spider2/spider2-lite/spider2-lite.jsonlから取られた、より複雑な自然言語クエリです。
ヘルメットの着用有無で分けて、オートバイ衝突事故の死亡率を計算してください。具体的には、2つのパーセンテージを計算してください:
1) 当事者(運転手または同乗者)がヘルメットを着用していた衝突事故におけるオートバイ乗員の死亡率
2) 当事者がヘルメットを着用していなかった衝突事故におけるオートバイ乗員の死亡率
各グループについて、オートバイ乗員の総死亡者数を、そのグループが関与した総衝突事故数で割って計算してください。
ヘルメットの着用状況を判断するには、partiesテーブル(party_safety_equipmentフィールド)を使用してください。
前のセクションで議論したような平均的な難易度のクエリでは、gpt-4oとXiyan-SQL32Bは同等の能力を持っているように見えますが、このケースのような複雑なクエリに対しては、両者とも正しい答えを生成することに失敗します。
結論と考察
タイトルがすべてを物語っています。LLMはSQLをマスターしつつありますが、「注釈」が重要です。進歩は目覚ましいものがありますが、集計やフィルタリングにおける微妙なエラーは、GPT-4oやXiyan-SQLのようなトップモデルでさえも完璧ではないことを示しています。これらのモデルはほとんどのクエリに対して強力なスタートダッシュを提供しますが、真の複雑さに直面すると依然として壁にぶつかります。
重要なのは、これらのツールを自律的なパイロットとしてではなく、熟練の副操縦士として扱うことです。M-Schemaのような手法を用いた効果的なコンテキストエンジニアリングは精度向上に不可欠ですが、最終的なクエリの人間による検証は、依然として譲れない必須事項です。完全に信頼できる自動化されたSQL生成の時代は近いですが、まだそこには至っていません。
この記事の英語版は👉LLMs Ace SQL… Just Ignore the Footnotes
Discussion