Text-to-SQL 实战:用大模型把自然语言转成查询语句
Text-to-SQL(也常写作 NL2SQL,即 Natural Language to SQL)是指把人类用自然语言提出的问题,自动转换为可在数据库上执行的 SQL 查询语句。它让不懂 SQL 的业务人员也能直接「问数据」,是增强型数据分析、智能问数与生成式 BI(GBI)的核心能力之一。本文从原理、难点、提示工程到可运行代码,带你完整落地一个 Text-to-SQL 流程。
什么是 Text-to-SQL 与典型场景
Text-to-SQL 的输入是一段自然语言问题与一份数据库 schema(表结构),输出是一条符合该数据库方言的 SQL。典型落地场景包括:
- 自助数据分析:运营或产品用中文提问,例如「上海的用户上个月下了多少笔订单」,系统直接返回 SQL 与结果。
- 智能问数助手:嵌入企业 IM 或数据平台,把对话转为查询,自动出图、出报表。
- 低代码报表搭建:根据描述生成查询雏形,工程师再做微调,缩短开发周期。
- 数据校验与探索:用自然语言快速验证假设,例如「哪些商品的退货率超过 5%」。
需要说明的是,Text-to-SQL 不等于「让模型随便编一条 SQL」。生产环境要求生成的语句可解析、可执行、且语义与用户意图一致,因此提示工程与执行校验必不可少。
核心难点
Schema 理解
模型必须准确理解表名、字段名、字段类型与主外键关系。现实数据库的字段名常常语义模糊,例如 flag、type、ext,模型若不了解其含义,极易写出错误条件。把表结构的 DDL 与字段注释一起提供给模型,是缓解该问题的基础手段。
语义歧义
同一个问题可能有多种合理解读。例如「最近活跃的用户」,模型需要判断:是按最后一次登录时间,还是按近 30 天有下单行为?「消费高」是指总金额高,还是客单价高?这类歧义需要靠约束说明、少样本示例,或在业务层做澄清来收敛。
多表关联与嵌套查询
跨表 JOIN、子查询、聚合分组是准确率下降的主要来源。模型容易漏写 JOIN 条件、用错关联方向,或在 GROUP BY 与 HAVING 上出错。复杂问题建议拆成多步推理:先确定涉及哪些表,再确定关联键,最后生成聚合逻辑。
提示工程方法
要让大模型稳定输出正确 SQL,关键在于把「上下文」喂足。一个实用的提示模板通常包含以下四部分。
表结构 DDL
直接贴出相关表的建表语句,让模型知道有哪些表、有哪些列、类型与约束是什么。
字段注释
在 DDL 之后补充业务语义,尤其是易混淆字段。例如注明 orders.status 的取值为 paid / pending / refunded。
少样本示例
给出 3 到 5 条「问题到 SQL」的配对示例,示范你期望的写法风格(是否用别名、是否限定方言、是否避免 SELECT *)。少样本能显著提升风格一致性与复杂查询的正确率。
约束说明
明确硬性规则,例如:只输出一条 SQL,不要解释;使用 SQLite 方言;禁止 SELECT *;时间字段为文本格式 YYYY-MM-DD,需用字符串比较。约束越具体,输出越可控。
下面是一段提示拼接示例,可直接用于中文场景:
你是 SQL 生成助手。请只输出一条可执行的 SQL,不要解释,使用 SQLite 方言。
数据库表结构:
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
city TEXT,
created_at TEXT
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER,
amount REAL,
status TEXT,
created_at TEXT
);
字段说明:
orders.status 取值为 paid(已支付)、pending(待支付)、refunded(已退款)。
示例:
问:每个城市有多少用户?
SQL:SELECT city, COUNT(*) FROM users GROUP BY city;
问:消费金额最高的前 5 位用户是谁?
SQL:SELECT u.name, SUM(o.amount) AS total
FROM users u JOIN orders o ON u.id = o.user_id
GROUP BY u.id ORDER BY total DESC LIMIT 5;
问题:上海的用户总共下了多少笔订单?
SQL:
Python 调用示例
下面给出两段可运行代码:第一段负责调用大模型生成 SQL,第二段负责在本地校验并执行,确保不会因为一条非法 SQL 直接报错崩溃。
生成 SQL 的代码
import os
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
SCHEMA = """
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
city TEXT,
created_at TEXT
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER,
amount REAL,
status TEXT,
created_at TEXT
);
"""
FEW_SHOT = """
问:每个城市有多少用户?
SQL:SELECT city, COUNT(*) FROM users GROUP BY city;
问:消费金额最高的前 5 位用户是谁?
SQL:SELECT u.name, SUM(o.amount) AS total
FROM users u JOIN orders o ON u.id = o.user_id
GROUP BY u.id ORDER BY total DESC LIMIT 5;
"""
def generate_sql(question: str) -> str:
prompt = f"""你是 SQL 生成助手。请只输出一条 SQL,不要解释,使用 SQLite 方言。
数据库表结构如下:
{SCHEMA}
示例:
{FEW_SHOT}
问题:{question}
SQL:"""
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
temperature=0,
)
return resp.choices[0].message.content.strip()
if __name__ == "__main__":
sql = generate_sql("上海的用户总共下了多少笔订单?")
print(sql)
校验与执行的代码
import sqlite3
def safe_execute(sql: str, db_path: str = "demo.db"):
conn = sqlite3.connect(db_path)
try:
cur = conn.cursor()
cur.execute(sql)
return cur.fetchall()
except sqlite3.Error as e:
return f"执行失败:{e}"
finally:
conn.close()
def validate_syntax(sql: str, db_path: str = "demo.db") -> bool:
conn = sqlite3.connect(db_path)
try:
conn.execute("EXPLAIN " + sql)
return True
except sqlite3.Error:
return False
finally:
conn.close()
if __name__ == "__main__":
sql = "SELECT COUNT(*) FROM orders o JOIN users u ON o.user_id = u.id WHERE u.city = '上海'"
if validate_syntax(sql):
print(safe_execute(sql))
else:
print("SQL 语法不通过,已拦截。")
通过先 EXPLAIN 再做真正执行,可以在不改动数据的前提下拦截大部分语法错误,是生产环境的基本防线。
主流方案对比
生态中既有开箱即用的开源框架,也有云厂商提供的托管能力,可按隐私、成本与可控性取舍。
开源框架
- DB-GPT:由蚂蚁集团发起的开源 AI 原生数据应用框架,内置 Text2SQL 优化、RAG、多智能体与 AWEL 工作流编排,并提供 DB-GPT-Hub 做有监督微调。适合需要私有化部署、与自有数据深度结合的场景。
- SQLCoder:由 Defog AI 开源的专用 SQL 生成大模型家族,基于 StarCoder 系列微调,官方评测在 sql-eval 框架上超过 gpt-3.5-turbo,可自托管,适合对数据不出域有强要求的团队。
云服务与通用大模型
OpenAI、Anthropic、Google 等通用大模型本身具备不错的 Text-to-SQL 能力,配合完善的提示工程即可快速验证。优势是省去运维,劣势是数据需要出域、按调用量计费,且对私有库结构需要每次携带 schema。
选型建议
数据敏感、需长期沉淀,优先开源自托管(DB-GPT + SQLCoder);只想快速做内部原型,先用通用大模型加提示工程;若 query 高度固定,可考虑用 DB-GPT-Hub 在自有 schema 上微调一个小模型,兼顾成本与准确率。
准确性提升技巧
- 精简 schema 上下文:只把问题相关的表与字段放进提示,表越多越容易误导模型。
- 提供字段取值样例:对枚举类字段,列出
status的可能值,避免模型猜值。 - 约束方言与习惯:固定使用某种 SQL 方言(如 SQLite 或 PostgreSQL),并禁止
SELECT *、要求使用参数化思路。 - 引入执行反馈闭环:把执行报错或空结果回灌给模型,让它自检重试一次,往往能修掉明显的关联或聚合错误。
- 复杂问题分步推理:让模型先输出涉及的表与关联键,再生成最终 SQL,比一步到位更稳。
- 用基准持续评估:以 Spider 等公开数据集为标尺,量化每次提示或模型切换带来的准确率变化。
小结
Text-to-SQL 把自然语言查询变成可执行的 SQL,是降低数据使用门槛的关键能力。它的落地重点不在「调通一次调用」,而在稳定的提示工程与执行校验:用 DDL 加字段注释把 schema 讲清楚,用少样本与约束把输出管住,再用 EXPLAIN 与异常回灌把风险兜住。对于追求私有化与高准确率的团队,DB-GPT 与 SQLCoder 等开源方案值得优先评估。
参考与延伸阅读
- Yu Tao 等人,Spider:Text-to-SQL 与跨领域语义解析的大规模人工标注数据集,EMNLP 2018,arXiv:1809.08887。
- Xue Siqiao 等人,DB-GPT:用私有大模型强化数据库交互,arXiv:2312.17449。
- DB-GPT 开源项目(蚂蚁集团发起,含 Text2SQL 与 DB-GPT-Hub),GitHub:https://github.com/eosphoros-ai/DB-GPT
- SQLCoder 开源项目(Defog AI,专用 SQL 生成大模型家族),GitHub:https://github.com/defog-ai/sqlcoder
- Defog 团队发布 SQLCoder 与 sql-eval 评估框架的说明,https://defog.ai/blog/open-sourcing-sqlcoder