ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

StarRocks max_by 聚合函数详解:返回最大 y 对应的 x,并深入 BE 聚合引擎实现

StarRocks max_by 聚合函数详解:返回最大 y 对应的 x,并深入 BE 聚合引擎实现 StarRocks max_by 聚合函数详解返回最大 y 对应的 x并深入 BE 聚合引擎实现【免费下载链接】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/starrocksmax_by是 StarRocks 提供的一种关联取值聚合函数它返回与y的最大值相关联的x的值例如找出考试最高分对应的科目。本文基于 max_by 官方文档完整覆盖其语法、参数、返回值、使用注意事项与可复现实战示例并结合后端BE聚合引擎源码解释空值忽略、并列最大值取首个遇到等行为在底层如何保证以及该函数如何支持分布式聚合与窗口场景。一、功能与语法max_by的语义是在分组内找到y的最大值然后返回该最大y所关联的x。官方文档给出的典型用法是SELECT max_by(subject, exam_result) FROM exam;即返回考试分数最高的科目名。语法max_by(x,y)参数x任意类型的表达式y可排序类型的表达式。返回值返回与x相同类型的值。版本支持该函数自 v2.5 开始支持。二、使用注意事项文档约定与源码印证官方文档列出三条使用注意事项每一条都能在 BE 源码中找到精确对应1. y 必须是可排序类型y若使用不可排序类型如bitmap、hll会返回错误。这一点从 BE 的函数注册逻辑可以得到印证aggregate_resolver_maxminby.hpp 中的MaxMinByDispatcherInner通过if constexpr显式跳过了聚合类型与 JSON 类型的组合注册即注册表本身就只覆盖可排序、可比较的逻辑类型其余类型在函数解析阶段就无法命中注册项而报错。2. y 为 null 的行被忽略文档约定如果y包含 null 值则与 null 对应的行会被忽略。这一点在 maxmin_by.h 的update()入口直接实现void update(FunctionContext* ctx, const Column** columns, AggDataPtr __restrict state, size_t row_num) const override { if (columns[1]-only_null() || columns[1]-is_null(row_num)) { return; // y 为 null 的行直接跳过不参与聚合 } ... }聚合逐行更新时只要columns[1]即y当前行为 null 或整列全为 null就直接返回因此 null 的y永远不会成为候选最大值。3. y 并列最大时返回第一个遇到的 x文档约定如果多个x对应相同的最大y函数返回最先遇到的那个x。源码层面这由比较运算符的严格大于实现普通类型路径maxmin_by.h 的MaxByElement使用if (right state.value)字符串类型特化maxmin_by.h 的StringLTGuard分支使用state.slice_max().compare(right) 0JSON 类型路径则退化为*right state.value非严格比较maxmin_by.h。由于普通与字符串路径采用严格比较后到达的并列最大值不会覆盖已有结果于是先遇到的x被保留——这与文档示例中physicssubject_id 3与musicsubject_id 5同为 95 分、最终返回physics的行为一致。三、完整实战示例继承自官方文档以下示例完整来自 max_by 文档可直接复现。1. 创建表 examCREATE TABLE exam ( subject_id INT, subject STRING, exam_result INT ) DISTRIBUTED BY HASH(subject_id);2. 插入数据并查询insert into exam values (1,math,90), (2,english,70), (3,physics,95), (4,chemistry,85), (5,music,95), (6,biology,null); select * from exam order by subject_id; ------------------------------------ | subject_id | subject | exam_result | ------------------------------------ | 1 | math | 90 | | 2 | english | 70 | | 3 | physics | 95 | | 4 | chemistry | 85 | | 5 | music | 95 | | 6 | biology | null | ------------------------------------ 6 rows in set (0.03 sec)注意biology一行的exam_result为 null用于验证y 为 null 的行被忽略的规则。3. 查询最高分对应的科目physics与music同为最高分 95函数返回先遇到的physicsSELECT max_by(subject, exam_result) FROM exam; ------------------------------ | max_by(subject, exam_result) | ------------------------------ | physics | ------------------------------ 1 row in set (0.01 sec)biology的 null 分数被忽略不参与最大值竞争因此不影响结果。四、BE 源码纵深解析max_by 聚合函数如何实现1. 聚合状态Statemax_by与min_by共用同一套模板骨架MaxMinByAggregateFunctionmaxmin_by.h由OPMaxByElement或MinByElement与状态类型区分方向。以x为复杂/数值类型时的状态MaxByAggregateData为例maxmin_by.hvalue当前已见的最大y初始化为该类型的最小值RunTimeTypeLimitsLT::min_value()保证第一行数据必然成为候选min_by的对应状态则初始化为最大值buffer_result以序列化字节形式保存当前最优x。用序列化的原始字节而非 C 对象来保存x是为了让x可以是任意类型包括字符串、嵌套类型等null_result记录当前最优x本身是否为 null用于max_by_v2变体。对于y为字符串类型的场景MaxByAggregateDataStringLTGuard特化maxmin_by.h将y也用buffersize两个原始缓冲保存以支持变长比较。2. 注册与实例化约 1150 个聚合实例x的返回类型与y的键类型构成返回类型 × 参数类型的笛卡尔积组合数量很大。aggregate_resolver_maxminby.hpp 的注释明确说明max_by/min_by is an arg x ret cartesian (~1150 full aggregate instantiations)注册由MaxMinByDispatcher对外层键类型type_dispatch_all展开再经由AggregateFactory::MakeMaxByAggregateFunctionLT, not_filter_nulls()生成具体聚合函数aggregate_factory.hpp。注册时同时注册了两个变体max_bynot_filter_nullsfalse与max_by_v2not_filter_nullstruemin_by/min_by_v2同理。FE 侧在 FunctionSet.java 中也定义了MAX_BY与MAX_BY_V2常量说明两者都是当前代码库中真实注册的一等函数。3. x 为 null 时max_by 与 max_by_v2 的行为差异not_filter_nulls这个模板参数正是两个变体的分水岭见 maxmin_by.h 中MaxByElement::operator()标准max_bynot_filter_nulls false当某个候选行的x为 null 时is_null分支什么都不做——该行不成为结果聚合会越过它继续看其余行最终结果只可能来自非 null 的xmax_by_v2not_filter_nulls true当x为 null 但y命中最大值时会显式写入value right并置null_result true即最大值对应的 x 是 null会被如实保留并在 finalize 时输出 null。因此标准max_by的返回值为 null 只有一种情形分组内不存在任何y非 null 且x非 null 的行finalize_to_column中buffer_result.empty()时append_default()maxmin_by.h。4. 分布式执行中间状态的序列化与合并StarRocks 查询会分布到多个 BE 上聚合因此max_by必须支持局部结果再合并。convert_to_serialize_format()maxmin_by.h将中间状态编码为y 值 [ null 标志] x 的序列化字节的紧凑二进制merge()maxmin_by.h解码后再调用同一个OP()比较更新。从源码结构看合并逻辑与逐行更新复用同一套比较代码路径保证了多路局部最大值再取全局最大与单路结果的一致性并列时仍遵循先到者保留的语义。此外该函数实现了update_batch_single_state_with_frame()maxmin_by.h按窗口 frame 逐行回放更新从而支持窗口函数场景下带 frame 边界的计算。5. 测试覆盖聚合行为的单元测试位于 aggregate_test.cpp包含max_by相关用例可作为验证空值忽略、并列取值等边界行为的测试入口。五、相关函数与延伸阅读min_by(x, y)与max_by对称返回最小y对应的x两者共用 maxmin_by.h 中MinByElement/MinByAggregateData的同一套模板实现函数注册与类型分发的完整逻辑见 aggregate_resolver_maxminby.hpp 及其配套的aggregate_resolver_maxminby{1,2,3}.cpp函数工厂入口见 aggregate_factory.hpp原始文档docs/en/sql-reference/sql-functions/aggregate-functions/max_by.md。小结max_by(x, y)的语义简单直观——给谁打分最高就返回谁但在工程上它涉及类型笛卡尔积注册、null 语义、并列 tie-break 与分布式状态合并等多个环节。理解update中跳过 nully、MaxByElement中严格大于比较保留首个并列值、以及not_filter_nulls区分max_by与max_by_v2这三处关键代码就能完整把握该函数从 SQL 语义到向量化聚合引擎落地的全过程。【免费下载链接】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),仅供参考
返回列表