土法炼钢兴趣小组的算法知识备份

【数据库研究前沿】Text-to-SQL 与 Agentic Query:DIN-SQL、C3、DAIL-SQL 工程复盘

文章导航

分类入口
database
标签入口
#text-to-sql#din-sql#c3#dail-sql#spider#bird#agent#schema-linking

源码下载

本文相关源码已整理,共 1 个文件。

打开下载目录 →

目录

2023 年前后,大语言模型(Large Language Model, LLM)把 Text-to-SQL 从”准确率 50% 上下、只能玩 demo”推到了”在 Spider dev 上 85%+、在 BIRD 上也接近 70%“的量级。这背后的工作既不是”把 GPT-4 塞进 chatbox 让它写 SQL”,也不是”再训一个微调 BERT”,而是一套从 schema linking、decomposition、self-consistency 到 agent 回路的系统工程。

本文上半把 DIN-SQL、C3、DAIL-SQL 三条代表性路线讲清楚,重点是它们在工程上能被复用的模块;下半给出一个不依赖付费 API、纯离线可跑的最小 Text-to-SQL 闭环 demo,演示 schema linking + SQL 校验 + 执行回退的工程骨架。


一、为什么 Text-to-SQL 是数据库问题,而不只是 NLP 问题

1.0 十年回顾

Text-to-SQL 不是一个新问题。早在 2017 年前后 Salesforce / Yale / IBM 就有一批 seq2seq 时代的工作(SQLNet、TypeSQL、SyntaxSQLNet),在 WikiSQL、Spider 上做分数。这些工作共享一个共识:跨 domain 的泛化是瓶颈,模型在训练 schema 上很好,换一个 schema 就退化。

2021 年前后随着 T5 / BART 这类预训练 seq2seq 模型出现,准确率被推高了一个台阶;2023 年 GPT-4 出现后又是一个台阶,在 Spider 上冲到 85%+。这条曲线看起来像”模型越大越好”,但它的代价在可控性而非准确率上:模型越大,错起来越像一本正经地瞎说,越难靠调参修正。

这就是为什么 2024 年以后的工程实践开始回归”流水线思维”:让确定性模块负责确定性的事(schema linking、validation、execution),让 LLM 只负责不确定的事(自然语言理解、草稿生成)。

1.1 这类系统的核心权衡

任何 Text-to-SQL 产品都要在三个维度做权衡:

三者基本是相互制约的。做高覆盖率最容易(让 LLM 生 SQL 执行就行),做高准确率要严苛流水线,做高可解释性要产品化 UI。工程化 Text-to-SQL 的核心就是在这三者之间找甜点,而不是盲目追求 benchmark 分数

很多人第一反应是把 Text-to-SQL 归到 NLP 任务。2024 年后的共识是:它本质上是一个数据库接入层问题。因为:

所以 Text-to-SQL 在 2025 年的正确切入视角是:一个带 LLM 的查询中间层,而不是”让 AI 写 SQL”。

1.1 Spider 与 BIRD 两个基准

Spider 分数高不代表 BIRD 分数高。Spider 上 90%+ 的方案在 BIRD 上会跌到 50% 左右。原因是 BIRD 要求模型理解业务词汇映射和数据质量,这些在 Spider 里几乎没有。

判断一个 Text-to-SQL 方法是否真有工程价值,看它在 BIRD 上的表现,而不是 Spider。


二、DIN-SQL:把任务拆成子问题

DIN-SQL(Pourreza & Rafiei, NeurIPS 2023, “DIN-SQL: Decomposed In-Context Learning of Text-to-SQL with Self-Correction”)的核心观点朴素但有效:不要让 LLM 一步写完 SQL,把它拆

2.1 四阶段 pipeline

question ──▶ Schema Linking ──▶ Classification ──▶ SQL Generation ──▶ Self-Correction
              (找相关列)         (简单/嵌套/连接)      (按类模板)         (执行失败回改)

这四步在 2024 年之后几乎成为所有 production Text-to-SQL 系统的骨架,即使不用 LLM,用规则 + 小模型也是这一套流程。

2.2 DIN-SQL 的工程遗产

DIN-SQL 本身的论文里使用了 GPT-4,但它真正被广泛引用的不是分数,而是”先 schema link、再分类、再生成、再自检”这套流水线抽象。这套抽象对任何模型可迁移。


三、C3:强调 prompt 工程而不是 fine-tune

C3(Dong et al., arXiv 2023, “C3: Zero-shot Text-to-SQL with ChatGPT”)把注意力放到 prompt 设计:

C3 的贡献在于证明了 即使完全不 fine-tune、不额外训练,光靠 prompt 工程 + self-consistency,闭源 LLM 就能把 Spider 推到当时 SOTA。这对工业界是一个解压信号:Text-to-SQL 不需要自建模型训练流水线

3.1 Self-Consistency 的工程实现

question ──▶ prompt ──▶ LLM (sample k=5) ──▶ exec each ──▶ majority vote on rows
                                             │
                                             └─▶ 结果集做 set equality,而不是字符串比较

要点:投票必须在执行结果上,不能在 SQL 字符串上。一条正确 SQL 可以有几十种等价写法,字符串级投票几乎必定失败。


四、DAIL-SQL:把 example selection 做正确

DAIL-SQL(Gao et al., VLDB 2024, “Text-to-SQL Empowered by Large Language Models: A Benchmark Evaluation”)是一个系统性对比多种方法的综合工作,它自己提出的 DAIL-SQL 方法关注 few-shot example 的选择:

4.1 DAIL-SQL 的另一半:系统评测

论文把前几年 Text-to-SQL 工作做了一个公平对比,结论里对工程师有价值的几条:

2025 年之后,Qwen2.5-Coder、DeepSeek-Coder V2 等开源模型把小模型下沿也拉到了”能用”的水平,但这仍然需要配合完整的 DIN-SQL 式流水线。

4.2 别再追 benchmark 分数

一个对工程更实用的观察:Spider / BIRD 分数高低,与在你的真实 schema 上好不好用,几乎无关。原因很直接:

所以用 benchmark 选底座模型合理,用它决定”能不能上线”不合理。上线前一定要在自己 schema 的 100–500 条代表性问题上做真实回归。这部分工作没有捷径。


五、Agentic Query:plan-execute-verify

2024 年后的 Text-to-SQL 进一步 agent 化,核心模式是 plan-execute-verify

┌────────┐      ┌────────────┐     ┌──────────────┐
│ Planner│──▶   │ Schema/IDX │──▶  │  SQL Draft   │
└────────┘      └────────────┘     └──────┬───────┘
      ▲                                   │
      │    ┌────────────────┐             ▼
      └─── │ Verifier/Reflect│◀── exec on real DB
           └────────────────┘

Planner 不只出 SQL,还要决定:先 EXPLAIN、先 SELECT LIMIT 5 探数据、再决定最终 SQL 要不要分步。Verifier 在执行后检查行数、NULL 比例、outlier,不合理就回到 Planner。

代表工作包括 MAC-SQL(Wang et al., COLING 2024)、CHASE-SQL(Pourreza et al., arXiv 2024)以及大量开源 agent 框架。它们共享的工程要点:

5.1 Agent 架构的几个失败模式

把 agent 架构用在 Text-to-SQL 上,常见失败模式:

这些模式都在 2024 年后的若干 production postmortem 里重复出现,说明 Text-to-SQL 的工程护栏很像传统数据库的只读事务:限制越多,越稳定


六、工程落地:一个离线可跑的最小闭环

上半是研究线,下半换工程视角:如果我现在要给一个 PostgreSQL / SQLite 仓库接一个离线、可审计、可重放的 Text-to-SQL 层,最小骨架是什么?

这个最小骨架放在 demo/mini_text2sql.pySQLite + Mock LLM + 模板化 schema linking + SQL 校验 + 执行回退。它不依赖任何付费 API,任何拉下来的人都能直接跑。

6.1 Demo 的五个步骤

question
   │
   ▼
1. schema_linking(question, schema) ──▶ 候选表 / 列(规则 + LLM)
   │
   ▼
2. render_prompt(question, linked_schema, few_shots)
   │
   ▼
3. llm.generate(prompt) ──▶ SQL 草稿(Mock 模式:按模板返回)
   │
   ▼
4. validate_sql(sql) ──▶ 语法检查 + 只读校验 + 表/列存在性
   │
   ▼
5. execute_or_retry(sql)  ──▶ 执行;失败把 stderr 回喂 LLM,最多 3 轮

6.2 Mock LLM:让 demo 离线可跑

Demo 默认用一个 MockLLM:它根据 question 关键词挑一个模板返回 SQL,参数化 schema。这足够演示整条流水线的形状,对读者理解工程骨架最有价值。接真实 LLM 只需要替换 MockLLM 为一个调用 openai / ollama / llama-cpp-python 的实现。

MockLLM 的核心代码非常短:

class MockLLM:
    """Deterministic LLM stub: match keywords → template SQL."""

    def generate(self, prompt: str, question: str, schema: dict) -> str:
        q = question.lower()
        if "how many" in q or "count" in q:
            table = _best_table_match(q, schema)
            return f"SELECT COUNT(*) FROM {table};"
        if "average" in q or "avg" in q:
            table, col = _best_numeric_column(q, schema)
            return f"SELECT AVG({col}) FROM {table};"
        if "top" in q:
            table, col = _best_numeric_column(q, schema)
            return f"SELECT * FROM {table} ORDER BY {col} DESC LIMIT 5;"
        # fallback: full scan
        table = next(iter(schema))
        return f"SELECT * FROM {table} LIMIT 10;"

6.3 SQL 校验:不是可选

校验层常被忽略但非常关键,它挡住 LLM 最常见的三类错误:

Demo 里用 sqlglot 实现:

from sqlglot import parse_one, exp

def validate(sql: str, schema: dict) -> list[str]:
    errors: list[str] = []
    try:
        tree = parse_one(sql, read="sqlite")
    except Exception as e:
        return [f"parse error: {e}"]
    if not isinstance(tree, (exp.Select, exp.With)):
        errors.append("only SELECT / WITH allowed")
    for tbl in tree.find_all(exp.Table):
        if tbl.name not in schema:
            errors.append(f"unknown table: {tbl.name}")
    for col in tree.find_all(exp.Column):
        t = col.table or _single_table(tree)
        if t and t in schema and col.name not in schema[t]:
            errors.append(f"unknown column {t}.{col.name}")
    for j in tree.find_all(exp.Join):
        if not j.args.get("on"):
            errors.append("join without ON clause")
    return errors

6.4 执行 + 回退

def answer(question: str, db: sqlite3.Connection, llm, schema: dict, max_rounds=3):
    last_error = None
    for _ in range(max_rounds):
        prompt = render_prompt(question, schema, last_error)
        sql = llm.generate(prompt, question, schema)
        errs = validate(sql, schema)
        if errs:
            last_error = "; ".join(errs)
            continue
        try:
            rows = db.execute(sql).fetchall()
            return sql, rows
        except sqlite3.Error as e:
            last_error = str(e)
    return None, None

这个模式可以直接套到真实 LLM 上,只是把 MockLLM.generate 换掉。最大 3 轮的预算是经验值——多于 3 轮意味着 schema linking 或 prompt 有结构性缺陷,需要人工介入,而不是继续让 LLM 自我纠错。

6.5 运行方式

cd demo
pip install sqlglot==25.6.1
python mini_text2sql.py

脚本会:

  1. 在内存里建 orders(id, amount, status)customers(id, name, country) 两张表。
  2. 跑一组预置问题("how many orders?""average amount""top customers by amount"、一个故意让模型幻觉的问题)。
  3. 打印每个问题的:linked schema、SQL、校验结果、执行结果。

故意失败的那个问题会触发 retry,用于让读者看到 validate + retry 是如何真的起作用的。

6.6 上线前要准备的四件事

这四件事每一件都比”换个更强的模型”更决定能否真正上线。反过来讲,很多团队踩的坑都是”光追模型,忘了工程基础设施”。

6.7 与其他文章的互链


七、一个更完整的参考架构

前面的 demo 偏骨架。把它扩展成一个可上线的参考架构大致长这样:

    用户问句
        │
        ▼
┌─────────────────────┐
│  输入守卫 guard     │ 禁用关键词、长度上限、PII 过滤
└─────────────────────┘
        │
        ▼
┌─────────────────────┐
│  schema linking      │ 向量召回 table/col + 规则强制加 tenant 列
└─────────────────────┘
        │
        ▼
┌─────────────────────┐
│  SQL 生成           │ LLM + few-shot + CoT
└─────────────────────┘
        │
        ▼
┌─────────────────────┐
│  sqlglot 校验        │ 语法 / 只读 / 权限 / join 条件
└─────────────────────┘
        │
        ▼
┌─────────────────────┐
│  cost guard          │ EXPLAIN,估计行数,> 10M 走 LIMIT 强制
└─────────────────────┘
        │
        ▼
┌─────────────────────┐
│  只读执行 read only  │ RLS-enabled 连接、非主库、超时 3s
└─────────────────────┘
        │
        ▼
┌─────────────────────┐
│  结果核查 / 摘要     │ NULL 比例、行数、outlier 判定
└─────────────────────┘
        │
        ▼
      答案 + SQL + provenance

几个值得展开的点:

7.1 RLS 是最后一道墙

Row-Level Security 让你可以给不同 tenant / role 设置行级可见性。一个 Text-to-SQL 连接必须用有 RLS policy 限制的账号,哪怕 LLM 幻觉出 SELECT * FROM orders 全表扫描,也只会看到当前租户的行。这条比任何 sqlglot 规则都更硬。

7.2 Cost Guard:EXPLAIN 做体检

LLM 生成的 SQL 常见问题是 “语法对、但跑起来全表扫 100 亿行”。上线前应该强制:

EXPLAIN (FORMAT JSON, VERBOSE) <generated sql>;

拿到估计 plan_rows,超过阈值就拒绝,或者自动追加 LIMIT N。这和 Azure SQL 自动 tuning 里的 “what-if analysis” 思路一致。

7.3 Provenance 是产品化的关键

一个 Text-to-SQL 系统要上线,必须给用户看到”这个数字是怎么算出来的”。最小实现是把生成的 SQL、涉及到的表、执行时间都显示在 UI 上。用户才能判断”这是一个对我业务有意义的答案”还是”LLM 编出来的数字”。

7.4 一张对比表:NL2SQL 产品化的三层选型

层级 方案 优点 缺点 适用
L1 纯按钮 BI 预置 query 零出错 灵活性差 面向 non-tech 用户
L2 模板 + 槽填充 受限 NL + 参数 可控、低成本 需要预建模板 固定报表
L3 Text-to-SQL LLM pipeline 最灵活 错误率 10%+ 探索性分析

真实产品几乎都是 L1 + L2 + L3 混合。L3 只处理”模板覆盖不到的问题”,并且要让用户意识到这是 “AI 辅助”,不是”权威回答”。

八、一个具体对比:L2 模板 vs L3 Text-to-SQL

为了让前面的分层讨论更具体,用一个常见需求对比:“上周销售额排名前 10 的客户”

8.1 L2 模板方案

DBA 预先写好模板 SQL:

-- template: top_customers_by_amount_in_period
SELECT c.name, SUM(o.amount) AS total
FROM orders o JOIN customers c ON o.customer_id = c.id
WHERE o.created_at BETWEEN :start AND :end
  AND o.status = 'paid'
GROUP BY c.name
ORDER BY total DESC
LIMIT :top_n;

用户通过 UI 填写”时间范围 = 上周” 和 “top_n = 10”。运行时把 :start:end 换成具体时间戳。

优点:100% 正确,p99 可预期,审计容易;缺点:模板需要人写。 ### 8.2 L3 Text-to-SQL 方案

用户直接问”上周销售额排名前 10 的客户”。LLM pipeline:

  1. Schema linking 找到 orderscustomers 两张表。
  2. 识别时间词”上周” → BETWEEN date_trunc('week', now() - interval '1 week') AND date_trunc('week', now())
  3. 识别”销售额” → SUM(orders.amount),需要判断是否过滤 refunded。
  4. 生成 SQL,校验,执行。

优点:灵活,用户不需要学习 UI;缺点:第 3 步最容易出错(销售额到底含不含退款?LLM 不知道业务口径),一旦出错数据是错的但看起来对。

8.3 推荐混合姿态

真实产品里,高频 20 个问题走 L2,长尾走 L3,L3 的回答必须标注 “AI 生成,请核对”。L2 + L3 的切换判断可以用一个”问题 → 模板”的语义路由器(本质也是个小 embedding 检索器)完成。这个姿态融合了两者的优点,是 2025 年之后”数据对话”类产品的主流做法。

九、再看数据契约

一个最容易被忽视、但对生产 Text-to-SQL 质量决定性的话题:数据契约(data contract)

9.1 什么是数据契约

数据契约指”这张表这个列的业务语义是什么”的形式化描述。例如:

table: orders
columns:
  amount:
    description: "订单总金额,含税,以人民币分为单位"
    unit: RMB_cents
    nullable: false
  status:
    description: "订单状态"
    enum: [pending, paid, refunded, canceled]
    semantic:
      paid: "真正计入 GMV 的成交"
      refunded: "已退款,不计入 GMV"

9.2 契约对 Text-to-SQL 的价值

没有契约时,LLM 必须通过列名猜业务语义,错是必然的。有了契约,schema linking 阶段可以把契约的 description / semantic 字段塞进 prompt,LLM 准确率明显提高。

更重要的是:契约让错误可定位。当 LLM 生成的 SQL 把 status = 'paid' 漏掉时,你可以清楚指出”它违反了 GMV 定义契约”,而不是泛泛说”模型错了”。

9.3 契约维护的现实

2025 年之后,dbt / Spectacles / OpenMetadata 这类工具把数据契约做成工程一等公民。对想上 Text-to-SQL 的团队而言,先把契约建起来比先上 LLM 更重要。没有契约的 Text-to-SQL 就像没有 schema 的数据库——能用但早晚出事。

十、与仓库其他文章的互链补充

十一、对产品团队的建议

最后给想把 Text-to-SQL 做成产品的团队几条提炼建议:

这些建议都不惊艳,但它们决定一个项目能不能活过第一年。技术团队常犯的错误是低估”非技术问题”的比重,而产品化 Text-to-SQL 本质上是一个 2/3 数据治理 + 1/3 模型工程的问题。

十二、一个常被问的问题:训 / 不训模型

团队选型时最常问的一题是:要不要 fine-tune 一个自己的 Text-to-SQL 模型?

12.1 Fine-tune 的代价

fine-tune 一个能用的 Text-to-SQL 模型(假定基座是 7B–70B 开源)要:

总成本常常是几人月到几人年。

12.2 不 fine-tune 的替代

用闭源 API + 好的 prompt pipeline + 强 retrieval,很多场景能做到 fine-tune 90% 的效果,而运维成本低一个量级。

经验法则:

12.3 真正值得投入精力的三件事

无论 fine-tune 与否,以下三件事的 ROI 都很高:

2024–2026 年的经验越发清晰:Text-to-SQL 的护城河在数据与评估,不在模型。把预算分配对,项目成功率会高很多。

十三、为什么这个问题值得长期跟踪

写这么多,最后想讲一下为什么 Text-to-SQL 在 2026 年仍然是个值得长期跟踪的方向。

这三点加在一起,使得 Text-to-SQL 即使长期停留在 “70% 准确率 + 人类兜底” 的状态,也依然有长期产品价值。它推动的副产品(数据治理、评估体系、可观测性)本身就对组织有价值,并不需要 “完美准确” 才值得投入。

所以,即使你不打算马上上线一个 Text-to-SQL 产品,把它作为一个思维训练场,定期看新论文、做小 demo、和数据治理团队对话,都是长期划算的投资。最后要提醒一句:这个方向的社区节奏非常快,每个月都有新 benchmark、新 prompt 技巧、新 agent 框架出现。保持跟踪但不盲目追新,是 2026 年的工程智慧。

十四、一个小结尾:为什么这一篇放了一个能跑的 demo

系列里大部分文章是讨论概念和工程建议,这一篇单独配了一个离线 demo。原因有三:

希望读者拉下 demo 跑一遍后,对 Text-to-SQL 工程化的实际形态有一个”动过手”的直觉。真正产品化时可以把 MockLLM 换成 OpenAI / Ollama 客户端,其它骨架基本不变。这种”骨架稳 + 模型可替换”的姿态,是 2026 年做 AI 应用最有生命力的工程哲学。

参考文献

  1. T. Yu et al., “Spider: A Large-Scale Human-Labeled Dataset for Complex and Cross-Domain Semantic Parsing and Text-to-SQL Task”, EMNLP 2018, https://arxiv.org/abs/1809.08887.

  2. J. Li et al., “Can LLM Already Serve as A Database Interface? A BIg Bench for Large-Scale Database Grounded Text-to-SQLs (BIRD)”, NeurIPS 2023, https://arxiv.org/abs/2305.03111.

  3. M. Pourreza and D. Rafiei, “DIN-SQL: Decomposed In-Context Learning of Text-to-SQL with Self-Correction”, NeurIPS 2023, https://arxiv.org/abs/2304.11015.

  4. X. Dong et al., “C3: Zero-shot Text-to-SQL with ChatGPT”, arXiv:2307.07306, 2023, https://arxiv.org/abs/2307.07306.

  5. D. Gao et al., “Text-to-SQL Empowered by Large Language Models: A Benchmark Evaluation”, PVLDB 17(5), 2024, https://www.vldb.org/pvldb/vol17/p1132-gao.pdf.

  6. B. Wang et al., “MAC-SQL: A Multi-Agent Collaborative Framework for Text-to-SQL”, COLING 2024, https://arxiv.org/abs/2312.11242.

  7. M. Pourreza et al., “CHASE-SQL: Multi-Path Reasoning and Preference Optimized Candidate Selection in Text-to-SQL”, arXiv:2410.01943, 2024, https://arxiv.org/abs/2410.01943.

  8. tobymao/sqlglot, GitHub, https://github.com/tobymao/sqlglot. 本文 demo 中使用的 SQL parser / AST 库。

  9. AlibabaResearch/DAMO-ConvAI, GitHub, https://github.com/AlibabaResearch/DAMO-ConvAI. DAIL-SQL 等工作的开源实现。


上一篇: 自治数据库十年回顾:Peloton、NoisePage、OtterTune 到云原生 auto-tuning 下一篇: 数据库作为 LLM 记忆体:语义缓存、RAG 与一致性

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。


By .