Text-to-SQL(文本转SQL)是利用大语言模型(LLM)将用户的自然语言问题转化为关系型数据库可执行的 SQL 查询的技术。这不仅是简单的”翻译”,更是语义解析、Schema 理解和逻辑推理的综合过程。 以下是使用 LLM 实现 Text-to-SQL 的具体深度分析,从基础原理到企业级架构的递进拆解。
一、 基础实现:Prompt Engineering (提示词工程)
最基础的实现方式是直接将数据库结构和用户问题喂给 LLM,让其生成 SQL。 核心要素:
- DDL (Data Definition Language): 提供
CREATE TABLE语句,让 LLM 知道表名、字段名、字段类型。 - 元数据: 提供外键关系,让 LLM 知道表与表如何 JOIN。
- Few-Shot (少量示例): 提供几个相似问题的 SQL 样例,极大地提升准确率。
- 用户问题: 自然语言输入。 基础 Prompt 模板:
你是一个资深的数据分析师,请根据以下数据库表结构,将用户的问题转化为 SQL。
【数据库方言】: PostgreSQL
【表结构】:
CREATE TABLE orders (order_id INT, customer_id INT, order_date DATE, amount DECIMAL);
CREATE TABLE customers (customer_id INT, name VARCHAR, city VARCHAR);
【参考示例】:
问题: 查询北京客户的订单总额
SQL: SELECT SUM(o.amount) FROM orders o JOIN customers c ON o.customer_id = c.customer_id WHERE c.city = '北京';
【用户问题】: 2023年消费最多的前3名客户是谁?
【输出要求】: 只输出纯 SQL,不要解释。
局限性: 真实业务中,数据库往往有几百上千张表,DDL 全文远超 LLM 的上下文窗口;即使塞得进去,LLM 也会出现”迷失在中间”现象,生成大量幻觉 SQL(不存在的字段、错误的 JOIN)。
二、 进阶实现:RAG + Schema Linking (检索增强与模式链接)
为了解决大库表的问题,必须引入 Schema Linking(模式链接),即:只把和用户问题相关的表结构喂给 LLM。 具体实现步骤:
- 向量化元数据:
将表名、字段名、字段注释、甚至是高频枚举值,拼接成文本,进行 Embedding 存入向量数据库。
例:
表: orders; 字段: amount; 注释: 订单金额 - 元数据检索:
用户输入”上个月的总营收”,先去向量库检索出最相关的 Top-K 表和字段(如
orders.amount,orders.order_date)。 - 业务知识注入:
很多隐含逻辑 LLM 是不知道的。比如”营收”在你们公司其实对应的是
amount > 0的总和。需要在检索时匹配”业务术语字典”一并注入 Prompt。 - 构建动态 Prompt: 仅将检索出的表结构和业务规则拼入 Prompt,发给 LLM 生成 SQL。
三、 高阶实现:Agentic Workflow (智能体工作流)
复杂的查询(多表关联、嵌套子查询、分组排序逻辑)单次 LLM 调用很难保证正确率。当前最先进的方案是 Plan-and-Solve Agent(规划与解决智能体)。 核心思想: 让 LLM 像人一样思考,先拆解问题,再逐步写 SQL。 工作流设计:
- Decomposer (分解器):
用户问:“上个月每个地区的客单价对比环比增长率”。
Agent 先拆解逻辑:
- 上个月每个地区的客单价 = 总金额 / 订单数
- 上上个月…(环比)
- 计算增长率
- Selector (选择器 / Schema Linker): 针对拆解后的子任务,分别去向量库召回需要的表和字段。
- Generator (生成器):
基于召回的 Schema,逐步生成 CTE (Common Table Expressions,即
WITH AS语法) 或者分步生成子 SQL。 - Executor & Reflector (执行器与反思纠错): 【这是提升成功率的关键】
- 将生成的 SQL 在测试库中执行(Dry-run)。
- 如果数据库报错(语法错误、字段不存在),将报错信息 + 原始 SQL + 问题重新丢给 LLM,让它自我修正。
- 如果执行成功但返回空结果,可让 LLM 判断是数据真的为空,还是逻辑写错了。
四、 企业级落地痛点及解决方案
在真实生产环境中,仅靠上述架构仍不够,以下是最常见的坑及解法:
| 痛点 | 描述 | 解决方案 |
|---|---|---|
| 幻觉问题 | LLM 编造出不存在的字段,或随意 JOIN 毫无关联的表。 | 1. 约束 Prompt:严禁使用不在提供列表内的字段。 2. 语法校验:用 sqlglot 等工具解析生成的 SQL AST,检查引用的表/列是否全在给定 Schema 内。 |
| 复杂数据计算 | 涉及同比/环比、时间滑动窗口、金额格式化。 | LLM 擅长逻辑但不擅长精确计算。允许 LLM 使用 Python UDF,或在 Prompt 中提供计算类辅助函数。 |
| 超大体量数据库 | 表数量上千,且表名/字段名全是拼音缩写(如 yhzhb)。 | 1. 强制要求维护数据字典(注释比代码更重要)。 2. 引入中间层 View(视图),将底层复杂星型模型抽象为宽表,让 LLM 基于宽表查询。 |
| 数据安全与隐私 | 不想把真实的表结构和数据发送给 OpenAI 等公有云。 | 1. 部署本地开源模型(如 DeepSeek-Coder, Llama-3, CodeLlama)。 2. 只传 DDL,绝对不传真实数据行。 |
| SQL方言差异 | 生成 MySQL 语法的 SQL,但底层数据仓库是 Hive/Spark。 | 在 Prompt 中明确指定方言,并在 Executor 环节使用对应方言的引擎做 Dry-run。 |
五、 现成的开源框架推荐
不要从零手写,推荐使用或参考以下成熟的开源 Text-to-SQL 框架:
- Vanna.ai (强烈推荐)
- 原理: 基于 RAG 的 Text-to-SQL。
- 优点: 极其易用,支持通过对话积累训练数据(DDL + 历史 QA 对),支持多种大模型和数据库,自带可视化界面。
- LlamaIndex / LangChain
- 原理: 提供基础的 Chain 和 Agent 组件。
- 优点: 灵活,适合深度定制 Agentic Workflow。LlamaIndex 的
SQLDatabase结合ReAct Agent是经典搭配。
- DB-GPT
- 原理: 专为数据库领域设计的多模型框架。
- 优点: 支持多 Agent 协作,数据隐私保护(本地模型部署),内置丰富的 Schema 检索和优化策略。
六、 总结与评估标准
怎么评估你的 Text-to-SQL 系统好不好?业界通常看两个指标:
- Execution Accuracy (执行准确率): 生成的 SQL 执行出的结果,和人工编写 SQL 执行出的结果是否一致。(关注业务结果,推荐优先看重)
- Exact Match (完全匹配): 生成的 SQL 字符串和标准答案是否一模一样。(要求极严,实际中很难达到,因为 SQL 写法多样) 最终落地建议: 不要幻想一个 LLM 能解决所有复杂查询。最好的架构是”人机协同”:LLM 负责生成 80% 的基础查询和初版复杂查询,剩下 20% 无法绕过的逻辑死角,允许用户通过 UI 微调 SQL,并将微调后的正确 SQL 反哺进向量库作为 Few-shot,实现系统的越用越聪明。
要提高 LLM 生成 SQL 的准确性,核心不是”换更大的模型”,而是把系统做成**“先理解 Schema → 再逐步生成 → 然后校验自纠 → 不断反馈学习”的闭环**。当前业界主流的 DIN-SQL、C3、DAIL-SQL 等工作,基本都是这套”Schema Linking + 分解 + 自检 + Agent 回路”的工程思路。 下面按”从易到难、从提效到闭环”给你一个可落地的路线图,每一步都写清楚:为什么这么做、怎么做、做到什么程度。
一张总览图:提高 SQL 准确性的 5 层路线
flowchart LR
A[1. Schema & 元数据治理] --> B[2. Prompt / RAG 优化]
B --> C[3. 分步生成与任务分解]
C --> D[4. 校验与自纠错回路]
D --> E[5. 反馈与持续学习]
A:::layer
B:::layer
C:::layer
D:::layer
E:::layer
classDef layer fill:#f5f5f5,stroke:#555,stroke-width:1px;
- 第 1 层:先把”数据库理解”搞清楚(治理 Schema、注释、外键、业务术语)。
- 第 2 层:让 LLM 只看相关 Schema,减少干扰(Prompt + RAG)。
- 第 3 层:不要让它一步写完复杂 SQL,先拆问题再生成(DIN-SQL 式流水线)。
- 第 4 层:生成后必须执行 + 校验 + 自动修正(自纠错回路)。
- 第 5 层:把好例子和坏例子都沉淀下来,持续训练 RAG / 小模型(用得越久越准)。
1. Schema & 元数据治理:先让”数据库自己说得清”
LLM 写错 SQL,很大一部分是因为Schema 本身就说不清: 缺注释、缺外键、字段名拼音缩写、业务逻辑藏在代码里而不是在表结构里。 实测有文章审计了 47 个企业库,发现 83% 的生产 DDL 不能直接用于 RAG 训练。
1.1 必做:增强 DDL & 注释
对每张表、每个字段,至少补齐这些东西:
- 表:中文业务名、业务含义、主用场景(如”订单事实表,用于统计销售额”)
- 字段:中文业务名、是否主键、是否外键关联哪张表、常见枚举值(如
status: 已支付/已取消) - 外键:显式写出来,哪怕数据库没建外键约束,也要在 DDL 注释或元数据里补上 示例增强后的表描述(可用于 RAG 训练 / Prompt):
-- 增强后的 orders 表定义
CREATE TABLE orders (
id INT PRIMARY KEY COMMENT '订单主键',
customer_id INT COMMENT '客户ID,关联 customers.customer_id',
product_code VARCHAR COMMENT '产品业务编码,需关联 product_sku.product_code 才能拿到产品名',
amount DECIMAL(10,2) COMMENT '订单金额,单位:元',
order_status VARCHAR COMMENT '订单状态:已支付/已取消/已退款',
create_time TIMESTAMP COMMENT '下单时间'
) COMMENT '订单事实表,用于统计销售额、订单数等';
Vanna 实战文章管这叫”DDL 增强技术”,用 SQLGlot 之类工具自动补外键、补注释,再喂给 RAG。
1.2 建议做:统一业务术语表(语义层)
- 维护一个”业务词 → 字段映射”的字典,例如:
- “销售额” →
orders.amount(且order_status = '已支付') - “GDP 增速” →
index_value WHERE index_id = 5 AND report_period = '2023Q1'之类
- “销售额” →
- 在 RAG 检索时,把”业务词 + 字段映射”一起检索出来,塞进 Prompt,这能大幅降低”词不对列”的问题。 效果: 治理得好,单靠这一层 + 简单 Prompt,就能在真实业务场景里把准确率拉高一个档次,后面所有层都是在”治理好的 Schema”上再叠加收益。
2. Prompt / RAG 优化:只给 LLM 看必要的 Schema
大库(几百张表)如果全量 DDL 塞进 Prompt,LLM 会”迷失在中间”,幻觉严重。 主流做法是:Schema Linking + RAG 检索,只把和问题相关的表/列喂给 LLM。
2.1 向量化元数据:表名、字段名、注释、示例值
对每张表,构造一段文本,例如:
表: orders
字段: id(int, 主键), customer_id(int, 关联 customers.id), amount(decimal, 订单金额), order_status(varchar, 订单状态:已支付/已取消/已退款)
注释: 订单事实表,用于统计销售额、订单数
高频值: order_status=已支付/已取消/已退款
然后 Embedding 进向量库(Chroma / Qdrant / PGVector 等),Vanna 就是这么做的。
2.2 检索 Top-K 表 + 字段 + 业务规则
用户输入问题后:
- 先用向量检索 Top-K 表和字段;
- 同时检索”业务术语表”中匹配的映射规则;
- 把检索结果拼成 Prompt,格式类似:
【数据库方言】: PostgreSQL
【相关表结构】:
CREATE TABLE orders (...);
CREATE TABLE customers (...);
【业务规则】:
- "销售额" 对应 orders.amount 且 order_status = '已支付'
- "客户数" 对应 COUNT(DISTINCT customer_id)
【参考示例】:
Q: 上月销售额?
SQL: SELECT SUM(amount) FROM orders WHERE order_status = '已支付' AND create_time >= ...
【用户问题】: 2025 年 Q1 各区域销售额
【要求】: 只输出可执行 SQL,不要解释。
2.3 Prompt 工程细节(C3 的一些经验)
C3 这篇工作表明,即便不微调,只靠 Prompt 也能把 Spider 推到当时的 SOTA:
- Clear Prompting:用完整
CREATE TABLE而不是简化版,让 LLM 看到完整约束。 - Calibration bias:显式约束 LLM 的”坏习惯”,例如:
- “不要滥用 DISTINCT”
- “不要无条件 JOIN 所有表”
- “只使用上面列出的表和字段,不要编造”
- Consistency:对同一问题多次采样 SQL,用执行结果做多数投票,而不是字符串相似度。 效果: Schema 治理 + RAG + 合理 Prompt,是”性价比最高”的一层,很多场景做到这层就够用了。
3. 分步生成 & 任务分解:DIN-SQL 式流水线
复杂查询(多 JOIN、嵌套子查询、分组聚合)一步写完极容易出错。 DIN-SQL 的核心贡献就是:不要让 LLM 一步写完 SQL,把它拆成子任务。
3.1 四阶段 Pipeline(DIN-SQL 骨架)
question → Schema Linking → Difficulty Classification → SQL Generation → Self-Correction
- Schema Linking:让 LLM 读 schema + 问题,输出相关表和列,提前剪枝。
- Difficulty Classification:把问题分为 easy / non-nested complex / nested complex,每类用不同模板 / few-shot。
- SQL Generation:按类别给不同的 CoT Prompt,生成 SQL。
- Self-Correction:执行 SQL,失败或结果可疑时,把错误信息喂回 LLM 修正。 你可以不照搬论文实现,但保留这个”先 Schema Link、再分类、再生成、再自检”的流水线思想,对任何模型都适用。
3.2 工程上怎么”拆”
一个实用做法是:把”写 SQL”拆成 3 个 LLM 调用:
- 调用 1:Schema Linking + 分解
- 输入:问题 + 全量 Schema(或 RAG Top-K)
- 输出:需要用到的表/列、查询结构(是否有嵌套/多 JOIN)、子问题拆分
- 调用 2:草稿 SQL
- 输入:只给上一步选出的表/列 + 子问题
- 输出:带注释的 CTE 或分步 SQL
- 调用 3:整合 & 重写
- 输入:草稿 SQL + 约束(不要 DISTINCT、不要多余 JOIN)
- 输出:最终可执行 SQL 效果: 分步生成可以显著降低复杂查询的错误率,是 DIN-SQL 等方法在 Spider/BIRD 上冲到 80%+ 执行准确率的关键。
4. 校验与自纠错回路:生成完一定要”跑一下”
DIN-SQL、C3、DAIL-SQL 的工程复盘都强调一点:SQL 校验 + 执行回退不是可选,是必须。
4.1 静态校验:语法 & Schema 约束
用 sqlglot 或数据库自带的 parser 做两件事:
- 语法检查:是否能解析成 AST;
- Schema 检查:
- SQL 中引用的表/列是否全在给定的 Schema 内;
- JOIN 条件是否用了外键或明显关联字段;
- 是否有”莫名其妙”的 DISTINCT / GROUP BY。 不满足就自动修正或打回让 LLM 重写。
4.2 动态校验:在真实库上 Dry-run
- 用只读连接在测试库 / 副库上执行 SQL;
- 捕获异常:语法错、字段不存在、权限问题等;
- 把错误信息 + 原始 SQL + 问题一起返回给 LLM,让它修正(DIN-SQL 的 Self-Correction 思路)。
4.3 结果级校验:空结果 / 异常值检测
- 执行成功但返回空:让 LLM 判断是”真的没数据”还是条件写错;
- 对聚合结果做合理性检查(销售额是否为负、是否远远超出历史范围)。
4.4 Self-Consistency:多次采样 + 执行结果投票
C3 的做法:多次生成 SQL,用执行结果等价类做多数投票,而不是字符串匹配:
问题 → 采样 N 条 SQL → 分别执行 → 按结果分组 → 选最大组的一条 SQL
这在工程上非常实用,对”语义正确但写法不同”的 SQL 非常有帮助。 效果: 校验 + 自纠错是”把准确率从 70% 拉到 90%“的关键,几乎所有生产级 Text-to-SQL 系统都会做这一层。
5. 反馈 & 持续学习:用得越久,系统越准
Vanna 这类 RAG2SQL 框架的一个重要特性是”自学习”:
- 用户确认 SQL 正确 → 自动把”问题 + SQL + DDL”存入向量库;
- 用户修正 SQL → 把修正后的版本作为新的训练样本;
- 长期积累后,RAG 检索到的例子越来越贴合业务,准确率自然上升。
5.1 建议的反馈闭环
- 前端 UI:允许用户点”这 SQL 不对 / 我改一下”;
- 后端记录:
- 原始问题
- LLM 生成的 SQL
- 用户修改后的 SQL
- 执行结果是否一致
- 定期回流:
- 把成功的 QA 对加入 RAG;
- 把典型错误做成”反模式”规则,加到 Prompt / 校验脚本里。
5.2 数据集质量:执行一致性(Execution Consistency)
研究显示,像 Spider/BIRD 这样的数据集中,>30% 的 NL–SQL 映射其实是错的。 SQLDriller 这类工作提出用”执行一致性”来清洗训练数据:同一问题生成的多条 SQL,执行结果必须一致。 对你来说,可以:
- 对内部积累的 QA 对,用”执行结果是否一致”做过滤;
- 只把结果稳定的 QA 对喂给 RAG / 微调模型。 效果: 这一层是”让系统越用越好”的关键,也是企业场景和开源 Demo 的最大差异点。
6. 模型 & 架构选型:别一上来就想着微调
从 2023–2025 的工程实践看,微调不是第一选择,原因是:
- 标注成本高(SQL 易写难评,执行一致性很难保证);
- 模型更新快,微调结果很快过时;
- 现有 LLM + RAG + 分解 + 自纠错,已经能在很多业务上达到 80%+ 执行准确率。 更推荐的路线:
- 先:Schema 治理 + RAG + Prompt + 分步生成 + 自纠错;
- 再:如果仍有明显领域模式(比如某类查询总是错),再考虑:
- 用开源 LLM(DeepSeek-Coder、Llama 3 等)做少量监督微调;
- 或训练一个”Schema Linking 小模型”专门负责选表/列,把问题简化。
7. 一个落地优先级清单(按投入产出排序)
如果你现在有一个 Text-to-SQL 系统,想”花最少时间把准确率拉上去”,可以按这个顺序做:
- 补注释 + 外键 + 业务术语表(Schema 治理) → 见效快,成本最低。
- 加 RAG:只给 LLM 看相关表/列 + 业务规则 → 对大库尤其关键。
- 加静态校验 + Dry-run + 自纠错回路 → 把明显错误堵住,DIN-SQL/C3 的核心收益。
- 改 Prompt:约束 DISTINCT/JOIN,加 Few-shot → C3 证明即便不微调,Prompt 也能拉不少分。
- 分步生成:Schema Linking → 分类 → 草稿 → 修正 → 对复杂查询有决定性作用。
- 构建反馈闭环:记录用户修正,回流 RAG / 训练数据 → 长期稳定提升的关键。
如果你愿意,可以告诉我你现在用的具体栈(哪个 LLM、什么库、多少张表、有没有 RAG),我可以按你现状帮你挑”3 件最该先做的事”,并给出更具体的实现细节和示例代码。
Text-to-SQL is the technique of using large language models (LLMs) to convert natural language questions into executable SQL queries for relational databases. It’s far more than simple “translation” — it’s a comprehensive process involving semantic parsing, schema understanding, and logical reasoning. Below is an in-depth analysis of implementing Text-to-SQL with LLMs, progressing from fundamental principles to enterprise-grade architecture.
1. Basic Implementation: Prompt Engineering
The most straightforward approach is to feed the database structure and the user’s question directly to an LLM and let it generate SQL. Core Elements:
- DDL (Data Definition Language): Provide
CREATE TABLEstatements so the LLM knows table names, column names, and data types. - Metadata: Provide foreign key relationships so the LLM knows how to JOIN tables.
- Few-Shot Examples: Provide a few SQL examples for similar questions — this dramatically improves accuracy.
- User Question: The natural language input. Basic Prompt Template:
You are a senior data analyst. Convert the user's question into SQL based on the following database schema.
[Database Dialect]: PostgreSQL
[Schema]:
CREATE TABLE orders (order_id INT, customer_id INT, order_date DATE, amount DECIMAL);
CREATE TABLE customers (customer_id INT, name VARCHAR, city VARCHAR);
[Reference Examples]:
Question: Total order amount for Beijing customers
SQL: SELECT SUM(o.amount) FROM orders o JOIN customers c ON o.customer_id = c.customer_id WHERE c.city = 'Beijing';
[User Question]: Who are the top 3 customers by spending in 2023?
[Output Requirement]: Output only raw SQL, no explanation.
Limitations: In real-world scenarios, databases often contain hundreds or thousands of tables. The full DDL far exceeds the LLM’s context window, and even if it fits, the LLM suffers from the “lost in the middle” phenomenon, generating heavily hallucinated SQL (non-existent columns, incorrect JOINs).
2. Intermediate Implementation: RAG + Schema Linking
To handle large databases, you must introduce Schema Linking — the practice of feeding the LLM only the table structures relevant to the user’s question. Implementation Steps:
- Vectorize Metadata:
Concatenate table names, column names, column comments, and even high-frequency enum values into text, then embed and store them in a vector database.
Example:
Table: orders; Column: amount; Comment: order amount - Metadata Retrieval:
When a user asks “total revenue last month,” first retrieve the Top-K most relevant tables and columns from the vector store (e.g.,
orders.amount,orders.order_date). - Business Knowledge Injection:
Many implicit business rules are unknown to the LLM. For instance, “revenue” in your company might actually mean the sum of
amount > 0. You need to match a “business terminology dictionary” during retrieval and inject it into the prompt. - Build Dynamic Prompt: Assemble only the retrieved table structures and business rules into the prompt, then send it to the LLM for SQL generation.
3. Advanced Implementation: Agentic Workflow
Complex queries (multi-table joins, nested subqueries, grouping and sorting logic) are hard to get right with a single LLM call. The current state-of-the-art approach is the Plan-and-Solve Agent. Core Idea: Let the LLM think like a human — first decompose the problem, then write SQL step by step. Workflow Design:
- Decomposer:
A user asks: “What’s the month-over-month growth rate of average order value by region last month?”
The agent first decomposes the logic:
- Average order value by region last month = total amount / order count
- Same metric for the previous month (month-over-month comparison)
- Calculate the growth rate
- Selector (Schema Linker): For each decomposed subtask, retrieve the necessary tables and columns from the vector store.
- Generator:
Based on the retrieved schema, progressively generate CTEs (Common Table Expressions, i.e.,
WITH ASsyntax) or produce sub-SQLs step by step. - Executor & Reflector: [The key to boosting success rates]
- Execute the generated SQL on a test database (dry-run).
- If the database throws an error (syntax error, non-existent column), feed the error message + original SQL + question back to the LLM for self-correction.
- If execution succeeds but returns empty results, let the LLM determine whether the data is genuinely absent or the logic is wrong.
4. Enterprise-Grade Pain Points and Solutions
In real production environments, the architecture above is still not enough. Here are the most common pitfalls and their remedies:
| Pain Point | Description | Solution |
|---|---|---|
| Hallucination | The LLM fabricates non-existent columns or arbitrarily JOINs unrelated tables. | 1. Constrained Prompt: Explicitly forbid using columns not in the provided list. 2. Syntax Validation: Use tools like sqlglot to parse the generated SQL AST and verify that all referenced tables/columns exist within the given schema. |
| Complex Calculations | Involves year-over-year / month-over-month comparisons, sliding time windows, currency formatting. | LLMs excel at logic but struggle with precise calculations. Allow the LLM to use Python UDFs, or provide calculation helper functions in the prompt. |
| Massive Databases | Thousands of tables with column/table names that are pinyin abbreviations (e.g., yhzhb). | 1. Mandate maintaining a data dictionary (comments matter more than code). 2. Introduce an intermediate View layer that abstracts complex star schemas into wide tables, letting the LLM query against the wide tables. |
| Data Security & Privacy | Reluctant to send real table structures and data to public clouds like OpenAI. | 1. Deploy local open-source models (e.g., DeepSeek-Coder, Llama-3, CodeLlama). 2. Only transmit DDL — never send actual data rows. |
| SQL Dialect Differences | SQL generated in MySQL syntax, but the underlying data warehouse uses Hive/Spark. | Explicitly specify the dialect in the prompt, and use the corresponding dialect engine for dry-runs in the Executor stage. |
5. Recommended Open-Source Frameworks
Don’t build from scratch. Consider using or referencing these mature open-source Text-to-SQL frameworks:
- Vanna.ai (Highly Recommended)
- Approach: RAG-based Text-to-SQL.
- Strengths: Extremely easy to use, supports accumulating training data through conversation (DDL + historical QA pairs), supports multiple LLMs and databases, and comes with a built-in visual interface.
- LlamaIndex / LangChain
- Approach: Provides foundational Chain and Agent components.
- Strengths: Flexible, ideal for deeply customized Agentic Workflows. LlamaIndex’s
SQLDatabasecombined withReAct Agentis a classic pairing.
- DB-GPT
- Approach: A multi-model framework designed specifically for the database domain.
- Strengths: Supports multi-agent collaboration, data privacy protection (local model deployment), and built-in rich schema retrieval and optimization strategies.
6. Summary and Evaluation Metrics
How do you evaluate whether your Text-to-SQL system is any good? The industry typically looks at two metrics:
- Execution Accuracy: Whether the results from the generated SQL match the results from a human-written SQL. (Focuses on business outcomes — recommended as the primary metric.)
- Exact Match: Whether the generated SQL string is identical to the reference answer. (Extremely strict; hard to achieve in practice since SQL can be written in many equivalent ways.) Final Implementation Advice: Don’t expect a single LLM to solve all complex queries. The best architecture is “human-AI collaboration”: the LLM handles 80% of basic queries and initial drafts of complex ones, while the remaining 20% of intractable logic gaps allow users to fine-tune SQL through a UI. The corrected SQL is then fed back into the vector store as few-shot examples, making the system smarter with use.
Improving LLM-generated SQL accuracy is not about “using a bigger model” — it’s about building a closed-loop system: “Understand Schema → Generate Step by Step → Validate & Self-Correct → Continuously Learn from Feedback.” Mainstream approaches like DIN-SQL, C3, and DAIL-SQL all follow this engineering philosophy of “Schema Linking + Decomposition + Self-Check + Agent Loop.” Below is an actionable roadmap ordered from easy to hard, from quick wins to closed loops. Each step clearly explains: why to do it, how to do it, and what level to aim for.
Overview: A 5-Layer Roadmap for Improving SQL Accuracy
flowchart LR
A[1. Schema & Metadata Governance] --> B[2. Prompt / RAG Optimization]
B --> C[3. Step-by-Step Generation & Task Decomposition]
C --> D[4. Validation & Self-Correction Loop]
D --> E[5. Feedback & Continuous Learning]
A:::layer
B:::layer
C:::layer
D:::layer
E:::layer
classDef layer fill:#f5f5f5,stroke:#555,stroke-width:1px;
- Layer 1: Get “database understanding” right first (govern schema, comments, foreign keys, business terminology).
- Layer 2: Show the LLM only relevant schema to reduce noise (Prompt + RAG).
- Layer 3: Don’t let it write complex SQL in one shot — decompose the problem first, then generate (DIN-SQL-style pipeline).
- Layer 4: After generation, always execute + validate + auto-correct (self-correction loop).
- Layer 5: Accumulate both good and bad examples to continuously train RAG / smaller models (the longer you use it, the more accurate it gets).
1. Schema & Metadata Governance: Make the Database Self-Documenting
A large portion of LLM SQL errors stem from schemas that are inherently unclear: missing comments, missing foreign keys, pinyin-abbreviated column names, and business logic buried in code rather than expressed in table structures. One study audited 47 enterprise databases and found that 83% of production DDLs cannot be directly used for RAG training.
1.1 Must-Do: Enhanced DDL & Comments
For every table and column, at minimum add:
- Table: business name, business meaning, primary use case (e.g., “Order fact table, used for sales reporting”)
- Column: business name, whether it’s a primary key, whether it’s a foreign key (and to which table), common enum values (e.g.,
status: paid/canceled) - Foreign keys: Document them explicitly — even if the database lacks FK constraints, add them in DDL comments or metadata Example of an enhanced table description (usable for RAG training / prompts):
-- Enhanced orders table definition
CREATE TABLE orders (
id INT PRIMARY KEY COMMENT 'Order primary key',
customer_id INT COMMENT 'Customer ID, references customers.customer_id',
product_code VARCHAR COMMENT 'Product business code; must join product_sku.product_code to get product name',
amount DECIMAL(10,2) COMMENT 'Order amount in yuan',
order_status VARCHAR COMMENT 'Order status: paid/canceled/refunded',
create_time TIMESTAMP COMMENT 'Order creation time'
) COMMENT 'Order fact table, used for sales amount and order count reporting';
Vanna’s practical guides call this “DDL enhancement technology” — using tools like SQLGlot to automatically add foreign keys and comments before feeding them to RAG.
1.2 Recommended: Unified Business Terminology Dictionary (Semantic Layer)
- Maintain a “business term → column mapping” dictionary, for example:
- “Sales revenue” →
orders.amount(whereorder_status = 'paid') - “GDP growth rate” →
index_value WHERE index_id = 5 AND report_period = '2023Q1', etc.
- “Sales revenue” →
- During RAG retrieval, pull both “business term + column mapping” together and inject them into the prompt. This significantly reduces “wrong column for the concept” errors. Impact: With good governance, this layer alone combined with simple prompting can boost accuracy significantly in real business scenarios. All subsequent layers build on top of a “well-governed schema” for compounding gains.
2. Prompt / RAG Optimization: Show the LLM Only Necessary Schema
For large databases (hundreds of tables), stuffing the full DDL into the prompt causes the LLM to get “lost in the middle” with severe hallucinations. The mainstream approach is: Schema Linking + RAG retrieval — feed the LLM only the tables and columns relevant to the question.
2.1 Vectorize Metadata: Table Names, Column Names, Comments, Sample Values
For each table, construct a text block, for example:
Table: orders
Columns: id(int, primary key), customer_id(int, references customers.id), amount(decimal, order amount), order_status(varchar, order status: paid/canceled/refunded)
Comment: Order fact table, used for sales amount and order count reporting
High-frequency values: order_status=paid/canceled/refunded
Then embed it into a vector store (Chroma / Qdrant / PGVector, etc.) — this is exactly how Vanna works.
2.2 Retrieve Top-K Tables + Columns + Business Rules
After the user submits a question:
- Use vector search to retrieve Top-K tables and columns;
- Simultaneously retrieve matching mapping rules from the “business terminology dictionary”;
- Assemble the retrieval results into a prompt, formatted like:
[Database Dialect]: PostgreSQL
[Relevant Schema]:
CREATE TABLE orders (...);
CREATE TABLE customers (...);
[Business Rules]:
- "Sales revenue" maps to orders.amount WHERE order_status = 'paid'
- "Customer count" maps to COUNT(DISTINCT customer_id)
[Reference Examples]:
Q: Last month's sales revenue?
SQL: SELECT SUM(amount) FROM orders WHERE order_status = 'paid' AND create_time >= ...
[User Question]: Sales by region in Q1 2025
[Requirement]: Output only executable SQL, no explanation.
2.3 Prompt Engineering Details (Insights from C3)
The C3 paper demonstrated that even without fine-tuning, prompt engineering alone can push Spider to SOTA:
- Clear Prompting: Use full
CREATE TABLEstatements instead of simplified versions, so the LLM sees complete constraints. - Calibration Bias: Explicitly constrain the LLM’s “bad habits,” for example:
- “Don’t overuse DISTINCT”
- “Don’t JOIN all tables unconditionally”
- “Only use the tables and columns listed above — don’t fabricate any”
- Consistency: Sample multiple SQLs for the same question, then use execution results for majority voting rather than string similarity. Impact: Schema governance + RAG + well-crafted prompts offer the best ROI. For many scenarios, this layer alone is sufficient.
3. Step-by-Step Generation & Task Decomposition: The DIN-SQL Pipeline
Complex queries (multi-JOINs, nested subqueries, grouped aggregations) are extremely error-prone when written in a single step. DIN-SQL’s core contribution: Don’t let the LLM write SQL in one shot — decompose it into subtasks.
3.1 Four-Stage Pipeline (DIN-SQL Skeleton)
question → Schema Linking → Difficulty Classification → SQL Generation → Self-Correction
- Schema Linking: Let the LLM read the schema + question, output relevant tables and columns, and prune early.
- Difficulty Classification: Categorize the question as easy / non-nested complex / nested complex, using different templates / few-shots for each.
- SQL Generation: Provide different CoT prompts per category to generate SQL.
- Self-Correction: Execute the SQL; if it fails or results look suspicious, feed the error back to the LLM for correction. You don’t have to replicate the paper’s implementation exactly, but retain the pipeline philosophy of “Schema Link first → Classify → Generate → Self-Check” — it works with any model.
3.2 How to Decompose in Practice
A practical approach: split “writing SQL” into 3 LLM calls:
- Call 1: Schema Linking + Decomposition
- Input: question + full schema (or RAG Top-K)
- Output: tables/columns needed, query structure (nested? multi-JOIN?), sub-question breakdown
- Call 2: Draft SQL
- Input: only the tables/columns selected in the previous step + sub-questions
- Output: annotated CTEs or step-by-step SQL
- Call 3: Integration & Rewrite
- Input: draft SQL + constraints (no DISTINCT, no unnecessary JOINs)
- Output: final executable SQL Impact: Step-by-step generation significantly reduces error rates on complex queries. This is the key technique that pushed DIN-SQL and similar methods past 80% execution accuracy on Spider/BIRD.
4. Validation & Self-Correction Loop: Always Run It After Generation
Post-mortems from DIN-SQL, C3, and DAIL-SQL all emphasize one point: SQL validation + execution rollback is not optional — it’s mandatory.
4.1 Static Validation: Syntax & Schema Constraints
Use sqlglot or the database’s built-in parser for two checks:
- Syntax Check: Can the SQL be parsed into an AST?
- Schema Check:
- Are all tables/columns referenced in the SQL within the provided schema?
- Do JOIN conditions use foreign keys or obviously related columns?
- Are there “unexplained” DISTINCT / GROUP BY clauses? If any check fails, auto-correct or send it back to the LLM for a rewrite.
4.2 Dynamic Validation: Dry-Run on a Real Database
- Execute SQL using a read-only connection on a test database / replica;
- Catch exceptions: syntax errors, non-existent columns, permission issues, etc.;
- Feed the error message + original SQL + question back to the LLM for correction (the DIN-SQL Self-Correction approach).
4.3 Result-Level Validation: Empty Results / Anomaly Detection
- Execution succeeds but returns empty: let the LLM determine whether there’s genuinely no data or the conditions are wrong;
- Perform sanity checks on aggregated results (is sales revenue negative? far outside historical range?).
4.4 Self-Consistency: Multiple Sampling + Execution Result Voting
C3’s approach: generate SQL multiple times, then use execution result equivalence classes for majority voting rather than string matching:
Question → Sample N SQLs → Execute each → Group by results → Select one SQL from the largest group
This is extremely practical in production, especially for “semantically correct but syntactically different” SQL variants. Impact: Validation + self-correction is the key to “pulling accuracy from 70% to 90%.” Nearly every production-grade Text-to-SQL system implements this layer.
5. Feedback & Continuous Learning: The Longer You Use It, the More Accurate It Gets
A key feature of RAG2SQL frameworks like Vanna is “self-learning”:
- User confirms SQL is correct → automatically store “question + SQL + DDL” in the vector store;
- User corrects SQL → store the corrected version as a new training sample;
- Over time, RAG retrieves increasingly relevant examples, and accuracy naturally improves.
5.1 Recommended Feedback Loop
- Frontend UI: Allow users to click “This SQL is wrong / Let me fix it”;
- Backend Logging:
- Original question
- LLM-generated SQL
- User-corrected SQL
- Whether execution results match
- Periodic Feedback Integration:
- Add successful QA pairs to RAG;
- Turn typical errors into “anti-pattern” rules, adding them to prompts / validation scripts.
5.2 Dataset Quality: Execution Consistency
Research shows that in datasets like Spider/BIRD, over 30% of NL–SQL mappings are actually incorrect. Work like SQLDriller proposes using “execution consistency” to clean training data: multiple SQLs generated for the same question must produce consistent execution results. In practice, you can:
- Filter internal QA pairs by “whether execution results are consistent”;
- Only feed stable-result QA pairs to RAG / fine-tuning models. Impact: This layer is the key to “making the system better with use” — and the biggest differentiator between enterprise deployments and open-source demos.
6. Model & Architecture Selection: Don’t Start with Fine-Tuning
Based on engineering practices from 2023–2025, fine-tuning is not the first choice, because:
- Annotation costs are high (SQL is easy to write but hard to evaluate; execution consistency is hard to guarantee);
- Models evolve rapidly, and fine-tuned results become outdated quickly;
- Current LLMs + RAG + decomposition + self-correction already achieve 80%+ execution accuracy on many business tasks. The recommended progression:
- First: Schema governance + RAG + prompting + step-by-step generation + self-correction;
- Then: If persistent domain-specific patterns remain (e.g., a certain query type is always wrong), consider:
- Supervised fine-tuning with open-source LLMs (DeepSeek-Coder, Llama 3, etc.);
- Or training a “Schema Linking small model” specifically for table/column selection, simplifying the problem.
7. Implementation Priority Checklist (Ordered by ROI)
If you have an existing Text-to-SQL system and want to “spend the least time boosting accuracy,” follow this order:
- Add comments + foreign keys + business terminology dictionary (Schema governance) → Quick wins, lowest cost.
- Add RAG: show the LLM only relevant tables/columns + business rules → Especially critical for large databases.
- Add static validation + dry-run + self-correction loop → Catches obvious errors; the core benefit of DIN-SQL/C3.
- Improve prompts: constrain DISTINCT/JOIN, add few-shot examples → C3 proved that even without fine-tuning, prompts can significantly boost scores.
- Step-by-step generation: Schema Linking → Classification → Draft → Correction → Decisive impact on complex queries.
- Build a feedback loop: log user corrections, feed back to RAG / training data → The key to sustained long-term improvement.
If you’d like, share your specific tech stack (which LLM, what database, how many tables, whether you have RAG), and I can help you pick the “3 things you should do first” based on your current situation, with more concrete implementation details and sample code.