为什么需要 Text2SQL:让业务直接问数据库
在很多公司里,数据是「取」出来的,不是「看」出来的。业务同学想查「这个季度华东区的新客转化率是多少」,往往要先提需求给数据团队,开发写好 SQL、跑出结果、再做成报表,一来一回一两天。BI 工具能解决一部分,但建模型、维护口径的门槛依然不低。Text2SQL 想做的事很直接:让人用自然语言问数据库,模型把问题翻译成 SQL,查完把结果讲成人话。
有人会问,这和 RAG 有什么区别?RAG 面向的是文档、知识库这类非结构化文本,回答靠「检索片段再生成」;Text2SQL 面向的是结构化表格,答案必须精确——多一个零、少一个条件就是完全不同的数字。正因为精确性要求高,Text2SQL 不是简单套一个 Prompt 就完事,而是需要围绕 Schema 感知、提示设计、执行安全、效果评估做一整条工程链路。这篇就按这条链路逐步拆解。
Text2SQL 的本质是「把取数的门槛从写 SQL 降到问问题」,但代价是模型生成的 SQL 必须被当作不可信代码来对待。
- 生成链路意图理解、Schema 感知、SQL 生成、安全校验、执行、结果解释。
- Schema 感知把表结构、字段注释、示例值组织成模型看得懂的上下文。
- 提示策略方言、安全规则、few-shot 示例共同约束生成行为。
- 执行安全只读账号、强制 LIMIT、危险语句拦截、审计脱敏缺一不可。
核心链路:从自然语言到可执行的 SQL
一条完整的 Text2SQL 管线通常包含六个环节。第一步是意图与实体识别,模型要理解用户想要什么维度的数据、按什么条件过滤、按什么排序;第二步是 Schema 感知,把数据库的表结构组织成上下文喂给模型,这一步直接决定生成质量,下一节单独展开;第三步是 SQL 生成,模型在约束下输出 SQL;第四步是安全校验,检查语句是否只读、是否超限;第五步是执行,用只读账号、带超时地跑;最后是结果解释,把查询结果转成业务同学看得懂的回答,必要时附上生成的 SQL 供核对。
实现上有两种路线。轻量路线是「一次生成」:问一句出一条 SQL,跑完给结果,适合字段清晰、逻辑简单的场景。进阶路线是「Agent 式」:模型先写一版 SQL,执行报错就把错误信息喂回去让它自查重写,甚至主动追问「你说的是本月还是上月?」。Agent 式能显著提升复杂问题的成功率,但要注意控制追问轮数,避免无限循环烧 Token。下面是一段轻量路线的主流程伪代码。
# Text2SQL 主流程(伪代码)
# 1. 接收自然语言问题 q
# 2. 召回相关表,组装 schema 上下文 ctx = build_schema_context(q)
# 3. 构造 prompt = system_rule + ctx + few_shots + q
# 4. sql = llm_generate(prompt)
# 5. 安全校验:is_read_only(sql) 且行数/执行时长未超限
# 6. 通过则执行并返回 (result, sql);不通过则拒绝并解释
def text2sql(q):
ctx = build_schema_context(q)
sql = llm_generate(assemble_prompt(q, ctx))
if not safe_check(sql):
return refuse_with_reason(sql)
return execute_readonly(sql), sql
Schema 感知:让模型认识你的表结构
Text2SQL 翻车的第一大原因不是模型笨,而是模型「不认识表」。业务库动辄几十上百张表,字段名可能是拼音缩写、英文简写或者历史遗留命名,模型全靠猜。把完整库结构全塞进上下文既不现实也浪费 Token,正确做法是两段式:先按问题关键词召回候选表,再把候选表的「精选结构」组装进提示。
精选结构要包含什么?表名与中文注释、关键字段名与类型、字段的中文含义、可枚举字段的取值示例(比如 status 有哪几个值)、以及常见的关联关系。示例值非常重要:模型看到 status 的值列表是「0 待支付、1 已支付、2 已退款」,就不会凭空猜。字段有歧义时(比如两张表都有 amount),要显式标注归属。召回不准的问题可以在关键词匹配之外,叠一层 Embedding 相似度,把语义近的表也召回进来。
# Schema 上下文的组织示例
# 召回结果(假设命中 orders / users 两张表)
orders: 订单表
id BIGINT 订单ID
user_id BIGINT 下单用户ID(关联 users.id)
amount DECIMAL 订单金额(元)
status TINYINT 0待支付 1已支付 2已退款
created_at DATETIME 下单时间
users: 用户表
id BIGINT 用户ID
region VARCHAR 所属区域(华东/华南/华北…)
is_new TINYINT 是否新客 1是 0否
Schema 上下文本身也要控制体积:单次生成建议把 schema 部分压在一两千 Token 内,太长的结构可以按「只给查询会用到的列」裁剪。裁剪的原则是宁多勿漏——漏掉关键列,模型就会用错列或者编列名。
提示策略:方言、规则与示例设计
SQL 生成的质量上限由提示约束决定。第一层约束是方言:MySQL、PostgreSQL、SQL Server 的函数和语法差异很大,系统提示里必须写明目标方言与版本,避免模型输出「看起来很对但语法不通」的语句。第二层是规则约束:默认不加 SELECT *、明确过滤条件不要拍脑袋补、日期比较用标准写法、字段名必须来自给定 Schema、不允许臆造列。第三层是 few-shot 示例:每个业务域放三到五个「问法→SQL」样例,覆盖常见的聚合、分组、时间范围、多表关联,模型会照着示例的写法答题。
还有一个容易被忽略的点:问题本身要「翻译」一遍。用户常问「上个月华东区卖了多少」,模型需要理解「上个月」对应哪段日期、金额是哪个字段、按什么口径汇总。可以要求模型在输出 SQL 的同时,用一句话解释它理解的查询意图,再让前端展示给用户确认——确认环节能拦下大量理解偏差,比反复改 SQL 便宜得多。
# 系统提示要点(节选)
# 1. 你只负责生成 SQL,目标方言是 MySQL 8.0。
# 2. 字段与表名必须来自给定的 Schema,禁止编造列名。
# 3. 禁止使用 SELECT *,列名要写全。
# 4. 所有查询必须添加合理过滤条件,禁止返回全表。
# 5. 时间类条件优先使用 BETWEEN 或 >= / <=,禁止臆测日期。
# 6. 输出格式:先一句查询意图说明,再输出 SQL。
执行安全:把 LLM 输出当成不可信输入
模型生成的 SQL 本质上是一段代码,而且是程序自动生成的代码——必须按不可信输入处理。第一道防线是账号:应用连接数据库用只读账号,权限只给 SELECT,从根上杜绝写操作。第二道防线是语句检查:解析 SQL 的顶层关键字,遇到 DELETE、UPDATE、INSERT、DROP、ALTER 等一律拒绝;同时强制追加 LIMIT,防止一次查出几百万行把服务拖垮。
第三道防线是执行治理:查询必须设置超时(比如 10 秒)和结果集上限,超出直接终止并提示用户缩小范围;复杂的聚合查询可以考虑走独立的只读副本,避免拖累主库。第四道防线是审计:记录「谁在什么时间问了什么问题、生成了什么 SQL、返回了多少行」,既方便排查问题,也能发现异常取数行为。最后,如果查询涉及敏感字段(手机号、身份证等),结果要按权限脱敏后再返回。
# SQL 安全校验(伪代码)
# 1. 解析出语句的第一个顶层关键字
# 2. 不在只读白名单(SELECT/WITH)中则拒绝
# 3. 正则拦截危险片段:注释内联、分号拼接、堆叠语句
# 4. 强制重写:给结果追加 LIMIT 上限(如 1000)
# 5. 执行时带超时与行数上限,超限即终止
def safe_check(sql):
stmt = first_keyword(sql)
if stmt not in ("select", "with"): return False
if re.search(r"(delete|update|insert|drop|alter|;)", sql, re.I): return False
return True
评估与调优:用指标驱动生成质量
Text2SQL 好不好,不能靠感觉。常用指标有三个:执行成功率(生成的 SQL 能通过校验并正常跑出结果的比例)、完全一致率(与人工标注 SQL 结果完全一致的比例)、人工正确率(抽样让人工判断结果是否符合问题意图)。执行成功不等于回答正确——SQL 能跑但查错了条件,是 Text2SQL 最典型的失败方式,所以三个指标要一起看。
调优要按错误类型归因。常见的错误分四类:选错表(召回或 Schema 上下文没给全)、选错字段(字段注释不清或歧义)、条件错误(日期口径、状态值理解错)、语法错误(方言不一致)。每一类都有对应的解法:选错表就优化召回和 Schema 裁剪,选错字段就补字段注释和示例值,条件错误就补 few-shot 和追问环节,语法错误就锁方言并在校验层加语法预检。每次调整后跑回归集,看整体指标有没有提升,避免「按下葫芦浮起瓢」。
| 错误类型 | 典型表现 | 主要解法 |
|---|---|---|
| 选错表 | 用了相似但错误的表 | 优化召回、Schema 标注归属 |
| 选错字段 | 字段含义理解偏差 | 补字段注释与枚举示例值 |
| 条件错误 | 时间/状态口径错误 | 补 few-shot、增加意图确认 |
| 语法错误 | 方言不匹配、语句非法 | 锁定方言、校验层预检 |
行业里也有公开基准可以参考:SPIDER 偏语法正确性,BIRD 更贴近真实业务库、关注执行正确率。用公开基准 + 自建业务回归集双轨评估,是判断模型和提示升级有没有真实收益的稳妥做法。
工程落地与适用边界
落地时最容易犯的错是「一步到位」:一上来就开放全库、全量用户、无确认环节。稳妥的节奏是:先挑 2 到 3 张核心表、小范围灰度,配上意图确认和结果展示(把 SQL 亮出来),跑通流程、积累错误样本后再扩表扩人。生成失败的兜底也很重要:校验不通过或执行超时,不要丢给用户一个冷冰冰的报错,而是提示「换个说法试试」或转人工取数。
最后说边界。Text2SQL 适合的是「口径清晰、结构规整」的查询:经营看板、报表取数、日志检索、客服复盘。下面几类场景要谨慎:涉及复杂业务口径的(同一个「营收」在不同部门定义不同,模型很难替你决策);需要多跳推理的(跨多张表多次聚合);以及高频实时写入的库(查询负载会互相影响)。把这些想清楚,Text2SQL 才能真正成为「业务自助取数」的引擎,而不是一个偶尔答对的玩具。
Text2SQL 的成功标准不是「模型能写 SQL」,而是「业务同学敢放心用」——安全、可解释、错了能被拦住。