为什么你的「AI 问数」总是答不准?
你一定见过这样的演示:把大模型接上数据库,问一句“上个月各门店销售额”,它当场写出一段 SQL,跑出结果,全场鼓掌。
然后你把它接进公司真实的数据仓库,问同一句话,它开始了表演:
- 把 pay_amount(实付)当成 order_amount(应收),金额对不上财务;
- 把“销售额”理解成 SUM(price),忘了乘数量;
- 按门店分组时,分不清是门店编号还是门店名称;
- “北京”这个条件,挂到了省份字段上,结果只查回一个城市;
- 换个问法问同一件事,两次答案不一样——因为它每次都在重新理解你的业务。
结论很反直觉:问数答不准,90% 不是模型的问题,而是数据侧缺少一层“业务语义”。
模型再强,它看到的也只是一堆表名和字段名:t_order、amt、sk、flag。 它不知道“销售额”指哪个口径、“门店”是哪个字段、“上月”是按自然月还是滚动 30 天。
要让一句话问数真正可用,得先把业务语义沉淀下来:这就是维度建模和指标管理。 而它们共同的产物,叫做语义一致性。
一、维度建模:给数据装上「业务坐标系」
维度建模(Dimensional Modeling)是一套经典的数据仓库建模方法,核心就两件事:
事实(Fact) —— 业务过程发生了什么,可以累加、可以度量。比如订单明细里的金额、数量、成本。
维度(Dimension) —— 从什么角度看这件事。比如时间、门店、商品、品类、区域、客户。
事实表通过外键关联到各个维度表,形成一个“星型”结构。这样任何一次分析,本质都是: 选几个维度做分组,选几个事实做聚合。
听起来很朴素,但把它落到平台里,有三类维度必须被“建模”出来,而不是靠人记住:
① 普通维度 —— 门店、商品、客户这类主数据。 定义好业务字段,并指定其中一个成员 ID 作为主键(维度关联就用它),同时指定展示字段。 这样“按门店分组”永远落在同一个字段上,“按门店看”展示的是门店名称而不是一串编号。
② 层级维度 —— 品类(一级/二级/三级)、区域(大区/省/城市)这类有层次的维度。 建模型时只需要填一个“层级数”,平台自动生成 level1_id / level1_name … levelN_id / levelN_name 与层级路径。 这一步的价值在于:“按品类看”和“按一级品类看”变成了明确的两个层级,而不是让 AI 猜你想聚合到哪一层。
③ 时间维度 —— 这是被最多人低估的一类。 勾选“年 / 季 / 月 / 日”后,平台自动生成 year_id、quarter_id、month_id、day_id 以及主键 date_key, 并可以一键生成预置日期数据。
为什么时间维度这么关键? 因为“按月的销售趋势”和“看 3 月的数据”是两个完全不同的动作: 前者要按时间维度的月份字段分组,后者是时间范围过滤。 如果数据侧没有时间维度,模型只能自己编一套 strftime 逻辑,十次里有三次会写错—— 比如把每个月分别查一遍,再拼起来给你,慢且容易漏。
最后一步:把事实表的外键“指”到维度上,并指定一个时间字段作为时间周期字段。 做完这一步,数据才算真正有了坐标系:指标可以沿维度下钻,可以按时间聚合,可以跨维度交叉。
二、指标管理:让「销售额」在全公司只有一个口径
有了维度,还差一半:算多少钱、怎么算。
现实中的“销售额”,在不同人嘴里至少有五种:
- 下单金额(含未支付)
- 支付金额(去掉未付、退款)
- 应收金额(减去优惠)
- 含税 / 不含税
- 含运费 / 不含运费
如果这些口径只存在于 BI 报表的 SQL 里、分析师的口口相传里, 那么每次问数,模型都会重新发明一次口径——数字自然对不上。
指标管理要做的,就是把口径变成平台里的一等公民:
原子指标 —— 最基础、不可再拆的度量,绑定在明细事实表(DWD)上,用一段聚合公式定义:
销售额 = SUM(order_amount) 订单数 = COUNT(DISTINCT order_id) 毛利 = SUM(order_amount - cost_amount)
衍生指标 —— 由已有指标组合而成,用指标编码引用,不允许再写 SQL:
毛利率 = {sales_amount} 客单价 = {order_count} 件单价 = {sales_quantity}
业务限定 —— 把“只算已支付订单”“只看直营门店”这类条件固化进口径:
有效销售额 = 销售额,限定 order_status = 'PAID'
这套设计的三个隐性收益,比“能配指标”本身更重要:
- 口径唯一:毛利率永远是“毛利 ÷ 销售额”,引用的是同一个定义,不会有人偷偷换个分母;
- 口径可解释:每个指标都有编码、口径描述、单位,人和 AI 看到的是同一份说明书;
- 口径可下钻:原子指标绑定明细事实表,所以任何维度、任何时间粒度都能加上去聚合,而不是预先算死的几个数。
三、语义一致性:准确率的真正来源
把维度建模和指标管理放到一起看,你会发现它们其实在做同一件事: 把“业务语言”映射到“数据语言”,并且只映射一次。
这就是语义一致性。它直接决定了智能问数的天花板,具体体现在三个“不跑偏”:
① 指标不跑偏 问数时先匹配已定义的指标,而不是让模型凭空生成计算逻辑。 用户说“销售额”,平台匹配到 sales_amount 这个指标编码,取它的公式与业务限定。 模型只负责“把口语映射到编码”,不负责“临时发明公式”。
② 维度不跑偏 “按品类”“按大区”“按门店”必须落到真实的维度模型与字段上。 层级维度让“一级品类 / 二级品类”成为明确的层级选择;时间维度让“按月 / 按季”成为明确的时间粒度选择。 模型不需要猜字段,只需要选层级。
③ 条件不跑偏 “北京”到底该挂在城市、省份还是大区字段上? 这是最容易出错、也最容易被忽略的一环。语义层里存着维度成员值(门店城市、品类名称、大区名称), 问数时会先检索成员,把“北京”定位到最合适的字段,再生成过滤条件。
一句话总结:
没有语义层,问数是“模型现场理解你的业务”——每次都可能不一样。 有了语义层,问数是“模型在你的业务坐标系里查数”——口径由你定义,模型只负责翻译和取数。
四、DataY 怎么做:从建模到问数的四步链路
DataY 是一套轻量数据平台(一个 JAR,内嵌 DuckDB,2 核 4G 就能跑通全链路), 维度建模、指标管理、智能问数是同一条流水线上的三段,而不是三个割裂的功能。
第 1 步:把数据准备好,并建好语义层
数据接入(ETL / 数据同步)→ 维度建模 → 指标管理,全程可视化:
- 维度模型支持普通维度、层级维度、时间维度三种类型,字段自动生成,支持物化落表;
- 事实模型选 DWD 明细层,外键一键关联维度,指定时间周期字段;
- 指标支持原子指标、衍生指标与业务限定,保存前可以预览 SQL 核对口径,保存时做公式与引用校验。
第 2 步:把语义层“索引”起来
传统做法是把表结构塞进提示词,表一多就爆上下文,而且模型仍然在猜。 DataY 的做法是建一个本地向量知识索引,把三类东西向量化:
- 指标:名称(加权)、编码、口径描述、单位;
- 维度字段:维度名称、编码、字段名、字段描述;
- 维度成员值:门店城市、品类名称这类具体取值。
索引是按租户隔离的,问数时先检索、再推理。 (指标名称在索引里做了重复加权,避免“销售额”被“平均销售价格”这类相近说法稀释。)
第 3 步:把一句话拆成「四要素」
问数不是“一句话生成 SQL”,而是把问题拆成四个槽位,逐个用真实元数据填实:
指标 → 维度 → 业务限定 → 时间范围
举例:“最近一周北京各品类的销售额和毛利率”
- 指标:sales_amount、gross_profit_rate
- 维度:dim_category(按一级品类分组)
- 业务限定:dim_region.city = '北京'(先检索成员值确认“北京”属于城市字段)
- 时间范围:最近 7 天(含今天),换算成具体日期
整条链路是可解释的:命中了哪个指标、按哪个维度、加了什么条件、算了哪段时间,都会展示出来。 你可以像校验同事一样校验 AI。
第 4 步:大模型不写 SQL,只调平台的数据查询接口
这是“能不能上生产”的分水岭。
很多人以为智能问数的实现是“让大模型生成 SQL 去查库”。DataY 不是这么做的,也不建议这么做。
在 DataY 里,智能问数链路是这样的:
自然语言 → 四要素(指标 / 维度 / 限定 / 时间范围)→ 调用平台的指标查询接口 → 由服务端生成 SQL → 执行 → 返回结构化结果
模型手里只有 5 个只读工具,全部围绕语义层:
- retrieve_metric_context:语义检索,召回候选指标、维度字段与维度成员
- list_metrics:指标清单,用于把口语映射到真实指标编码
- describe_metrics:某个指标可用的维度、层级与时间字段
- sample_dimension_values:维度取值抽样,确认“北京”该挂哪个字段
- query_metric_data:唯一的数据出口,入参是结构化的指标+维度+条件+时间范围
注意:这个工具清单里没有任何可以执行任意 SQL 的能力。 模型能看到指标口径、能选维度和条件, 但它不生成 SQL、也不参与 SQL 的拼装——SQL 完全由服务端根据指标定义、维度模型和权限策略生成并执行。 (查询结果里会把实际执行的 SQL 一并返回,便于页面上展示和人工核对,但那是只读的“结果说明”, 不是模型写出来的东西。)
这样设计带来四个直接好处:
- 口径不可能被“临时发明”:模型只能引用已定义的指标,不存在“这次它又换了个算法”;
- 权限控制点唯一:所有取数都流经同一个接口,权限、行级范围只需要在这一个地方生效;
- 审计与治理可控:耗时、并发、返回行数都在这一个入口统一收口 (连“回给模型的明细行数”都有上限,避免大结果灌爆上下文);
- 换模型不影响口径:换一家大模型、换一个客户端,口径与权限策略不变。
一句话:模型只负责“翻译”和“选槽位”,取数这件事始终在平台的掌控之内。
五、为什么数据权限必须长在查询接口上
聊到这一步,很多团队会忽略一个更要命的问题:权限。
问数把取数门槛降到了“会说中文就行”,这既是它的价值,也是它的风险:
- 销售同事一句“上个月各大区销售额”,可能就把他本来无权查看的大区数据问了出来;
- 如果靠提示词约束模型——“请不要查询其他大区的数据”——那等于没有权限: 提示词是建议,不是边界,用户换个问法就可能被绕开;
- 如果每个数据出口(问数、面板、API、AI 客户端)各写一套权限逻辑,早晚会出现“某个入口漏了”。
DataY 的做法是把数据权限做进查询接口本身,而不是做在 AI 这一层:
① 权限模型:RBAC + 维度成员范围
角色绑定“数据范围”——指定哪个维度的哪些成员。例如:
- 角色「华东销售」:dim_region 限定 level1_name = '华东大区'
- 角色「上海门店经理」:dim_store.city = '上海'
同一维度内的多条条件按 OR 组合,跨维度按 AND 组合——这正是“可访问的维度成员集合”。
② 强制前置:拼进 SQL,而不是写进提示词
用户发起查询时,平台会先解析当前用户所有角色的生效范围, 在生成 SQL 阶段就把范围条件拼进 WHERE,再执行。这意味着:
- 用户怎么问,改不了它;
- 模型怎么理解、怎么拆解,也改不了它;
- 它是查询条件的一部分,无法被提问参数绕过。
③ 三个关键性质
- 入口一致:网页端智能问数、指标查询面板、MCP 客户端(Claude / Cursor 等) 走的是同一个查询接口,因此权限策略完全一致,不存在“网页挡住了、API 没挡”的缺口;
- 失败关闭(fail-closed)可选:如果受控维度没有和事实表建立关联, 平台可以配置成直接拒绝查询,而不是默默返回全量数据;
- 管理员例外是显式的:管理员不受数据范围限制,这是明确的角色语义,不是“漏配”。
结论:数据权限不是智能问数的附加功能,而是它能不能上线的前提。 没有权限兜底的问数,能力越强越危险;有了统一接口兜底,才敢把它开放给业务同事。
六、实测:电商场景,一句话问数
我们用内置的**「电商销售分析应用」资产包**做了实测(应用市场一键初始化即可获得):
- 数据源 2 个:业务源库 + 数仓
- 数据模型 9 个:日期维度(年/季/月/日)、区域维度(大区/省/城市三级)、品类维度(一/二/三级)、 门店 / 商品 / 客户维度,以及订单明细事实表(DWD)、门店日汇总(DWS)、品类排行(ADS)
- 指标 12 个:销售额、销量、成本、毛利、订单数、客户数、有效销售额(带业务限定)
- 毛利率、客单价、件单价、单均毛利、客均消费
- 任务 11 个:7 个 ETL + 3 个 SQL + 1 个统一编排(一键把源数据加工到汇总层)
实际问数效果:
问:“各一级品类的销售额和毛利率” 答:电子产品 167,738.85 元 / 29.63%;家居生活 20,990.20 元 / 32.87%;服饰鞋包 14,081.30 元 / 31.18%
问:“每个月的销售额和订单数” 答:一次查询返回 12 个月的分组结果,不需要把它拆成 12 次查询
问:“销售额和有效销售额分别是多少” 答:202,810.35 元 与 184,079.15 元——差额正是未支付与退款的订单, 说明业务限定真的生效了,而不是被悄悄忽略
这些数字,和「指标管理 → 指标查询」面板里手选指标查出来的结果完全一致。 这正是语义一致性的价值:AI 问数、指标面板、报表,用的是同一套口径。
七、怎么上手
方式一:本地跑起来(10 分钟)
- 克隆仓库:git clone cnb.cool/yuyuejia/da…
- 构建:mvn clean package -DskipTests
- 启动:java -jar datay-web/target/*.jar --DATAY_AI_API_KEY=sk-xxxx
- 打开 http://localhost:8080,默认账号 admin/admin
进去之后:
- 打开数据应用 → 应用市场,一键初始化「电商销售分析应用」 (数据源、模型、指标、ETL、编排全部自动建好,含 ID 替换);
- 到任务编排执行一次,自动把样例数据加工到数仓;
- 到数据模型 → 智能问数,点一下“重建知识索引”,然后问: “上个月各品类的销售额和毛利率”。
小提醒:如果你的库里已经有数据,第一次用问数前记得重建一次知识索引—— 指标、维度、维度成员都变了,索引也要跟着更新。
方式二:在线体验 👉 datay-demo.yuyuejia.com.cn/
方式三:接到你自己的 AI 客户端 DataY 提供 MCP 端点 /mcp/datay,把同一套指标问数能力(list_metrics / describe_metrics / sample_dimension_values / query_metric_data)暴露给 Claude Desktop、Cursor、DSH 等客户端。 同一套语义层,网页端和你的 AI 助手共用同一个口径。
八、写在最后
过去两年,大家把大量精力花在“换更强的模型”上,却常常忽略一个事实:
大模型解决的是“语言到意图”的问题,解决不了“意图到口径”的问题。 后者只能靠数据侧的语义层。
维度建模解决“从哪些角度看”,指标管理解决“按什么口径算”, 两者合起来就是语义一致性——它才是智能问数准确率的真正来源。
DataY 想做的事很朴素:让一套 2 核 4G 就能跑起来的数据平台, 把维度建模、指标管理、任务调度、智能问数串成一条完整的链路, 让“一句话拿数”从演示走向真正可用。