StarRocks stddev/stddev_pop 总体标准差聚合函数详解:语法、返回值与源码实现
2026/9/17 16:15:54 网站建设 项目流程

StarRocks stddev/stddev_pop 总体标准差聚合函数详解:语法、返回值与源码实现

【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks

本文基于 StarRocks 官方文档docs/en/sql-reference/sql-functions/aggregate-functions/stddev.md,系统讲解STDDEV/STDDEV_POP/STD三个总体标准差(population standard deviation)聚合函数的语法、参数约束与返回值,并结合后端 BE 的方差/标准差聚合实现(be/src/exprs/agg/variance.hbe/src/exprs/agg/factory/aggregate_resolver_variance.cpp)与前端 FE 的函数注册代码,说明其计算原理、分布式合并机制以及作为窗口函数使用的版本前提,帮助读者在实时分析场景中准确使用并理解该函数的底层行为。

一、函数概览与适用场景

StarRocks 中的stddev函数族用于计算表达式(通常为表中某数值列)的总体标准差,即把整个数据集视为完整总体而非抽样样本时的标准差。文档中给出的函数名为:

  • STDDEV(expr)
  • STDDEV_POP(expr)
  • STD(expr)

三者语义等价,均返回总体标准差。文档明确指出:自 v2.5.10 起,STDDEV也可以作为窗口函数使用,即可以出现在OVER (PARTITION BY ... ORDER BY ...)子句中,在分区与排序框内逐行计算滑动标准差。这在“计算每条记录相对其所在时间窗口波幅”的实时分析场景中十分有用。

与之相对,样本标准差由STDDEV_SAMP提供(参见 stddev_samp 文档),两者在分母上相差一个自由度(nn-1),后文源码部分会展示这一差异在实现中的体现。

二、语法与参数

语法

STDDEV(expr)

参数

  • expr:待计算的表达式。当它是一列时,该列必须能够求值为以下类型之一:TINYINTSMALLINTINTBIGINTLARGEINTFLOATDOUBLEDECIMAL

也就是说,STDDEV只接受数值类型输入,字符串、日期等非数值类型需要先转换为数值再传入。

三、返回值与计算公式

函数返回DOUBLE 值,计算的是总体标准差,其中n为表中行数:

该图片来自仓库 docs/en/_assets/stddevpop_formula.png,即 $\mathrm{stddev} = \sqrt{\frac{\sum(x_i-\bar{x})^2}{n}}$(分母为n,这是它与样本标准差stddev_samp分母为n-1的本质区别)。

文档示例

文档中的标准用法示例(TPC-H 风格的lineorder表):

mysql> SELECT stddev(lo_quantity), stddev_pop(lo_quantity) from lineorder; +---------------------+-------------------------+ | stddev(lo_quantity) | stddev_pop(lo_quantity) | +---------------------+-------------------------+ | 14.43100708360797 | 14.43100708360797 | +---------------------+-------------------------+

可见stddevstddev_pop结果完全一致,佐证了二者同为总体标准差。

空结果与 NULL 行为

从后端实现 be/src/exprs/agg/variance.h 的StddevAggregateFunction::finalize_to_column可以看到总体标准差(is_sample = false分支)的行为:

  • 当组内有效行数count > 0时,返回sqrt(m2 / count)
  • 当组内无任何有效行(count == 0,例如全部为 NULL 或空分组)时,返回0

这一“空组返回 0”的行为与样本标准差不同——stddev_sampcount <= 1时会通过AggNullPred判定为 NULL(见 variance.h 中StddevAggregateFunction::AggNullPred的定义)。因此在使用STDDEV时不需要担心单行分组导致 NULL 的问题。

四、源码级实现解析

4.1 函数注册:stddev、std、stddev_pop 的映射关系

BE 端在 be/src/exprs/agg/factory/aggregate_resolver_variance.cpp 的StdDispatcher中,对每一种支持的数值类型lt(要求lt_is_numeric<lt>lt != TYPE_DECIMAL256)注册了如下聚合函数名到实现类的映射:

resolver->add_aggregate_mapping<lt, DevFromAveResultLT<lt>, VarState>( "stddev", true, AggregateFactory::MakeStddevAggregateFunction<lt, false>()); resolver->add_aggregate_mapping<lt, DevFromAveResultLT<lt>, VarState>( "std", true, AggregateFactory::MakeStddevAggregateFunction<lt, false>()); resolver->add_aggregate_mapping<lt, DevFromAveResultLT<lt>, VarState>( "stddev_pop", true, AggregateFactory::MakeStddevAggregateFunction<lt, false>()); resolver->add_aggregate_mapping<lt, DevFromAveResultLT<lt>, VarState>( "stddev_samp", true, AggregateFactory::MakeStddevAggregateFunction<lt, true>(), typename StddevAggregateFunction<lt, true>::AggNullPred());

从源码结构看,stddevstdstddev_pop三者都映射到MakeStddevAggregateFunction<lt, false>——模板参数false表示“非样本”即总体标准差;而stddev_samp使用true(样本)。这与文档将三者列为同义函数完全吻合,同时也解释了为什么示例中stddevstddev_pop的结果分毫不差。

FE 端在 fe/fe-core/src/main/java/com/starrocks/catalog/FunctionSet.java 中定义了对应的函数名常量:

public static final String STDDEV = "stddev"; public static final String STDDEV_POP = "stddev_pop"; public static final String STDDEV_SAMP = "stddev_samp";

并有STDDEV_ARG_TYPE集合约束其入参类型,与文档中列出的 TINYINT ~ DECIMAL 参数约束相呼应。

4.2 在线算法:Welford 增量均值/二阶矩统计

STDDEV的核心实现是 be/src/exprs/agg/variance.h 中的DevFromAveAggregateFunction及其派生类StddevAggregateFunction。聚合状态结构只有三个字段:

template <typename T> struct DevFromAveAggregateState { // Average value. T mean{}; // The square of the difference between // each sample value and the average of all sample values. // It's calculated incrementally. T m2{}; // Items. int64_t count = 0; };

每来一个新数据点,update()采用经典的 Welford 在线算法增量更新,而不需要存储全部样本:

int64_t temp = 1 + this->data(state).count; TResult delta = column->immutable_data()[row_num] - this->data(state).mean; TResult r = delta / temp; // 对新均值的修正量 this->data(state).mean += r; // 增量更新均值 this->data(state).m2 += this->data(state).count * delta * r; // 增量更新二阶中心矩 this->data(state).count = temp;

其中m2即 $\sum(x_i - \bar{x})^2$,mean是均值。最终输出(非样本分支)就是sqrt(m2 / count),正是文档公式的程序化表达。这种单遍扫描、常数状态空间的设计使得STDDEV可以流式处理海量数据,不需要二次扫描。

对 DECIMAL 输入,update()中还包含DecimalV2Value/Decimal128P38S9的特化分支,保证中间量按十进制语义运算。同时模板别名DevFromAveResultLT定义了结果类型:

template <LogicalType LT, typename = guard::Guard> inline constexpr LogicalType DevFromAveResultLT = TYPE_DOUBLE; template <> inline constexpr LogicalType DevFromAveResultLT<TYPE_DECIMALV2, guard::Guard> = TYPE_DECIMALV2; template <LogicalType LT> inline constexpr LogicalType DevFromAveResultLT<LT, DecimalLTGuard<LT>> = TYPE_DECIMAL128;

可以推断,对 DECIMAL 输入列,BE 内部的聚合状态与结果会保持十进制类型以避免浮点精度损失;文档中“Returns a DOUBLE value”的表述以整数/浮点输入为主,实际使用 DECIMAL 列时可结合查询结果观察其返回类型。

4.3 分布式合并:merge 与序列化

StarRocks 查询通常在多个 BE 节点上并行聚合,因此部分状态需要可合并、可传输。DevFromAveAggregateFunction提供了:

  • serialize_to_column():将(mean, m2, count)三个字段顺序memcpy进 Binary 列,作为可传输的中间状态;
  • merge():按合并公式把远端部分状态并入本地状态:
TResult sum_count = this->data(state).count + count; this->data(state).mean = mean + delta * (this->data(state).count / sum_count); this->data(state).m2 = m2 + this->data(state).m2 + (delta * delta) * (count * this->data(state).count / sum_count); this->data(state).count = sum_count;

其中delta是两端均值之差,这是两路 Welford 状态合并的标准推导(类似方差合成公式),保证多节点局部聚合后合并得到的结果与单节点全量计算一致。这也意味着STDDEV属于可分布式分解的“可加聚合”(decomposable aggregate),能充分利用 StarRocks 的并行执行与跨节点 shuffle 聚合。

4.4 窗口函数支持

v2.5.10 起STDDEV支持作为窗口函数。从源码看,基类实现了窗口专用的批量入口:

void update_batch_single_state_with_frame(FunctionContext* ctx, AggDataPtr __restrict state, const Column** columns, int64_t peer_group_start, int64_t peer_group_end, int64_t frame_start, int64_t frame_end) const override { for (size_t i = frame_start; i < frame_end; ++i) { update(ctx, columns, state, i); } }

它按帧(frame)边界对当前窗口范围内的行逐条执行update,从而得到每一行对应窗口帧内的标准差;get_values()则负责把同一状态的值批量写入输出列的[start, end)区间,供同侪组(peer group)多行复用。

五、实践建议与边界说明

  1. 总体 vs 样本:统计整表/整群体的波动程度用STDDEVSTDDEV_POPSTD);对抽样数据估计总体波动时用 STDDEV_SAMP。二者在n较大时结果接近,小样本差异明显。
  2. 输入类型:仅数值类型可用(TINYINT/SMALLINT/INT/BIGINT/LARGEINT/FLOAT/DOUBLE/DECIMAL),且 BE 注册时排除了 DECIMAL256(见 aggregate_resolver_variance.cpp 中lt != TYPE_DECIMAL256的约束)。
  3. 版本前提:窗口函数用法要求 v2.5.10 及以上版本,旧版本中STDDEV只能作为普通聚合函数使用。
  4. 空组行为:全 NULL 或空分组时STDDEV返回 0(非 NULL),在按组统计波动性时可利用该特性区分“无数据”与“零波动”的场景需结合COUNT判断。
  5. 可验证依据:函数注册的完整测试可参考 FE 的 AggregateTest,其中包含 stddev 相关聚合计划断言;BE 端聚合实现的单元测试位于be/test/exprs/目录下。

六、小结

STDDEV/STDDEV_POP/STD是 StarRocks 中计算总体标准差的三个等价函数,返回 DOUBLE 值,输入须为数值类型,自 v2.5.10 起支持窗口函数用法。其底层由StddevAggregateFunction实现,基于 Welford 在线算法以(mean, m2, count)三字段状态单遍完成计算,并支持跨节点部分状态合并,因而既能高效处理大规模聚合,也能在分布式执行下保证结果精确。理解文档中的语法与公式之余,对照 variance.h 与 aggregate_resolver_variance.cpp 的实现,可以更准确地把握其在 DECIMAL 输入、空分组、并行 shuffle 等边界情况下的真实行为。

【免费下载链接】starrocksThe world's fastest open query engine for sub-second analytics both on and off the data lakehouse. With the flexibility to support nearly any scenario, StarRocks provides best-in-class performance for multi-dimensional analytics, real-time analytics, and ad-hoc queries. A Linux Foundation project.项目地址: https://gitcode.com/GitHub_Trending/st/starrocks

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询