← 数据分析 Agent

Text-to-SQL 是怎么实现的?只给表名和列名为什么不够?

高频 中等 Text-to-SQL 原理与上下文 · 第 1 / 2 问 更新于 2026/09/29
Text-to-SQL数据分析Agent元数据Prompt
本题落地项目AI Agent数据分析平台

简化版

Text-to-SQL 就是把数据库的结构和业务知识作为上下文交给大模型,让它把自然语言问题翻译成可执行的 SQL。只给表名和列名不够,因为模型不知道字段的业务含义(order_status 里哪些值算有效订单)、指标口径(销售额是实付金额求和还是订单金额求和、要不要扣退款)、表之间怎么关联,也不知道哪些表允许查。所以工程上要把表说明、字段语义、指标口径和表关系一起注入 Prompt,要求模型结构化返回 SQL,再经过安全校验、执行、失败纠错,才算一条可用的链路。

详细版

一条可用的 Text-to-SQL 链路:

  1. 准备上下文:当前数据集允许查询的表、每张表的说明和关联关系、字段的类型与业务含义、指标的计算口径;
  2. 结构化生成:Prompt 要求模型只返回 JSON,包含 sql、reason(为什么这么写)、usedTables、usedFields;
  3. 执行前校验:只允许 select、禁止多语句和危险关键字、表必须在白名单里、补行数上限;
  4. 执行与纠错:执行报错时把原 SQL 和数据库报错交给模型改一版;
  5. 结果加工:摘要、图表、解读。

只给表名列名时,常见四类错误:

缺什么典型错误
字段业务含义status = 2 到底是已支付还是已发货,模型只能猜
指标口径「销售额」算成订单金额,没扣退款,没去重
表关系关联字段写错,生成不存在的列
可查范围查到了不该查的表,或者在大表上全表扫描

完整版教学

一、Text-to-SQL 在解决什么问题

数据分析最常见的门槛不是「不会分析」,而是「不会取数」:业务人员知道自己想看「上个月各城市销售额排行」,但不会写 SQL,每次都要找数据同学排期。大模型天生会写 SQL,看起来正好补上这个缺口。

问题在于,大模型懂 SQL 语法,却不懂你的数据库。它没见过你的表,不知道 pay_amount 和 order_amount 哪个才是实付,也不知道「销售额」在你们公司要不要扣掉退款。Text-to-SQL 的工程核心,就是把这些只有你自己知道的东西,稳定地交给模型。

二、只给表名列名,会错在哪

看一个具体例子。订单表有这些列:

biz_order(id, order_no, user_id, city, pay_amount, pay_time, order_status)

用户问「上个月销售额是多少」,只看列名,模型大概率写出:

select sum(pay_amount) from biz_order
where pay_time >= '2026-08-01' and pay_time < '2026-09-01';

语法完全正确,但在业务上有两个问题:order_status 里有「已退款」的订单也被算进去了;如果还有一张订单明细表,模型也可能去明细表上求和,把一单多件的金额重复累加。模型写出来的 SQL 能跑、有数字,但数字是错的,而且没有任何报错提醒你。这是 Text-to-SQL 最危险的一类错误。

记忆钩子:Text-to-SQL 最怕的不是报错,而是「能跑但算错」。报错还能纠正,算错没人发现。

三、上下文要给哪四样东西

要让模型算对,上下文至少包含四类信息:

信息回答模型的哪个疑问例子
表说明这张表是干什么的biz_order 是订单主表
表关系表和表怎么连biz_order.user_id = biz_user.id
字段语义这一列是什么意思、能不能聚合pay_amount 是实付金额,属于指标,可聚合
指标口径业务术语怎么算销售额 = sum(pay_amount),订单数 = count(distinct order_no)

组织成 JSON 交给模型,比堆一段自然语言描述更不容易让模型漏看:

{
  "tableName": "biz_order",
  "tableComment": "订单主表",
  "relationDescription": "通过 biz_order.user_id = biz_user.id 关联用户",
  "fields": [
    {"fieldName": "pay_amount", "semanticType": "指标",
     "businessMeaning": "订单实际支付金额", "aggregateEnabled": "是"}
  ]
}

指标口径单独成一段,让「销售额」「客单价」这类业务词直接对应到 SQL 表达式,模型不用自己推断。

四、为什么要求模型结构化输出

让模型只返回 JSON,而不是一段夹着 SQL 的解释文字,有三个好处:

  1. 可解析:程序能稳定取出 sql 字段交给执行环节,不用从 Markdown 代码块里抠;
  2. 可解释:reason 字段展示给用户,让他知道模型是怎么理解问题的,理解错了一眼能看出来;
  3. 可排查:usedTables、usedFields 记录模型自己认为用了哪些表和字段,出错时方便对照。

但要注意,usedTables 只是模型的自述,不能当作安全依据。判断 SQL 查了哪些表,必须由程序从 SQL 文本里解析出来再和白名单比对,模型报什么都不算数。

五、一条完整的链路

用户问题
  → 意图识别(趋势 / 排行 / 占比 / 对比 / 明细 / 异常)
  → 组装上下文(表、关系、字段语义、指标口径)
  → 模型生成 SQL(结构化 JSON)
  → 安全校验(只读、单语句、表白名单、行数上限)
  → 执行
      ├─ 成功 → 结果摘要 → 图表 → 数据解读
      └─ 报错 → 把原 SQL + 数据库报错交给模型纠错 → 重新校验执行

每一步都有明确的输入输出,结果落库,页面能看到 SQL、执行耗时、结果条数和错误信息。只有「生成 SQL」这一步靠模型,其余都由代码把关。

六、准确率为什么总是上不去

Text-to-SQL 在演示里效果很好,一上真实业务库就掉准确率,常见原因有四个:

  • Schema 太大:假设 200 张表、每张 20 个字段、每个字段描述约 30 个 Token,整个库的描述就要约 12 万 Token,既塞不进上下文,塞进去模型也挑不准。大库要先做表和字段的检索筛选,只把相关的几张表交给模型;
  • 口径不清:同一个「活跃用户」在不同部门有不同定义,元数据里没写清楚,模型只能猜;
  • 问题本身有歧义:「最近的销售额」是最近 7 天还是本月,要么在 Prompt 里约定默认值,要么让系统追问;
  • 复杂查询:多层嵌套、窗口函数、跨多表关联,模型出错率明显上升。

七、Text-to-SQL 的适用边界

Text-to-SQL 适合长尾的、临时的取数需求:明细查询、分组排行、简单聚合。它不适合承担核心指标:像「GMV」「复购率」这种公司层面要对齐口径的数字,每次让模型现写,写法可能每次都不一样,跨时间、跨对象不可比。成熟的做法是核心指标做成固定口径的查询工具,Text-to-SQL 作为兜底,处理工具覆盖不到的问题。

八、常见误区与追问

  • 误区:模型的 SQL 能跑通就说明对了。 能跑只说明语法正确,口径错了一样能跑出数字,而且不会报错,必须靠元数据约束和结果核对。
  • 误区:把整个数据库的表结构都塞给模型最保险。 表一多上下文就超限,无关表越多模型越容易选错表,要只注入当前数据集允许查询的表。
  • 误区:模型返回的 usedTables 可以用来做权限判断。 那只是模型的自述,安全判断必须从 SQL 文本里解析表名,与白名单比对。
  • 误区:Text-to-SQL 可以取代所有报表查询。 核心指标要对齐口径、跨期可比,应该做成固定口径的工具,Text-to-SQL 更适合长尾问题。
  • 追问:怎么让模型区分「订单金额」和「实付金额」? 在字段语义里写清业务含义,并把「销售额」这类业务术语写成指标口径,直接给出 SQL 表达式。
  • 追问:用户的问题有歧义时怎么办? 在 Prompt 里约定默认的时间范围和口径,或者先做意图识别,歧义太大时让系统反问用户,而不是让模型自己猜。

九、加强记忆

Text-to-SQL 是让懂 SQL、却不懂你数据库的大模型,把业务问题翻译成 SQL。只给表名列名最大的风险是「能跑但算错」:没扣退款、在明细表上重复累加,而且不报错。所以上下文要给四样东西——表说明、表关系、字段语义、指标口径,最好组织成 JSON;输出要结构化,取 sql、reason、usedTables、usedFields,但 usedTables 只是模型自述,安全判断要从 SQL 里解析。完整链路是意图识别、组装上下文、生成、校验、执行、失败纠错、摘要解读,只有生成这一步靠模型。准确率上不去多半是 Schema 太大、口径不清、问题歧义和复杂查询;核心指标交给固定口径工具,Text-to-SQL 管长尾。

项目实战落地

项目里怎么做的

《AI Agent数据分析平台》把 Text-to-SQL 需要的上下文做成了后台可维护的四层元数据:

  • 数据集(dataset):业务场景的入口,比如「电商销售分析数据集」;
  • 数据表(dataset_table):表名、表说明,以及关系说明 relation_description;
  • 字段语义(dataset_field):字段说明、字段类型、语义类型(维度 / 指标 / 时间 / 标识)、业务含义、是否可查询、是否可聚合;
  • 指标口径(metric_definition):指标名、编码、类型、口径公式和 SQL 表达式,比如销售额 sum(pay_amount)、订单数 count(distinct order_no)。

生成 SQL 时,后端只读取当前数据集下启用的表和指标,拼成两段 JSON 填进 Prompt 模板的 {datasetSchema} 和 {metricDefinitions},连同 {userQuestion} 一起交给模型。模板要求模型「根据数据集结构、字段语义、指标口径和用户问题生成只读 SQL,只返回 JSON,包含 sql、reason、usedTables、usedFields 字段」。解析出的结果写进分析记录 analysis_query 的 generated_sql、sql_reason、used_tables、used_fields,模型原文存进 raw_sql_response。

为什么这样取舍

  • 元数据放数据库,不写在 Prompt 模板里。 表结构和口径会变,管理员在后台改完,下一次生成就用新的,不用改代码、不用重启。
  • 只注入启用项。 停用的表和指标不进上下文,模型从源头上看不到不该用的东西。
  • SQL 类任务单独选模型。 生成 SQL 和纠错优先使用「SQL 分析」用途的模型,意图识别、解读这类文本任务用「文本分析」模型,互不干扰。

面试官还会追问

  • 用户先点「识别意图」、再点「生成 SQL」,后端怎么避免产生两条只做了一半的分析记录?
  • 重新生成 SQL 之后,页面上旧的查询结果、图表和解读为什么会一起消失?
  • 专门的「SQL 分析」模型没有配置时,系统会用哪个模型?

学完《AI Agent数据分析平台》,上面这些追问你都会迎刃而解。

本题落地项目登峰造极AI Agent数据分析平台基于Agent、Text-to-SQL和安全查询执行,实现数据集管理、指标口径维护、分析意图识别、SQL纠错、图表推荐、数据解读和报告生成,适合BI分析落地、过程追踪和多轮会话。SpringbootSpringAIAgentText-to-SQLLLM源码+SQL喂饭学习教程配套面试文档环境安装文档项目运行文档 学习这个项目