從 SQL 到 Text-to-SQL:讓資料庫聽懂人話

從資料表、SQL 與 JOIN 開始,逐步理解 Text-to-SQL 的研究發展,以及我的 AAP-SQL 方法如何兼顧準確率與成本。

14 分鐘閱讀
從 SQL 到 Text-to-SQL:讓資料庫聽懂人話
文章目錄

公司、醫院、學校和政府每天都在累積資料,但「資料存在」不代表「每個人都能使用」。很多時候,我們知道自己想問什麼,卻不知道資料放在哪張表,也不知道該怎麼寫 SQL。

我的碩士論文研究的,就是如何讓使用者直接用自然語言提問,再由系統自動產生可以執行的 SQL。這項技術稱為 Text-to-SQL。

我提出的 AAP-SQL,會在生成 SQL 前找出問題相關的欄位、資料值與表格關係,再把這些資訊提供給模型。公開原始碼與碩士論文記錄了方法與實驗結果。

不過,在介紹我的方法之前,我想先從最基本的問題開始:資料庫到底長什麼樣子?SQL 又在做什麼?

先認識資料庫:表、欄位與資料列

關聯式資料庫可以想成一組彼此有關聯的試算表。假設一間商店有兩張表:

顧客表 customers

customer_idnamecity
101AmyTaipei
102BobTainan

訂單表 orders

order_idcustomer_idamount
90011011200
9002101650
9003102800

直向的 name、city、amount 稱為欄位,描述資料具有哪些屬性;橫向的每一筆內容稱為資料列。整個資料庫有哪些表、欄位與型別,合起來稱為 Schema(資料庫結構)。

其中,customers.customer_id 是每位顧客不重複的編號,稱為主鍵。orders.customer_id 則指向顧客表中的同名編號,稱為外鍵。靠著這個關係,我們才知道訂單 9001 和 9002 都屬於 Amy。

真實資料庫當然比這複雜得多:可能有數十張表、數百個欄位,而且名稱常是縮寫。使用者只看到「顧客」和「訂單」,系統看到的卻可能是 cust_info、txn_hdr 和一大串編號。

SQL 是怎麼向資料庫提問的?

SQL(Structured Query Language)是操作關聯式資料庫的語言。若要找出住在台北的顧客,可以寫:

SELECT name
FROM customers
WHERE city = 'Taipei';

這三行可以直接讀成:

  • SELECT name:我要取得姓名。
  • FROM customers:資料來自顧客表。
  • WHERE city = 'Taipei':只保留城市為台北的資料。

如果問題改成「台北顧客總共下了幾筆訂單?」,就必須同時使用顧客表和訂單表:

SELECT COUNT(*)
FROM customers
JOIN orders
  ON customers.customer_id = orders.customer_id
WHERE customers.city = 'Taipei';

這裡的 JOIN 是把兩張表連接起來,ON 則指定連接規則。最後用 COUNT(*) 計算符合條件的訂單數量。

SQL 很精確,但也因此不能只靠大概。表名、欄位、連接方式或條件值只要有一個選錯,語法即使可以執行,也可能得到錯誤答案。

Text-to-SQL 不只是把中文翻成英文

Text-to-SQL 希望把這段流程自動化。使用者只要問「台北顧客總共下了幾筆訂單?」,系統就應該自己找出相關資料表、建立連接、加入篩選條件,最後產生 SQL。

看起來很像翻譯,但真正的流程更接近:

理解問題 → 找到表與欄位 → 確認資料值 → 判斷表格關係 → 組成 SQL → 執行查詢

這也是為什麼「會寫出合法 SQL」和「能查到正確答案」是兩回事。大型語言模型可能很熟悉 SQL 語法,卻不可能事先知道每間公司的資料庫如何命名、資料如何編碼。

在真實資料庫中,常見困難大致有三類。

1. 問題和欄位名稱對不起來

使用者問「客戶住在哪裡」,資料庫裡可能同時有 city、district、address 和 branch_location。系統必須判斷問題真正指向哪一個欄位,這通常稱為 Schema Linking(結構對齊)。

2. 人的說法和資料庫儲存值不同

使用者說「高階信用卡」,資料庫可能存成 gold;使用者說「已完成」,系統裡也可能用數字 1 表示。模型理解句意還不夠,它也要知道資料實際怎麼存,否則條件寫得很合理,查詢結果仍可能是空的。

3. 不知道表格應該怎麼連接

一個問題常同時需要顧客、訂單、產品等多張表。若外鍵沒有完整記錄,或兩個看似相同的欄位其實代表不同概念,模型就容易使用錯誤的 JOIN。

前人的研究做到哪裡了?

早期的 Text-to-SQL 多半針對單一、固定的資料庫設計。模型在訓練時已經看過資料庫結構,因此問題相對單純,但也很難直接搬到另一個系統。

2018 年推出的 Spider 資料集改變了研究方向:測試時會出現訓練期間沒有看過的資料庫。模型不能只記住欄位名稱,而必須學會如何理解新的 Schema。之後的研究開始從不同角度降低錯誤,例如:

  • IRNet 和 NatSQL 使用中間表示法,先把問題整理成較容易推理的結構,再轉成 SQL。
  • BRIDGE 把資料庫中的實際值提供給模型,幫助它連結問題和欄位。
  • PICARD 在生成過程中限制不合法的 SQL,減少語法錯誤。

大型語言模型出現後,研究重心進一步轉向提示設計與多階段推理。DAIL-SQL 會挑選適合的範例放進提示;DIN-SQL 把結構對齊、問題分類、SQL 生成和修正拆成多個步驟;CHESS 則使用多個模組,分別負責檢索、Schema 篩選、候選 SQL 與測試。

評估方式也越來越貼近現實。BIRD 使用較大型的真實資料庫,並加入資料值和領域知識;Spider 2.0 更進一步涵蓋企業資料流程、不同 SQL 方言、說明文件與多步驟操作。

在我整理論文時,BIRD 排行榜上的高資源方法大約已達 70% 到 80% 的執行準確率。不過,部分方法需要專門訓練模型、反覆呼叫大型語言模型,或一次產生許多候選 SQL 再挑答案。它們展示了很高的上限,但成本與重現門檻也不低。

因此,現在的問題不只是「能不能產生 SQL」,還包括:能不能在複雜資料庫中,用合理成本穩定地找到正確答案?

我的做法:保留全貌,只放大重要部分

我的研究提出 AAP-SQL。它的核心概念可以用地圖來比喻:系統仍保留整座城市的道路,避免迷失方向;但只把目的地附近的街道、地標和路口放大,不需要把每條巷子的照片都攤在眼前。

換成資料庫的語言,就是:保留所有表和欄位的基本結構,再只對最可能相關的欄位補充詳細資訊。

這點很重要。AAP-SQL 並不是把沒有選中的欄位全部刪掉。如果前面的檢索判斷錯誤,大型語言模型仍能看到完整的 Schema;被選為核心欄位的部分,則會得到更多資料值與關係線索。

整體流程如下:

  1. 先廣泛搜尋,再重新排序:系統先找出一批可能相關的欄位,再用針對 Text-to-SQL 訓練的排序模型,選出大約十個最值得注意的核心欄位。
  2. 查看少量真實資料:針對核心欄位,實際查詢最多三個不同的值。例如看到 gold、silver,模型就比較容易理解信用卡等級的儲存方式。
  3. 對齊問題中的明確文字:如果問題中出現像 "OWNER" 這樣帶引號的值,系統會在文字欄位中進行不分大小寫的精確搜尋,確認它究竟存在哪一欄。
  4. 補充可能遺漏的連接線索:除了資料庫已宣告的外鍵,也比較欄位名稱、值的重疊程度和唯一性,找出可能但未正式標註的關係。這些只會作為 JOIN 線索,不會被當成絕對正確的外鍵。
  5. 整理成容易閱讀的提示:完整 Schema、外鍵、核心欄位、抽樣值、找到的條件值、外部線索與使用者問題會分區排列,最後才交給大型語言模型產生 SQL。

其中,前兩步像是在大地圖上圈出重點;後兩步則告訴模型「資料實際長什麼樣」以及「道路可能怎麼接」。

用一個完整例子理解 AAP-SQL

論文中有一道問題,大意是:

有多少張高階信用卡,其處分權類型是 "OWNER"?

人類看到這句話,可能直覺以為只要查信用卡表。但系統實際上需要完成四個判斷:

  1. 找到信用卡資料所在的 card 表。
  2. 從題目附帶的外部線索得知,「高階」在這個資料庫中對應 gold。
  3. 搜尋真實資料後,確認 "OWNER" 存在於 disp.type,而不是 card.type。
  4. 使用 disp_id 把 card 和 disp 兩張表連接起來。

最後產生的查詢概念如下:

SELECT COUNT(card.card_id)
FROM card
JOIN disp
  ON card.disp_id = disp.disp_id
WHERE card.type = 'gold'
  AND disp.type = 'OWNER';

這個例子正好說明 Text-to-SQL 的難處:自然語言只有一句,背後卻同時需要理解欄位、真實值與表格關係。AAP-SQL 的工作,就是在生成 SQL 之前,先把這些最可能影響答案的資訊找出來。

我怎麼評估它是否有效?

測試使用 BIRD Dev dev_20240627 的 1,534 道問題和 11 個 SQLite 資料庫,以查詢結果是否正確作為主要評分。

我主要觀察兩個指標:

  • 執行準確率:把生成的 SQL 真的送進資料庫執行,再比較結果是否和標準答案相同。它比只檢查 SQL 字串更合理,因為不同寫法也可能得到同一個正確結果。
  • Token 用量:Token 是大型語言模型處理文字時使用的基本單位,一個中文字、英文單字或符號可能被切成一個或多個 Token。這裡統計模型呼叫的文字用量,用來比較不同方法的輸入負擔。

BIRD 的部分題目還提供 Evidence,也就是額外的領域線索。例如前面的題目會告訴系統「高階」對應 gold。下方歷史比較中的「基礎提示加外部線索」,代表直接把基本 Schema、問題和這些線索交給模型,沒有加入 AAP-SQL 的檢索與資料值處理。

Gemini 3.1 Flash Lite 的評估結果

方法執行準確率總 Token平均每題 Token
AAP-SQL66.69%3.41M約 2,222

1,534 題中有 1,023 題得到正確執行結果,執行準確率為 66.69%。總用量為 3,409,705 Token,平均每題約 2,222 Token。公開研究摘要列出了這次測試的設定與結果。

方法比較:Gemini 2.5 Flash-Lite

方法執行準確率總 Token平均每題 Token
AAP-SQL62.58%3.32M約 2,164
DIN-SQL61.08%47.32M約 30,848
CHESS61.21%42.43M約 27,659
基礎提示加外部線索57.76%2.19M約 1,427

這組方法比較統一使用 Gemini 2.5 Flash-Lite。AAP-SQL 答對 960 / 1,534 題,基礎方法答對 886 題;平均每題 Token 從約 1,427 增加到 2,164。補充欄位與資料值增加了輸入用量,也讓更多問題得到正確答案。

同一組測試中,DIN-SQL 與 CHESS 的總用量分別為 47.32M 與 42.43M Token,AAP-SQL 為 3.32M。這呈現了研究要處理的取捨:提供足夠的資訊幫助模型回答,同時減少反覆生成與推理的文字用量。

哪些設計真的有幫助?

接著,我在 Gemini 2.5 Flash-Lite 的測試中逐一移除元件,觀察準確率如何改變。這種做法稱為「消融實驗」,可以看出各項設計對結果的影響。

在有外部線索的條件下:

  • 不查看核心欄位的真實資料值,準確率下降 1.95 個百分點。
  • 不做問題文字與資料值的對齊,下降 1.69 個百分點。
  • 把專門訓練的檢索器換成通用檢索器,下降 1.63 個百分點。
  • 拿掉第二階段的重新排序,下降 1.24 個百分點。
  • 只使用資料庫正式宣告的外鍵,不補充其他關係線索,下降 0.65 個百分點。

這些結果顯示,幫助模型的不是「把資料庫內容塞得越多越好」,而是能不能找出和當前問題有關的欄位、真實值與連接方式。

錯誤分析:哪些資訊會幫忙,哪些會干擾?

在 Gemini 2.5 Flash-Lite 的方法比較中,AAP-SQL 把錯誤題數從基礎方法的 648 題降到 574 題。

首先,新增資訊不一定永遠帶來幫助。原本答錯的問題中有 143 題被修正,但也有 69 題原本正確、加入資訊後反而答錯。這表示檢索到的內容有時仍會干擾模型。

只需要修正欄位、文字值或 JOIN 的題目有改善,但同時需要複雜結構、篩選和關聯推理的錯誤,沒有明顯下降。檢索出的核心欄位能完整涵蓋標準答案所需欄位的比例為 38.20%;其餘欄位仍保留在完整 Schema 中,供模型生成 SQL 時參考。

文字值對齊目前以帶引號的精確內容為主。錯字、近義詞、未加引號的名稱,以及日期與單位轉換,是後續改善這個步驟的方向。

結語

Text-to-SQL 的終點,不只是讓模型「寫得出 SQL」,而是讓沒有資料庫背景的人也能可靠地取得資料,同時讓系統成本維持在可接受範圍。

AAP-SQL 的核心做法是:保留資料庫全貌,再針對最相關的欄位補充真實資料值與表格關係。 在 Gemini 2.5 Flash-Lite 的比較中,這個方法提高了基礎提示的執行準確率,文字用量也低於 DIN-SQL 與 CHESS。使用 Gemini 3.1 Flash Lite 的評估則得到 66.69% 的執行準確率。

從方法比較與錯誤分析可以看到,欄位名稱之外,資料實際如何儲存、表格如何連接,都會影響答案。AAP-SQL 將這些資訊放到生成 SQL 之前處理,讓模型有更具體的依據來回答問題。