StarRocks percentile_disc 分位数函数详解从 SQL 语法、实战示例到 C 聚合内核实现【免费下载链接】starrocksThe worlds 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/starrocksPERCENTILE_DISC是 StarRocks 提供的精确分位数聚合函数它基于输入列的离散分布返回真实的百分位值当精确的分位点落在两个相邻值之间时它返回两者中较大的那个值。本文完整继承官方文档对语法、参数、返回值与示例的说明并结合当前仓库中前端函数注册Java与后端聚合执行C的源码实现解释取较大值这一行为在引擎内部的落地方式帮助读者既能直接上手使用该函数也能理解其类型检查、常量处理与排序求值等底层机制。该函数自 v2.5 起受支持。函数定义与语义官方文档percentile_disc.md对该函数的核心定义是Returns a percentile value based on a discrete distribution of the input columnexpr. If the exact percentile value cannot be found, this function returns the larger value between the two closest values.即输入列expr的取值集合被视为一个离散分布函数返回该分布中对应percentile位置上的分位值若目标分位点无法精确命中某个数据值例如中位点落在两个值中间则返回两个最接近候选值中较大的那个即统计学中的上分位数upper percentile。这一取较大值的语义在后端实现中被逐字实现下文源码部分会给出对应代码行。语法PERCENTILE_DISC (expr, percentile)参数说明expr需要计算分位数值的列。该列可以是任意可排序的数据类型任意 sortable 类型均可。percentile想要查找的分位点。必须是一个0 到 1 之间的常数量化数。例如查找中位数时设置为0.5查找第 70 百分位时指定为0.7。关于percentile必须是常量这一点在 FE 分析器源码中有明确保障FunctionAnalyzer.java 中对PERCENTILE_DISC及其低基数变体LC_PERCENTILE_DISC做了专门处理——将第二个参数的类型强制归一为DOUBLE并使用IS_IDENTICAL比较模式精确匹配函数签名若没有匹配到任何注册签名则直接抛出SemanticException。这与文档中It must be a constant floating-point number between 0 and 1的约束相互印证。返回值返回值的数据类型与expr完全相同。FE 侧的分析器源码同样支持这一结论FunctionSet.java 在为每个可排序类型SORTABLE_TYPES注册PERCENTILE_DISC时入参签名为(type, DOUBLE)、返回类型直接就是type本身for (Type type : SORTABLE_TYPES) { addBuiltin(AggregateFunction.createBuiltin(FunctionSet.PERCENTILE_DISC, Lists.newArrayList(type, FloatType.DOUBLE), type, VarbinaryType.VARBINARY, false, false, false)); }值得注意的是分析器对 DECIMAL 类型还有一段额外的精度/标度修正逻辑FunctionAnalyzer.java当第一个参数为 DecimalV3 时会基于原始参数类型重新构造函数元数据确保返回值携带与输入列一致的精度和标度而不会因签名匹配过程发生隐式提升。使用注意事项NULL 值不参与计算NULL values are ignored in the calculation。下文的实战示例中chemistry科目存在一条score为 NULL 的记录但中位数结果仅由非 NULL 的 80 与 100 两个值决定正好可以验证该规则。实战示例以下示例完整来自官方文档。建表并写入数据CREATE TABLE exam ( subject STRING, score INT ) DISTRIBUTED BY HASH(subject); INSERT INTO exam VALUES (chemistry,80), (chemistry,100), (chemistry,null), (math,60), (math,70), (math,85), (physics,75), (physics,80), (physics,85), (physics,99);查看数据select * from exam order by subject; ------------------ | subject | score | ------------------ | chemistry | 80 | | chemistry | 100 | | chemistry | NULL | | math | 60 | | math | 70 | | math | 85 | | physics | 75 | | physics | 80 | | physics | 85 | | physics | 99 | ------------------计算每个科目的中位数select subject, percentile_disc(score, 0.5) from exam group by subject;输出---------------------------------------- | subject | percentile_disc(score, 0.5) | ---------------------------------------- | chemistry | 100 | | math | 70 | | physics | 85 | ----------------------------------------用源码公式逐步验证示例结果后端在最终阶段对每个分组收集到的值排序后按下式选取结果值详见下文 percentile_cont.h 中的finalize_to_columnindex ceil((n - 1) * rate) // n 为去 NULL 后的元素个数rate 为 percentile result sorted[index]逐组验证文档示例chemistry非 NULL 值排序后为[80, 100]n2index ceil(1 × 0.5) 1→ 取sorted[1] 100。两个值之间无法精确命中 50 分位按取较大值规则返回 100math[60, 70, 85]n3index ceil(2 × 0.5) 1→ 取sorted[1] 70physics[75, 80, 85, 99]n4index ceil(3 × 0.5) ceil(1.5) 2→ 取sorted[2] 85。三个结果与文档给出的输出完全一致同时也直观展示了精确分位点不可达时返回较大值的离散语义chemistry 组是典型例子。源码级实现原理FE函数注册与签名匹配注册范围覆盖所有可排序类型。FunctionSet.java 中PERCENTILE_DISC遍历SORTABLE_TYPES逐一注册第二个参数固定为DOUBLE同一循环中还注册了低基数列优化用的LC_PERCENTILE_DISCpercentile_disc_lc。这与文档中任意可排序类型的表述一致。第二个参数强制归一为 DOUBLE。如前所述FunctionAnalyzer.java 在语义分析阶段将argumentTypes[1]改写为FloatType.DOUBLE后再查签名因此写入 SQL 的0.5、0.7等字面量无需手动转 DOUBLE 即可匹配。BE聚合状态与最终求值percentile_disc的具体实现位于 percentile_cont.h与percentile_cont共用文件与状态结构核心类为PercentileDiscAggregateFunction。聚合状态由PercentileState定义percentile_cont.hstruct PercentileState { void update(CppType item) { items.emplace_back(item); } void update_batch(const ImmBufferCppType vec) { /* memcpy 批量追加 */ } ItemType items; // 该分组收集到的所有非 NULL 值 GridType grid; // 部分合并/归并路径使用的分格数据 double rate 0.0; // 第二个常量参数 percentile };最终求值finalize_to_column的关键逻辑// percentil_cont.hPercentileDiscAggregateFunction::finalize_to_column typename PercentileStateTypesLT::ItemType new_vector std::move(this-data(state).items); // ...合并 grid 中的分格数据后... pdqsort(new_vector.begin(), new_vector.end()); // ① 对分组内全部值排序 const double rate this-data(state).rate; if (new_vector.empty()) { column-append_default(); // ② 全组 NULL → 返回 NULL return; } if (new_vector.size() 1 || rate 1) { column-append(new_vector.back()); // ③ 单元素或 percentile1 → 最大值 return; } // choose the uppper one // ④ 源码注释即文档语义 int index ceil((new_vector.size() - 1) * rate); // ⑤ 上分位点公式其中步骤②对应文档中NULL 被忽略的极端情形——分组内全部为 NULL 时返回 NULL 默认值步骤④⑤即larger value between the two closest values的实现ceil((n-1) × rate)保证在分位点落在两个元素之间时索引向上取整从而选中较大的那个候选值结果类型按输入类型分支处理DATE、DATETIME 与数值/字符串/DECIMAL 分别走对应赋值路径若遇到未注册的不可排序类型则抛出runtime_error(Invalid PrimitiveTypes for percentile_disc function)这也从执行侧解释了为什么文档强调expr必须是可排序类型percentile_cont.h。常量参数的传输路径percentile 为何必须写常量文档要求percentile是常量BE 侧执行路径也体现了对这一约定的专门处理。在 aggregator.cpp 中聚合输入列的求值逻辑对第二参数做了区别对待// if function has at least two argument, unpack const column selectively // for function like percentile_disc, the second args is const, do not unpack it if (agg_expr_ctxs[j]-root()-is_constant()) { _agg_input_columns[i][j] std::move(col); // 常量列原样保留不展开 } else { _agg_input_columns[i][j] ColumnHelper::unpack_and_duplicate_const_column(chunk-num_rows(), std::move(col)); }注释明确点名percentile_disc第二个参数作为常量列直接传入聚合状态state.rate而无需像第一参数那样展开为与 chunk 行数对齐的普通列。这说明 FE 保证第二个参数是常量、BE 依赖该常量一次性写入聚合状态二者共同构成percentile 必须是 0~1 常量这一使用约束的实现基础。函数在 BE 侧的入口注册位于 aggregate_resolver_others.cpp按输入类型将percentile_disc解析为对应的PercentileDiscAggregateFunctionLT模板实例。小结PERCENTILE_DISC(expr, percentile)对可排序列做精确非近似离散分位计算percentile为 0~1 的常量精确分位点不可达时返回相邻两值中较大者源码以ceil((n-1) × rate)的取整方向直接落实该语义返回值类型与输入列完全一致NULL 不参与计算该实现是精确算法需要对每个分组的全部值排序pdqsort在分组基数极大、且只需估算分位的场景中应结合场景权衡是否改用近似分位类函数主要源码位置FE 注册见 FunctionSet.java参数分析见 FunctionAnalyzer.javaBE 求值见 percentile_cont.h常量参数处理见 aggregator.cpp。【免费下载链接】starrocksThe worlds 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),仅供参考