智能问数与 Text2SQL 经营看板落地

日期:2026-07-16

一、项目背景

某头部零售客户的数据团队常年被"取数"需求淹没:业务侧一个临时分析要经历提需求、排期、写 SQL、核对、出图五步,平均响应周期约 2-3 天,且业务人员自己写的 SQL 经常查错表、连错维度,返工频繁。我们当时判断,痛点不在算力也不在数据质量,而在"自然语言到 SQL"这最后一公里的门槛。这个项目里我们采用了"大模型生成 SQL + 查询层强制权限校验 + 看板沉淀"的架构,把取数能力直接交到业务手里,同时让每一次自动查询都跑在受控的权限边界内。那条硬红线是:生成的 SQL 绝不能直接落地执行,必须先过一道表/列级权限与注入校验。

二、落地场景

典型链路是:业务人员在对话面板用自然语言提问(如"上月华东区各门店的 GMV 和环比"),我们采用的 AI 网关把请求路由到大模型做 NL2SQL,模型产出 SQL 草稿与置信度;SQL 先送进查询代理层做白名单校验与参数化改写,确认只访问授权库表、无危险语句后,在只读从库执行;结果自动选图(折线/柱状/饼)并回填到可编辑看板。高频问句可一键"钉"成固定看板卡片,沉淀为经营驾驶舱。整个过程数据不出本地集群,模型只看到表结构语义描述,看不到真实业务数据行,从源头降低泄露面。

我们当初也试过把数据库样例行直接塞进 prompt 让模型"照着写",短期正确率确实高,但安全团队一票否决:样例里混进了真实订单与会员手机号,存在泄露风险。所以我们把 schema 知识库和真实数据彻底解耦——模型训练与推理期只接触表注释、字段类型、维度枚举值这类"骨架",真实"血肉"永远留在查询代理层之后的只读从库。这个解耦让同一个 NL2SQL 能力后来能平滑复用到某股份制银行的经营分析场景,因为权限与数据边界的架构是通用的,换库只换 schema 描述即可。

三、关键技术挑战与解决思路

挑战一:NL2SQL 准确率与防注入

模型生成的 SQL 最大的两个风险是"写错"和"写坏"。写错靠置信度与执行前 dry-run 兜:生成后先在影子库 EXPLAIN 一遍,执行计划异常或命中预期行数偏差过大就打回重生成。写坏靠注入防护:所有 SQL 强制走参数化,且查询代理层用 AST 解析器白名单拦截 DROP/UPDATE/DELETE/INSERT 以及 information_schema 探测,任何非 SELECT 直接拒绝。配置示意:

sql_guard:
  allow: [SELECT]
  deny: [DROP, DELETE, UPDATE, INSERT, TRUNCATE]
  forbid_schema_probe: true
  paramize: true

这套防护把我们遇到的恶意/异常 SQL 拦截率拉到 100%,未发生过一次越权写,也成为后续多租户接入的通用底座。

挑战二:表结构语义理解与置信度

零售客户库表多达数百张,模型容易把"销售额"映射到错的事实表。我们把表结构、字段注释、常用维度关系抽成向量化的 schema 知识库,提问时先检索最相关的表/列片段注入 prompt,给模型"带地图"生成,而不是盲猜。同时让模型一并输出置信度与所引用的表清单,低于阈值(0.7)的请求不自动执行,转为"请确认是否指 XX 表"的交互澄清。实测带 schema 检索的生成一次性正确率比裸生成提升约 25 个百分点,误连维度表的情况几乎消失。

挑战三:行级数据权限约束

列级白名单不够——业务人员只能看自己区域的数据。我们在查询代理层做行级约束注入:根据用户身份,在生成 SQL 的 WHERE 后自动追加组织过滤条件(如 region IN (...)),且这个条件业务侧不可见、不可被模型生成的 SQL 覆盖。例:

-- 模型生成
SELECT store_id, SUM(gmv) FROM fact_sales WHERE dt='2024-11' GROUP BY store_id;
-- 代理层注入后
SELECT store_id, SUM(gmv) FROM fact_sales WHERE dt='2024-11' AND region IN ('East') GROUP BY store_id;

这样即便模型"好心"帮用户查了全区域,也被边界条件拦在授权范围内,越权扫描在 SQL 执行前就被截断。

挑战四:结果可解释

业务不信任"黑盒吐数"。我们要求模型在出图同时生成一段自然语言解读,并附上实际执行的 SQL 与所用表,用户可点开核对"这个数怎么来的"。看板卡片默认展示 SQL 来源,异常波动自动标注可能的原因维度。可解释性直接把业务侧的采纳率从约 40% 提到约 75%,也减少了大量"这数对不对"的来回确认。

四、效果数据

该项目上线约两个月后的脱敏示意数据:

指标改造前改造后提升幅度
问数自助率约 15%约 70%提升约 55 个百分点
SQL 一次性正确率约 55%约 88%提升约 33 个百分点
平均取数耗时约 2 天约 10 分钟降至约 1/280
越权查询拦截依赖人工约 100%全程自动拦截

案例片段(已脱敏): 某区域运营提问"帮我看全国门店上周销量",模型生成跨全量 fact_sales 的查询。查询代理层识别其权限仅为华东区,注入 region IN ('East') 后执行,并返回提示:[GUARD] user=op_east role=region_limit -> inject WHERE region IN ('East'); deny cross-region scan用户侧看到的是仅华东区的结果与提示"已按您的区域权限过滤",实际未触达其他区域一行数据。

五、可复用经验总结

  • 权限在查询层做,不要在模型层做:模型不可信、易绕过,强制的 SQL 改写注入才是真正的边界。
  • 生成 SQL 必先校验表与列权限:白名单 + AST 解析 + 参数化三件套,是 NL2SQL 上生产的底线。
  • 给模型"带地图":schema 检索注入比裸生成正确率高一大截,且要让模型自报置信度与引用表。
  • 可解释是采纳率的开关:把 SQL 和执行来源亮出来,业务才敢用。
  • schema 与数据解耦:模型推理期只吃表结构"骨架",真实数据行留在代理层之后,这样能力能跨库跨客户复用,安全评审也只需过一遍架构而非逐条数据。

结语

Text2SQL 真正难的不是"生成一条能跑的 SQL",而是"生成一条只对、且只在权限内的 SQL"。把权限校验、注入防护、可解释这三道关卡前置到查询层,自然语言问数才从演示玩具变成业务日常。