ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Apache Ossie 表达式语言指南:聚合函数与可分解性(Distributive / Algebraic / Holistic)一次讲透

Apache Ossie 表达式语言指南:聚合函数与可分解性(Distributive / Algebraic / Holistic)一次讲透

Apache Ossie 表达式语言指南:聚合函数与可分解性(Distributive / Algebraic / Holistic)一次讲透

【免费下载链接】ossieApache Ossie, industry wide specification effort to standardize how we exchange semantic metadata across analytics, AI and BI platforms, providing a vendor neutral, single source of truth for semantic data项目地址: https://gitcode.com/GitHub_Trending/osi1/ossie

Apache Ossie 是 Apache 旗下的开源规范项目,目标是标准化分析、AI 与 BI 平台之间交换语义元数据的方式,为语义数据提供厂商中立的"单一事实来源"。本文聚焦 Ossie 表达式语言中的两块硬骨头:聚合函数怎么写,以及"可分解性(Decomposability)"为什么决定了指标能否被预聚合和分布式优化——这是写指标之前最该先建立的概念。

Ossie 表达式语言在哪一层运行?三层语义模型里的逻辑层速览

先给结论:表达式语言只服务于逻辑层(Logical Layer),也就是指标、字段、过滤器所在的那一层。

Ossie 把语义模型分成三层,可以把它当成一张地图来定位:

层职责表达式语言现状
本体层(Ontological Layer)面向更抽象的业务概念建模,接近 OWL 一类建模语言语言尚在另行定义中
逻辑层(Logical Layer)对应传统 BI 语义模型:metrics、fields、filters✅ 本文主角,Ossie SQL 表达式语言
物理层(Physical Layer)各数据库的原生 SQL由各引擎自行处理

逻辑层的表达式语言有四个设计原则:可移植(核心函数在所有合规实现中行为一致)、熟悉(基于 ANSI SQL:2003 核心语法)、分析聚焦(优先覆盖 BI 高频函数)、可扩展(厂商可通过方言扩展核心能力)。

还有一个容易被忽略的边界:这套语言用于声明模型(declaring),而不是查询接口。表达式里允许出现列/指标引用、CASE WHEN、算术与比较运算符、聚合函数、窗口函数等;但SELECT/FROM/JOIN由语义层接管,GROUP BY由粒度规格控制,WHERE用模型的 filter 属性替代。换句话说,你写的是"这个指标是什么",而不是"帮我跑一条查询"。完整定义见 core-spec/expression_language.md。

一张能力地图看懂 Ossie 聚合函数:从 SUM 到 APPROX_PERCENTILE

这一节能拿到的是一张"函数能力地图":哪些是必选(REQUIRED)、哪些是推荐(RECOMMENDED)、各自什么语义、属于哪一类可分解性——一张表看完,后面再按需查手册。

类别函数语法含义可分解性要求级别
核心聚合SUMSUM(expr)求和DistributiveREQUIRED
核心聚合COUNTCOUNT(expr)非空值计数DistributiveREQUIRED
核心聚合COUNT(*)COUNT(*)总行数DistributiveREQUIRED
核心聚合COUNT(DISTINCT expr)COUNT(DISTINCT expr)去重计数HolisticREQUIRED
核心聚合AVGAVG(expr)算术平均值AlgebraicREQUIRED
核心聚合MIN/MAXMIN(expr)/MAX(expr)最小 / 最大值DistributiveREQUIRED
统计聚合STDDEVSTDDEV(expr)样本标准差AlgebraicREQUIRED
统计聚合STDDEV_POP/STDDEV_SAMPSTDDEV_POP(expr)等总体 / 样本标准差AlgebraicREQUIRED
统计聚合VARIANCEVARIANCE(expr)样本方差AlgebraicREQUIRED
统计聚合VAR_POP/VAR_SAMPVAR_POP(expr)等总体 / 样本方差AlgebraicREQUIRED
分位数MEDIANMEDIAN(expr)中位数(第 50 百分位)HolisticREQUIRED
分位数PERCENTILE_CONTPERCENTILE_CONT(p) WITHIN GROUP (ORDER BY expr)连续分位数(插值)HolisticREQUIRED
分位数PERCENTILE_DISCPERCENTILE_DISC(p) WITHIN GROUP (ORDER BY expr)离散分位数(实际值)HolisticREQUIRED
近似聚合APPROX_COUNT_DISTINCTAPPROX_COUNT_DISTINCT(expr)概率草图近似去重计数,典型误差约 2%Sketch-basedRECOMMENDED
近似聚合APPROX_PERCENTILEAPPROX_PERCENTILE(expr, p)近似分位数(如 t-digest),典型误差约 1%Sketch-basedRECOMMENDED

其中p是 0 到 1 之间的数,0.75即第 75 百分位。

除了这张主表,还有两个必选支持的增强用法值得单独留意:

-- DISTINCT 修饰:SUM、COUNT 都支持 SUM(DISTINCT amount) COUNT(DISTINCT customer_id) -- 条件聚合:所有聚合都应支持基于 CASE WHEN 的过滤式聚合 SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END) COUNT(CASE WHEN status = 'completed' THEN 1 END)

条件聚合意味着"先过滤再聚合"不必依赖WHERE,这在逻辑层里正好契合了"表达式只声明、过滤交给 filter 属性"的设计。

可分解性怎么决定查询优化路径?Distributive、Algebraic、Holistic 与 Sketch-based 四类详解

这一节回答一个具体的工程问题:一个指标能不能"先分片算、再合并结果"?这直接决定了它能否走多阶段聚合、预聚合和分布式执行路径,是语义引擎优化空间的来源。

类别包含函数分片合并行为引擎视角的优化空间
Distributive(分布式)SUM、COUNT、MIN、MAX各分片结果按同形运算直接合并:和相加、极值取更大/更小最省——预聚合表可以完全替代明细扫描
Algebraic(代数式)AVG、STDDEV、VARIANCE分片结果不能直接平均,但保留少量中间量(如总和 + 行数)后即可公式换算合并中等——预聚合存中间量而非最终值,合并时做一次代数运算
Holistic(整体式)MEDIAN、PERCENTILE_CONT/DISC、COUNT(DISTINCT expr)分片结果无法合并,必须回到全量数据小——常规路径只能全量计算
Sketch-based(草图式)APPROX_COUNT_DISTINCT、APPROX_PERCENTILE用可高效合并的概率草图(HyperLogLog、t-digest 等)近似代替前两类大——牺牲约 1%~2% 精度换来分片可合并

💡 用三个场景把差异讲透:

  • SUM(Distributive):把全国销量拆成华东、华北两片各自求和,两片之和就是全国总和,零误差、可无限分片。
  • AVG(Algebraic):两片各自的平均值不能直接再平均——两片行数不同,直接平均会产生偏差。正确做法是每片保留"总和 + 行数",合并时做一次加权换算。
  • MEDIAN(Holistic):两片各自的中位数拼不出整体中位数(左半区间可能全是小值、右半全是大值,信息已经丢失),必须回到明细数据。

这就是为什么规范给每个函数都标注了可分解性:它不是分类学兴趣,而是写给执行引擎的"优化说明书"——Distributive 走"预聚合 + 合并",Algebraic 走"中间量 + 公式",Holistic 要么全量扫描、要么降级到 Sketch-based 近似。规范中提到的多阶段聚合扩展(Analytical Context Extension)正是建立在这套分类之上。

在语义模型里写出聚合指标:YAML 写法、多方言 dialects 与 NULLIF 防御性习惯

结论先行:聚合函数主要落在模型的metrics(指标)上,指标定义在模型层级、可以跨多个数据集引用。下面以仓库自带的 TPC-DS 示例 examples/tpcds_semantic_model.yaml 为例,从一个真实指标讲起。

先算一个"人效"指标。示例中的store_productivity(每员工销售额):

- name: store_productivity expression: dialects: - dialect: ANSI_SQL expression: SUM(store_sales.ss_ext_sales_price) / NULLIF(SUM(store.s_number_employees), 0) description: Sales per employee across stores datatype: Decimal ai_context: synonyms: - "sales per employee" - "employee productivity"

这一行表达式里藏着两个值得学的写法:

  1. 防御性除法:NULLIF(x, 0)在分母为 0 时返回 NULL 而不是报错。凡是写"总量 ÷ 总量"的比率指标,都应该给它加上——分母来自另一个聚合时,"分母恰好为 0"是迟早会发生的事。
  2. 可分解性在组合指标上会变化:分子分母都是SUM(Distributive),各自都能预聚合;但"两个可分解量相除"整体不再可分解——你不能把各分片的"人效"平均起来得到总体人效。正确的执行路径是:各分片分别合并分子、分母,最后再除一次。

再看多方言机制。指标的表达式挂在expression.dialects数组下,同一个指标可以为不同引擎并列声明不同方言版本:

expression: dialects: - dialect: ANSI_SQL expression: DATE_TRUNC('month', order_date) - dialect: BIGQUERY expression: DATE_TRUNC(order_date, MONTH)

比如DATE_TRUNC在 ANSI 与 BigQuery 里参数顺序相反,BigQuery 的分位数则对应APPROX_QUANTILES(amount, 100)[OFFSET(50)]这类数组取元素写法。方言机制把这些引擎差异收进声明本身,核心函数的语义保持一致——这正是 Ossie"厂商中立单一事实来源"的具体落点。另外注意ai_context.synonyms:给指标配上同义词,AI 检索和自然语言查询才能稳定命中它。

完整函数清单与合规级别(REQUIRED / RECOMMENDED / 方言扩展)见 core-spec/expression_language.md;指标结构的 schema 定义(ai_context等字段)见 core-spec/spec.md。

从 Tableau / DAX 迁移过来:聚合函数对照速查表

这一节给一张精简速查表,覆盖日常最高频的聚合函数;完整对照(日期、字符串、窗口函数)在 core-spec/expression_language.md 的 Cross-Reference 章节。

Ossie 标准TableauLooker StudioDAX
SUM(x)SUM(x)SUM(X)SUM(x)
COUNT(DISTINCT x)COUNTD(x)COUNT_DISTINCT(X)DISTINCTCOUNTNOBLANK(x)
AVG(x)AVG(x)AVG(X)AVERAGE(x)
STDDEV(x)STDEV(x)STDDEV(X)STDEV.S(x)
VARIANCE(x)VAR(x)VARIANCE(X)VAR.S(x)
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY x)PERCENTILE(x, 0.75)PERCENTILE(X, 75)PERCENTILE.INC(x, 0.75)

⚠️ 迁移时留意 DAX 与 SQL 的空值语义差异,规范里专门给出了两条提醒:

  • DAX 的COUNT(x)对应 SQL 语义应选COUNTA(都统计非空值,但 DAXCOUNT不支持布尔列);
  • DAXDISTINCTCOUNT会把 BLANK 当作一个取值计入,SQLCOUNT(DISTINCT x)则排除 NULL——可空列上会差 1,建议改用DISTINCTCOUNTNOBLANK对齐。

带走这三件事:要点回顾与从 examples / validation 开始的上手路径

最后收拢为三条可以记住的结论:

  1. 每个聚合函数都带一个可分解性标签,它决定执行策略。Distributive 分片合并零代价;Algebraic 需要中间量做公式换算;Holistic 必须全量计算或改用 Sketch-based 近似(约 1%~2% 误差)。理解这个分类,才能理解语义引擎的优化空间,也才能判断"这个指标能不能建预聚合表"。
  2. 核心函数必选、方言吸收差异。dialects数组让同一模型在 ANSI、Snowflake、BigQuery 等不同引擎上都正确运行;写比率指标时记得NULLIF防除零。
  3. 规范只声明、不查询。表达式语言描述"模型是什么",GROUP BY/WHERE等由语义层接管,不要把查询 SQL 写进指标定义。

上手路径建议:

  • 克隆仓库开始动手:git clone https://gitcode.com/GitHub_Trending/osi1/ossie
  • 通读 examples/tpcds_semantic_model.yaml,它是覆盖聚合、窗口函数指标与多方言声明的完整样本;
  • 对照 core-spec/expression_language.md 确认自己用到的函数是否属于 REQUIRED 集合;
  • 用 validation/ 目录下的校验工具验证自己的语义模型;
  • 若想扩展阅读语义结构,见 core-spec/spec.md 与 docs/。

【免费下载链接】ossieApache Ossie, industry wide specification effort to standardize how we exchange semantic metadata across analytics, AI and BI platforms, providing a vendor neutral, single source of truth for semantic data项目地址: https://gitcode.com/GitHub_Trending/osi1/ossie

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进