Text-to-SQL 的表结构上下文该怎么组织?表关系要写到什么程度?
简化版
表结构上下文要按「表 → 字段」两层组织,并且只放当前数据集允许查询的表。表这一层写表名、表说明和关联关系;字段这一层写字段名、类型、中文说明、语义类型(维度 / 指标 / 时间 / 标识)、业务含义,必要时列出枚举值和单位。表关系必须写成完整的等式,比如 biz_order.user_id = biz_user.id,不能只写「通过 user_id 关联」——关联字段两边不一定同名,写得含糊,模型就会生成一个不存在的 biz_user.user_id。
详细版
组织表结构上下文的五条原则:
- 范围最小:只放当前业务场景用得到、且启用的表,无关表越多,模型越容易选错;
- 两层结构:表一层、字段一层,用 JSON 或清晰的列表组织,比整段自然语言更不容易漏看;
- 关系写全:关联条件写成
表.字段 = 表.字段的完整等式,一对多关系要说明方向; - 字段带语义:除了类型,还要写业务含义、语义类型,状态类字段列出每个码值的含义,金额写明单位;
- 口径单独放:「销售额」「客单价」这类业务指标不混在字段说明里,单独一段给出计算公式和 SQL 表达式。
不同写法的效果对比:
| 写法 | 模型的典型反应 |
|---|---|
| 「订单表和用户表通过 user_id 关联」 | 生成 biz_order.user_id = biz_user.user_id,用户表没有这一列,执行报错 |
| 「通过 biz_order.user_id = biz_user.id 关联用户」 | 直接照抄等式,关联正确 |
字段只写 status int | 猜 1、2、3 的含义,过滤条件写错 |
| 字段写「订单状态:1 待支付,2 已支付,3 已退款」 | 统计有效订单时自动加 status = 2 |
完整版教学
一、表结构上下文要回答模型的四个问题
模型拿到一个问题,要写出正确的 SQL,心里得有四个问题的答案:
① 有哪些表可以用? → 表清单(只放允许查询的)
② 每张表是干什么的? → 表说明
③ 表和表怎么连? → 关联关系
④ 每一列是什么意思、怎么用? → 字段说明、语义类型、业务含义、码值、单位
上下文就是按这四个问题来组织的。少回答一个,模型就会在那一处自由发挥,而自由发挥的结果往往是「语法正确、业务错误」。
二、为什么要按「表 → 字段」两层组织
把所有信息写成一大段话,比如「订单表有订单号、用户 ID、金额……,用户表有……」,模型很容易把 A 表的字段张冠李戴到 B 表上。两层结构让每个字段明确归属到某张表:
[
{
"tableName": "biz_order",
"tableComment": "订单主表",
"relationDescription": "通过 biz_order.user_id = biz_user.id 关联用户",
"fields": [
{"fieldName": "order_status", "fieldType": "varchar",
"semanticType": "维度", "businessMeaning": "订单状态:已支付、已退款"},
{"fieldName": "pay_amount", "fieldType": "decimal(10,2)",
"semanticType": "指标", "businessMeaning": "实际支付金额,单位元"}
]
}
]
JSON、DDL(CREATE TABLE 语句加注释)、Markdown 表格都可以,关键是结构稳定、每个字段归属清楚。DDL 的好处是模型对它非常熟悉,缺点是很难自然地塞进业务含义;JSON 更适合附带语义信息。
三、表关系为什么必须写成完整等式
这是 Text-to-SQL 里最容易被忽略、却最常出错的一点。看两种写法:
含糊写法:biz_order 和 biz_user 通过 user_id 关联
完整写法:通过 biz_order.user_id = biz_user.id 关联用户
含糊写法下,模型读到「通过 user_id 关联」,很自然地写出:
join biz_user u on o.user_id = u.user_id -- biz_user 根本没有 user_id 这一列
这条 SQL 能通过只读安全校验(它确实只是 select),却在执行时报 Unknown column 'u.user_id'。问题的根源在于:关联字段两边往往不同名,一边是外键 user_id,另一边是主键 id。完整等式把两边都写死,模型只需照抄,不需要推断。
易错点:表关系描述是会被原样送进 Prompt 的「代码」,不是给人看的备注。写备注的习惯(「通过 xx 关联」)在这里会直接变成 SQL 错误。
四、字段说明要写到什么程度
类型只告诉模型「这是个数字」,业务含义才告诉它「这个数字怎么用」。三类字段最需要补充:
| 字段类型 | 要补充什么 | 不补的后果 |
|---|---|---|
| 状态 / 枚举 | 每个码值的含义 | 过滤条件写错,把退款单算进销售额 |
| 金额 / 数量 | 单位、是否含税、是否已扣优惠 | 口径不一致,分和元混用 |
| 时间 | 是下单时间还是支付时间,精确到日还是秒 | 「上个月销售额」按下单时间算,和财务对不上 |
语义类型(维度 / 指标 / 时间 / 标识)也很有用:告诉模型哪些字段适合 group by(维度、时间),哪些适合 sum、avg(指标),哪些只是用来关联和去重(标识,比如订单号)。
五、范围控制:只放允许查询的表
上下文里的表越多,出错的机会越多。控制范围有两层:
- 按业务场景分数据集:电商销售、课程订单、招聘岗位各是一个数据集,用户先选数据集,只把这个数据集的表交给模型;
- 按启用状态过滤:临时停用的表、敏感表不进上下文,模型从源头上看不到。
算一笔账:一个数据集 3 张表、每张 8 个字段,每个字段描述约 40 个 Token,整段上下文约 1000 Token,模型看得清清楚楚;如果把全库 200 张表都塞进去,上下文膨胀到十万 Token 量级,不仅成本高,模型选表的准确率也会明显下降。表更多的场景,要先用检索把相关的表和字段挑出来,再交给模型。
六、口径为什么单独放一段
字段说明回答的是「这一列是什么」,指标口径回答的是「这个业务词怎么算」,两者不是一回事:
字段:pay_amount 实际支付金额
口径:销售额 = sum(pay_amount),只统计已支付订单
客单价 = sum(pay_amount) / nullif(count(distinct order_no), 0)
「客单价」不是任何一个字段,它是两个指标的比值,还要处理分母为零。把这类口径直接给成 SQL 表达式,模型遇到「客单价」就照着用,不用自己推导,结果才能跨次一致。
七、上下文不是越全越好
给模型的信息要准确、必要,而不是越多越好:
- 生成 SQL 需要完整的字段语义和口径;
- 让模型拆分析计划时,只需要知道有哪些表、字段大概是什么类型、有哪些指标,给太细反而分散注意力;
- 样例数据(每张表放两三行真实数据)能帮模型理解字段格式,但要注意脱敏,也会增加 Token。
同一套元数据,按任务裁剪成不同粒度的上下文,是比较成熟的做法。
八、常见误区与追问
- 误区:表关系写「通过 user_id 关联」模型就懂。 关联字段两边经常不同名,含糊的描述会让模型生成不存在的列,必须写成完整等式。
- 误区:给了字段类型就够了。 类型不告诉模型状态码的含义、金额的单位和时间的口径,这些才是算错的主要来源。
- 误区:上下文里表越全越保险。 无关表越多,模型越容易选错表,Token 成本也越高,应只放当前场景允许查询的表。
- 误区:指标口径可以写在字段说明里。 很多指标是多个字段的组合或比值,不对应任何单一字段,应该单独给出公式和 SQL 表达式。
- 追问:表太多、塞不进上下文怎么办? 先按业务场景拆数据集;再大就对表和字段做语义检索,只把和问题相关的几张表交给模型。
- 追问:DDL 和 JSON 哪种格式更好? DDL 模型更熟悉,JSON 更方便附带业务含义和语义类型;关键在于结构稳定、字段归属清楚。
九、加强记忆
表结构上下文要回答模型四个问题:有哪些表能用、每张表干什么、表和表怎么连、每一列什么意思。组织上按「表 → 字段」两层写成 JSON 或带注释的 DDL,防止字段张冠李戴。表关系一定写成 表.字段 = 表.字段 的完整等式,因为外键和主键常常不同名,「通过 user_id 关联」这种备注式写法会让模型生成不存在的列,而且能过只读校验、执行才报错。字段要补码值、单位和时间口径,语义类型区分维度、指标、时间、标识。范围上只放当前数据集启用的表,业务指标单独成段给出 SQL 表达式;不同任务按需裁剪上下文粒度。
项目实战落地
项目里怎么做的
《AI Agent数据分析平台》的表结构上下文由 buildDatasetSchema 按「数据集 → 表 → 字段」逐层组装:
- 先查当前数据集下启用的数据表,每张表输出
tableName、tableComment、relationDescription; - 再按「数据集 + 数据表」查这张表的字段语义,每个字段输出
fieldName、fieldComment、fieldType、semanticType、businessMeaning、queryEnabled、aggregateEnabled,挂到表的fields数组下; - 整个数组转成 JSON 字符串,填进 SQL 生成模板的
{datasetSchema}。
预置的电商数据集里,关系说明都写成了完整等式,比如订单主表是「通过 biz_order.order_no = biz_order_item.order_no 关联订单明细;通过 biz_order.user_id = biz_user.id 关联用户」。教程里专门记录了反例:写成「通过 user_id 关联」时,模型会生成不存在的 biz_user.user_id,SQL 能通过只读校验,却在执行阶段报 bad SQL grammar。
为什么这样取舍
- 关系说明原样进 Prompt。 这一列不是给人看的备注,而是模型生成关联条件的直接依据,所以管理端要求写成等式。
- 可查询、可聚合作为语义信息交给模型。 它们告诉模型哪些字段适合查、适合聚合;项目的执行校验按表白名单把关,没有做字段级白名单,这一点在项目的面试文档里如实写明,面试时要按源码实际能力回答。
面试官还会追问
- Agent 生成分析计划时用的数据集上下文,和生成 SQL 时用的一样吗?为什么要裁剪?
- 字段标了「不可聚合」,模型还是对它做了 sum,项目在执行前拦得住吗?
学完《AI Agent数据分析平台》,上面这些追问你都会迎刃而解。