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

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

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

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

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

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

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

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

顧客表 customers

customer_idnamecity
101AmyTaipei
102BobTainan

訂單表 orders

order_idcustomer_idamount
90011011200
9002101650
9003102800

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

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

真實資料庫當然比這複雜得多:可能有數十張表、數百個欄位,而且名稱常是縮寫。使用者只看到「顧客」和「訂單」,系統看到的卻可能是 cust_infotxn_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. 問題和欄位名稱對不起來

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

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

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

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

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

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

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

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

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 開發集,共 1,534 個自然語言問題、11 個 SQLite 資料庫,所有主要比較都固定使用 Gemini 2.5 Flash-Lite。這樣可以盡量讓差異來自方法本身,而不是因為某個方法換了更強的生成模型。

我主要觀察兩個指標:

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

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

方法執行準確率總 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

62.58% 代表 1,534 題中有 960 題得到正確執行結果。相較之下,基礎方法答對 886 題;AAP-SQL 多答對 74 題,準確率提高 4.82 個百分點,但平均每題只從約 1,427 增加到 2,164 Token。

和流程較複雜的 DIN-SQL、CHESS 相比,AAP-SQL 的準確率在這次固定條件的本地重現中略高,總 Token 則分別減少 92.98% 和 92.18%。換句話說,它不是單純追求最低用量,而是希望在「太少資訊容易答錯」與「反覆推理成本太高」之間找到較好的平衡。

不過,這些準確率差異沒有進行統計顯著性檢定,因此不能只憑小幅數字差距宣稱某個方法在所有情況下都比較好。這裡比較能支持的結論是:在相同資料、相同模型與相同執行環境下,選擇性補充資訊確實呈現出有競爭力的準確率與明顯較低的 Token 用量。

哪些設計真的有幫助?

為了確認效果不是偶然,我也做了「消融實驗」:每次拿掉一個元件,再重新測試。它就像從機器上逐一拆下零件,觀察性能下降多少。

在有外部線索的條件下:

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

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

它還沒有解決哪些問題?

AAP-SQL 把錯誤題數從基礎方法的 648 題降到 574 題,但它仍不是完整答案。

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

其次,目前最難的仍是同時牽涉多種判斷的問題。只需要修正欄位、文字值或 JOIN 的題目有改善,但同時需要複雜結構、篩選和關聯推理的錯誤,並沒有明顯下降。核心欄位檢索也仍有進步空間:選出的欄位能完整涵蓋標準答案所需欄位的比例只有 38.20%。因為系統仍保留完整 Schema,這不代表其餘題目一定無法回答,但顯示「找到所有關鍵欄位」仍然很困難。

另外,目前的文字值對齊主要處理帶引號的精確內容,還不擅長錯字、近義詞、未加引號的名稱、日期或單位轉換。實驗也集中在 BIRD 開發集、SQLite、單輪問題與一個主要生成模型;Token 統計只計算線上大型語言模型用量,不包含離線訓練、資料庫查詢延遲和系統維運成本。

結語

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

我的研究驗證了一個相對務實的方向:保留資料庫全貌,再把有限的資源用在最相關的欄位、真實資料值與表格關係上。 在這次實驗中,它比單純提示更準確,也比多階段方法節省大量 Token。

未來還需要改善模糊值對齊、核心欄位召回與複合查詢推理,並在更多資料庫、模型和企業環境中驗證。不過我相信,若要讓自然語言真正成為資料庫的入口,先幫模型找到「現在最需要知道的資訊」,會是很重要的一步。