搭建指标语义层,不能从“给指标起一个好名字”开始,而要把一个业务指标拆成可审核的定义、可执行的计算、可连接的数据实体和可解释的使用边界。指标管理回答“这个指标是什么、谁负责、现在是否有效”;指标语义回答“在什么数据粒度、时间周期、过滤条件和关联路径下,统计维度,系统如何计算它”。只有两部分合在一起,BI、报表和 AI 问数才能围绕同一套口径生成结果。
文章以“订单完成率”为例,说明从指标登记到语义查询的实现方法。读完后,你可以建立指标元数据表、设计语义模型、定义度量与维度、处理关联和时间粒度,并为每个指标增加可验证的 SQL、样例结果和版本记录。
先区分指标管理与指标语义
指标管理是治理视角的目录。它记录业务人员如何理解和使用指标,也记录指标的负责人、状态、版本和审核过程。它的产物通常是一张指标目录,或者一个带生命周期的指标对象。
指标语义是执行视角的模型。它把指标映射到事实表、度量字段、实体关系、维度、时间轴和过滤器,最后能够把“按月查看华东区订单完成率”编译成一条确定的查询。语义层并不等于一张宽表,也不等于把所有 SQL 复制到 BI 工具里。它更接近一份可查询的业务模型。
两者的边界可以这样理解:
| 对象 | 主要问题 | 典型字段 | 主要使用者 |
|---|---|---|---|
| 指标管理 | 指标是否被定义、审核和维护 | 编码、名称、口径、负责人、状态、版本 | 业务、数据治理、管理者 |
| 指标语义 | 指标如何被计算、切分和关联 | measure、dimension、entity、时间粒度、过滤器 | 数据开发、BI、查询引擎、AI |
| 查询实例 | 这一次要在哪个范围内取数 | 指标、分组、过滤条件、时间约束 | 用户、报表、应用 |
如果只做指标管理,目录会越来越完整,但查询仍然依赖人工拼 SQL。如果只做指标语义,系统可以算出数,却说不清指标由谁负责、为什么变更、哪些场景不应该使用。建设顺序应该是先固定业务定义,再把定义翻译成可执行模型。
一套可落地的整体架构
最小可用的指标语义层至少包括五个部分:指标目录、语义模型、关系图、查询编译器和验证记录。
事实模型与维度模型提供可用字段、数据粒度和实体关联关系;指标目录在此基础上保存经过治理的指标定义、业务口径、适用范围和版本信息;指标语义模型再把这些业务定义映射为可执行的计算逻辑。关系图负责描述不同业务实体之间的连接方式,查询编译器根据用户选择生成 SQL,验证记录则保留口径校验、结果比对和版本使用依据。

事实模型与维度模型决定了指标可以基于哪些字段计算、数据是什么粒度以及数据模型如何关联;指标管理目录在这些数据基础上沉淀业务定义和口径约束;指标语义模型再将数据结构与业务口径组合为可复用的计算语义。
指标上线不能只检查 SQL 能不能运行,还要检查以下问题:
- 分子和分母是否处于同一数据范围。
- 多表关联是否改变了原始数据粒度。
- 时间字段是否明确了时区,以及自然月和财务月的差异。
- 指标定义中的过滤器是否在每次查询中都能生效。
- 同一个指标在明细、日报和大屏中是否使用了同一版本。
- 验证结果是否记录了使用的指标版本、查询条件和对比依据。
只有把这些验证信息保留下来,指标语义层才能从“能生成 SQL”进一步变成“结果可解释、口径可追溯、问题可复核”的基础设施。
指标管理应该包含哪些信息
指标管理对象最好采用稳定的唯一标识,而不是只依赖展示名称。展示名称可能从“支付成功订单数”改成“已支付订单数”,但指标标识应该保持稳定,避免下游查询和权限配置跟着改名。
唯一标识与基本信息
标识和基本信息解决“我说的是哪个指标”。至少记录:
| 字段 | 说明 | 示例 |
|---|---|---|
| metric_id | 永久稳定的指标标识 | order_completed_rate |
| name | 面向业务的名称 | 订单完成率 |
| aliases | 同义词和旧名称 | 完成率、履约完成率 |
| domain | 所属业务域 | 交易 |
| subject | 指标分析对象 | 订单 |
| description | 一句话业务解释 | 已完成订单数占有效订单数的比例 |
| tags | 检索和分类标签 | 订单、履约、比率、日 |
| display_format | 展示格式 | percentage |
aliases 对 AI 问数尤其有用。用户问“履约完成率”时,系统可以先把它归一到 order_completed_rate,再进入语义查询。别名不能替代正式定义,正式名称、编码和描述仍然要由指标负责人维护。
业务口径信息
业务口径是指标管理的核心。它不能只写“完成订单占比”,因为这句话没有说明订单范围、完成条件和排除规则。字段可以拆成以下几组:
- 统计对象:按订单、订单行、用户还是支付事件统计。
- 统计范围:哪些业务单据进入计算,哪些单据排除。
- 分子定义:完成状态的判定字段和取值。
- 分母定义:有效订单的判定条件。
- 去重规则:按订单 ID 去重,还是允许一单多行参与计算。
- 时间口径:下单时间、支付时间、发货时间还是完成时间。
- 空值规则:时间为空、状态为空时如何处理。
- 例外规则:退款、取消、测试单、内部单是否排除。
把口径拆成字段后,审核人可以逐项确认,审核对象也从整段文字变成明确字段。例如:
metric_id: order_completed_rate
name: 订单完成率
description: 有效订单中已完成订单的占比
subject: order
grain: one_row_per_order
numerator:
metric: completed_order_count
filter: status = 'completed'
denominator:
metric: valid_order_count
filter: is_test = false AND status != 'cancelled'
time:
field: completed_at
default_grain: day
null_policy: exclude_rows_without_completed_at
这段 YAML 仍然不是最终 SQL,但它已经把“比例怎么算”拆成了可以审查的结构。后续语义层可以将 numerator 和 denominator 分别映射到基础度量,再用统一的 ratio 规则组合。
责任、生命周期与审核信息
指标进入生产后,定义会变更。管理对象需要记录变更责任,而不是只保存当前值。至少需要:
| 信息 | 作用 |
|---|---|
| owner | 对业务含义负责的人或团队 |
| steward | 对数据质量和指标目录维护负责的人或团队 |
| approver | 通过口径审核的人或角色 |
| status | 草稿、审核中、已发布、已废弃等生命周期状态 |
| version | 当前定义版本 |
| effective_from | 当前版本生效时间 |
| deprecated_at | 废弃时间,可为空 |
| change_reason | 本次变更的原因 |
| change_ticket | 关联需求、工单或评审记录 |
| last_verified_at | 最近一次口径验证时间 |
指标状态要和运行时版本绑定。一个“已发布”指标如果仍然指向可变的 草稿 版本,那么指标状态和实际结果就会脱节。实际实现中,应给每个发布版本生成不可变快照,查询请求明确带上版本,或者由服务端解析当前有效版本并记录解析结果。
数据来源与可追溯信息
指标管理还应说明数据来自哪里。不要只保存一张表名,因为数据链路通常经过模型、字段、转换和授权层。至少记录:
- 来源模型:事实表、宽表或语义模型名称。
- 来源字段:分子、分母、时间字段和过滤字段。
- 数据层级:原始层、明细层、汇总层或服务层。
- 数据粒度:一行代表什么业务事件。
- 血缘入口:上游模型、下游报表、API 或应用。
- 数据刷新:刷新周期、延迟范围和最近刷新时间。
- 质量约束:非空、唯一、取值范围、及时性和对账规则。
这里的“粒度”必须和指标语义对齐。若事实表一行代表订单行,而指标按订单计数,管理对象必须明确使用订单 ID 去重,否则“订单数”很容易被误写成“订单行数”。
指标语义应该包含哪些信息
语义层需要把业务口径翻译成查询引擎可以执行的模型。一个指标的语义对象,至少要回答六个问题:算什么、基于什么粒度、在哪个时间轴、能按什么维度拆分、通过什么实体连接、哪些过滤器始终有效。
使用边界与权限信息
一个指标并不是在所有场景都可用。指标管理应记录:
- 推荐使用场景,例如经营日报、履约分析。
- 禁止使用场景,例如不能和支付金额直接相除,不能用于实时库存判断。
- 允许的分析维度,例如区域、渠道、订单类型。
- 敏感级别和数据权限要求。
- 是否允许导出、共享或提供给外部应用。
- 是否允许 AI 自动调用,是否需要人工确认。
这些信息会影响语义层的字段暴露。权限不是文章末尾补一个过滤条件就够了,它需要独立的权限控制模块来保证数据安全。
语义模型与数据粒度
语义模型应声明底层模型和它的行粒度。以订单事实模型为例:
semantic_models:
- name: orders
model: ref('fct_orders')
description: 每行代表一张有效订单或一条订单状态记录,具体以 snapshot_type 区分
defaults:
agg_time_dimension: ordered_at
entities:
- name: order
type: primary
expr: order_id
- name: customer
type: foreign
expr: customer_id
dimensions:
- name: status
type: categorical
expr: status
- name: region
type: categorical
expr: region_code
- name: ordered_at
type: time
expr: ordered_at
granularities: [day, week, month]
measures:
- name: order_count
agg: count_distinct
expr: order_id
- name: completed_order_count
agg: count_distinct
expr: order_id
filter: status = 'completed'
示例中的 grain、entity、dimension 和 measure 不是装饰字段。它们决定了查询是否可以安全生成。比如 order_count 使用 count_distinct(order_id),是因为底层可能存在订单状态快照;如果已经确认一行就是一张订单,也可以使用普通 count,但这个判断必须写进模型约束并由测试证明。
Measure:可聚合的度量
Measure 描述要从数据行中计算什么数量。常见类型包括:
| 类型 | 含义 | 示例 |
|---|---|---|
| count | 统计行数 | 订单行数 |
| count_distinct | 统计唯一实体数 | 唯一订单数 |
| sum | 对数值求和 | 商品收入 |
| average | 求平均值 | 平均履约时长 |
| min/max | 取边界值 | 最早完成时间 |
| expression | 基于已有度量计算 | 收入减成本 |
| cumulative | 按时间累计或滚动计算 | 月初至今收入 |
Measure 应该明确表达式、聚合类型、单位、格式和空值行为。下面是一个更接近实现的定义:
measures:
- name: product_revenue
label: 商品收入
expr: item_amount - item_discount
agg: sum
unit: CNY
format: currency
nullable: false
- name: fulfillment_hours
label: 履约时长
expr: timestamp_diff(completed_at, paid_at, hour)
agg: average
unit: hour
nullable: true
average 特别需要关注分组粒度。按订单平均履约时长和按用户平均履约时长,不一定能通过再次平均得到总体平均。语义层应保存分子、分母或原始可加度量,在查询时按目标粒度重新计算,而不是对已汇总结果做二次平均。
Dimension:可切分的分析属性
Dimension 描述“按什么看”。它通常来自事实表或维度表中的字段,也可以是 SQL 表达式。常见维度包括:
- 组织维度:区域、事业部、门店、销售团队。
- 业务维度:订单类型、渠道、客户层级、商品类目。
- 时间维度:日、周、月、季度、财务周期。
- 状态维度:订单状态、支付状态、履约状态。
- 派生维度:是否大客户、价格带、订单金额区间。
每个 dimension 至少应有名称、表达式、数据类型、展示标签和可用粒度。时间维度还要规定时区、周起始日、自然月与财务月。区域维度要说明编码表和层级路径,否则“华东”可能在不同系统中对应不同的编码集合。

维度不是越多越好。每增加一个维度,语义层都要回答它来自哪张表、如何关联、是否受权限控制、在指标定义中是否有特殊含义。把所有字段一股脑暴露出去,会让 AI 和用户在错误的粒度上组合指标。
时间语义与默认时间轴
指标必须有默认时间维度,否则“本月收入”和“本月完成订单数”可能分别使用下单时间与完成时间,用户却以为它们可以直接比较。时间语义建议拆分为:
event_time:业务事件发生时间。available_time:数据在仓库可查询的时间。metric_time:指标默认聚合时间轴。timezone:时间转换规则。calendar:自然日、自然周、财务周期或运营周期。freshness:数据延迟和可用范围。
time:
metric_time: completed_at
timezone: Asia/Shanghai
calendar: gregorian
week_start: monday
allowed_granularities: [day, week, month]
freshness_sla: "T+1 08:00"
一个查询请求可以覆盖默认时间轴,但不能默默覆盖。请求中显式指定 time_dimension: ordered_at 时,返回结果应记录这一选择,并在解释信息中告诉用户“订单完成率按下单时间统计”。
Metric:把度量组合成业务指标
业务指标通常不是一个裸的 sum。指标语义需要声明类型、组成度量、表达式、过滤器和结果格式。
metrics:
- name: valid_order_count
label: 有效订单数
type: simple
measure: order_count
filter: is_test = false AND status != 'cancelled'
- name: completed_order_count
label: 已完成订单数
type: simple
measure: order_count
filter: is_test = false AND status = 'completed'
- name: order_completed_rate
label: 订单完成率
type: ratio
numerator: completed_order_count
denominator: valid_order_count
scale: 100
format: percentage
这里要区分指标过滤器和查询过滤器。指标过滤器属于指标含义,任何调用 completed_order_count 的请求都要自动带上;查询过滤器只限制当前请求,例如“只看华东区域”。如果把固定的业务过滤器交给调用方填写,指标名称相同但结果口径会随页面变化。
派生指标还要说明依赖关系。一个比率指标不能只保存 numerator_sql 和 denominator_sql 两段字符串,否则血缘、版本和权限很难追踪。应保存引用的指标 ID,由编译器解析依赖图并检查分母为零、版本一致和时间轴一致。
从语义定义生成 SQL
语义查询可以使用结构化请求作为输入,避免让用户直接拼接 SQL:
{
"metric": "order_completed_rate",
"group_by": ["region", "metric_time"],
"time_dimension": "completed_at",
"time_grain": "month",
"filters": [
{"field": "region", "op": "in", "value": ["华东", "华南"]},
{"field": "completed_at", "op": "between", "value": ["2026-01-01", "2026-06-30"]}
],
"semantic_version": "2026.08.1"
}
编译流程应该先解析指标,再解析维度和实体,最后生成 SQL。不要一收到请求就把字符串拼进 SQL。
提供给 AI 的字段应是经过筛选的语义上下文:
- 指标名称、别名、定义和单位。
- 默认时间轴和允许的时间粒度。
- 可用维度、维度层级和枚举值。
- 指标固定过滤器与禁止的过滤器。
- 关联实体和安全 join path。
- 典型问题、对应查询和结果解释模板。
- 不确定时必须追问的条件。
AI 不能绕过语义层直接生成任意 SQL。否则指标管理记录的过滤器、权限和版本都会失效。更安全的流程是让模型只生成结构化查询,服务端完成字段白名单、类型校验、权限校验和 SQL 编译。
常见失败方式与修正方法
只存名称和公式
名称和公式能让人读懂一部分信息,但无法说明时间轴、粒度、权限和关联。修正方式是把公式拆成度量、过滤器、实体和时间配置,并为每个字段增加来源和验证记录。
只做一张指标宽表
宽表虽能够有效削减查询开销,但无法自动处理指标口径定义、版本管理以及跨业务主题关联问题。宽表适合作为语义查询产出的物化加速结果,不应当作为指标语义的唯一实现载体。语义层仍需要完整维护宽表字段背后对应的业务实体与计算生成规则。
把查询过滤器当成指标定义
如果让各个报表自己去加 status = 'completed' 这个过滤条件,同样一个指标,不同报表写出来的逻辑就可能不一样,算出来的数据也就对不上。这类固定的业务过滤,应该直接写在指标版本里面。报表这边的筛选,只用来缩小查询的数据范围,不要去改指标本身的计算规则。
忽略数据粒度
最危险的错误通常来自 join。订单事实连接订单明细后再统计订单数,很容易重复。修正方式是明确每个模型的 grain,声明主键和外键,对一对多join设置拒绝或聚合策略。
默认时间字段不明确
月销售额有多种统计口径:下单月、支付月、发货月。指标要设置默认的统计时间。允许查询时手动切换时间口径;一旦切换,结果里要标注清楚实际使用的时间口径。
只验证查询能运行
SQL 执行没报错,不代表业务口径就是对的。指标上线发布前要做校验,要看边界场景、表关联逻辑,还要做数据对比。至少要重点核对:分母为 0、join 带出重复数据、时间为空、跨月发生状态变更这些容易出错的情况。
结语
指标管理负责让定义可理解、可负责、可追踪;指标语义负责让定义可计算、可组合、可审计。落地时,先固定指标的统计对象、粒度、过滤器和时间口径,再把这些内容映射为 measure、dimension、entity 和 metric。查询服务只接受结构化请求,由语义编译器完成版本解析、连接检查、权限验证和 SQL 生成。
最终交付不应只有一个指标目录页面。一个可用的指标语义层,还应交付语义模型、版本快照、生成 SQL、测试样例和审计记录。这样同一个指标才能在报表、API 和 AI 问数中保持同一套计算规则。
评论