数据分析场景为什么要单独维护指标口径?口径公式和 SQL 表达式有什么区别?
简化版
因为业务里的「销售额」「客单价」「活跃用户」不是某一列,而是一个约定好的算法:算哪些订单、扣不扣退款、按什么去重。不把口径写下来,模型每次按自己的理解现写,同一个问题换种问法就可能算出不同的数,跨时间、跨对象都不可比。口径公式是写给人看的业务定义(「销售额 = 已支付订单的实付金额之和」),SQL 表达式是写给机器执行的片段(sum(pay_amount))。前者让业务和技术对齐口径,后者让模型照着用,结果稳定一致。
详细版
一条完整的指标口径通常包含:
| 字段 | 作用 | 例子 |
|---|---|---|
| 指标名称 | 用户问题里出现的业务词 | 客单价 |
| 指标编码 | 程序里唯一引用它的标识 | avg_order_amount |
| 指标类型 | 基础指标、计算指标、比率指标 | 计算指标 |
| 口径公式 | 业务语言的定义,给人对齐口径 | 销售额 ÷ 订单数 |
| 业务含义 | 这个指标用来衡量什么 | 每笔订单的平均支付金额 |
| SQL 表达式 | 可以直接嵌进 SQL 的计算片段 | sum(pay_amount) / nullif(count(distinct order_no), 0) |
| 状态 | 启用的才交给模型 | 启用 |
维护方式:口径按数据集管理,同一数据集内指标编码唯一;只把启用的口径注入 Text-to-SQL 和分析计划的上下文;口径变了,只改这一处,所有后续分析自动使用新口径。
完整版教学
一、指标不是字段,而是约定
数据库里有 pay_amount(实付金额),但没有「销售额」这一列。「销售额」是业务上的约定:
销售额 = 已支付订单的实付金额之和
不含已退款订单
按支付时间归属到月份
这三句话里任何一句理解不同,算出的数就不一样。人工写报表时,这些约定靠数据同学的经验记在脑子里;换成让大模型写 SQL,这些约定必须显式写下来交给它,否则它只能按字面猜。
二、没有口径时会发生什么
同一个用户,连着问两个问题:
问 1:「上个月销售额是多少」
模型:select sum(pay_amount) from biz_order where ... → 128 万
问 2:「上个月各城市的销售额」
模型:select city, sum(item_amount) from biz_order_item ... → 各城市加起来 131 万
两次用了不同的表、不同的金额字段,总数对不上。用户会问「到底哪个对」,而你很难回答。口径不统一,数据分析系统最宝贵的东西——可信度——就没了。 把「销售额 = sum(pay_amount)」写成口径交给模型,两次问答都按同一个表达式算,数字才能对得上。
记忆钩子:口径的价值在「一致」。同一个指标,换种问法、换个维度、换个时间,都要用同一套算法。
三、口径公式和 SQL 表达式为什么要分开
两者服务的对象不同:
| 口径公式 | SQL 表达式 | |
|---|---|---|
| 给谁看 | 业务方、管理员、审核人 | 模型、执行引擎 |
| 写法 | 自然语言或数学式 | 能嵌进 select 的 SQL 片段 |
| 例子 | 客单价 = 销售额 ÷ 订单数 | sum(pay_amount) / nullif(count(distinct order_no), 0) |
| 关注点 | 业务含义是否正确 | 能否执行、边界是否处理 |
只有公式,模型还得自己翻译成 SQL,翻译环节可能出错;只有 SQL,业务方看不懂,没法确认口径是不是他们要的。两者并存,业务确认公式、技术保证表达式,模型直接用表达式。
四、SQL 表达式要处理哪些边界
写 SQL 表达式时,有几个坑要在口径里一次性处理好,免得每次靠模型临场发挥:
-- 比率类指标要防除零:订单数为 0 时返回 null,而不是报错
sum(pay_amount) / nullif(count(distinct order_no), 0)
-- 计数类指标要去重:一个订单多条明细时,按订单号去重
count(distinct order_no)
-- 条件类指标要写全过滤条件
sum(case when order_status = '已支付' then pay_amount else 0 end)
这些细节写进口径,就等于把数据团队的经验沉淀下来,模型每次都能用对。
五、指标类型有什么用
常见分三类:
- 基础指标:直接对某个字段做聚合,比如销售额、订单数;
- 计算指标:由基础指标组合而成,比如客单价 = 销售额 ÷ 订单数;
- 比率指标:分子分母都是指标,结果是百分比,比如复购率、转化率。
分类的意义在于提醒使用方式:比率指标不能简单相加,各城市的转化率加起来没有意义,汇总时要重新用分子分母计算;计算指标在分组后要按组重算,而不是把明细的比值取平均。
六、从指标口径到语义层
当指标越来越多、被多个系统共用时,业界会把它们统一管理成语义层(也叫指标平台):
原始表 → 语义层(维度、指标、口径、关联关系)→ BI 报表 / 数据分析 Agent / API
语义层的好处是「一处定义、处处一致」:报表、Agent、接口都从同一个地方取口径。对数据分析 Agent 来说,语义层越完整,模型需要自己推断的就越少,准确率越高。在项目规模不大时,一张指标口径表加一张字段语义表,就是最小可用的语义层。
七、口径的维护与变更
口径不是写一次就完事:
- 按数据集管理:不同业务场景的「收入」可能是不同算法,指标挂在数据集下,编码在数据集内唯一;
- 只注入启用口径:废弃或待审核的口径设为停用,模型看不到就不会用;
- 变更要留痕:口径改了,新旧分析结果会对不上,要能说清楚「这个数是按哪一版口径算的」,重要场景应在分析记录里保存当时所用的口径。
八、常见误区与追问
- 误区:字段说明写清楚了,就不需要单独的指标口径。 很多指标是多个字段的组合或比值,不对应任何单一字段,必须单独定义算法。
- 误区:口径公式写了,模型自然会翻译成正确的 SQL。 翻译环节本身会出错,去重、除零、过滤条件都可能漏,应该直接给出可执行的 SQL 表达式。
- 误区:比率指标可以按维度直接加总或取平均。 比率要用分组后的分子分母重新计算,直接相加或平均会得到错误结果。
- 误区:口径定好就不用管了。 口径会随业务变化,要按数据集管理、只注入启用项,并能说明历史结果用的是哪一版口径。
- 追问:什么是语义层?和指标口径表是什么关系? 语义层统一管理维度、指标和关联关系,供报表、Agent 和接口共用;一张指标口径表加字段语义表就是最小可用的语义层。
- 追问:用户问了一个口径表里没有的指标怎么办? 让模型基于字段语义现写并在结果里说明「该指标无标准口径」,同时把高频出现的新指标沉淀进口径表。
九、加强记忆
指标不是一列数据,而是一个约定好的算法:算哪些记录、扣不扣退款、怎么去重、按哪个时间归属。没有口径,模型每次现写,换种问法就算出不同的数,系统失去可信度。口径表要有名称、编码、类型、公式、含义、SQL 表达式和状态:口径公式给人看,用来对齐业务;SQL 表达式给模型用,把去重、除零、过滤条件一次写好。基础、计算、比率三类指标提醒使用方式,比率不能直接相加。指标多了就是语义层,一处定义、处处一致;口径按数据集管理,只注入启用项,变更要能追溯。
项目实战落地
项目里怎么做的
《AI Agent数据分析平台》有一个专门的「指标口径管理」页面,数据存在 metric_definition 表:dataset_id 绑定数据集,metric_name、metric_code、metric_type(基础指标、计算指标、比率指标)、formula(口径公式)、business_meaning、sql_expression、status。
预置口径举例:
| 数据集 | 指标 | 口径公式 | SQL 表达式 |
|---|---|---|---|
| 电商销售分析 | 销售额 | 支付金额求和 | sum(pay_amount) |
| 电商销售分析 | 订单数 | 订单编号去重计数 | count(distinct order_no) |
| 电商销售分析 | 客单价 | 销售额 / 订单数 | sum(pay_amount) / nullif(count(distinct order_no), 0) |
| 招聘岗位分析 | 平均最高薪资 | 最高薪资平均值 | avg(salary_max) |
生成 SQL 时,buildMetricDefinitions 只读取当前数据集下启用的口径,转成 JSON 填进 Prompt 的 {metricDefinitions};指标口径管理接口只允许管理员调用,因为它会直接影响模型的上下文。
为什么这样取舍
- 公式和表达式分两列。 管理员在页面上能对照业务定义检查 SQL 表达式对不对,模型拿到的是可以直接用的表达式。
- 口径挂在数据集下。 电商的「销售额」和课程的「课程收入」都是对
pay_amount求和,但属于不同数据集,各自维护,互不干扰。
面试官还会追问
- 同一个数据集下,指标编码为什么不能重复?保存时是怎么校验的?
- Agent 生成分析计划时也会读取指标口径,它读的是哪几项?和生成 SQL 时读的一样吗?
学完《AI Agent数据分析平台》,上面这些追问你都会迎刃而解。