StarRocks STRING_AGG 函数完全指南:PostgreSQL 风格字符串聚合的语法、示例与底层实现
StarRocks STRING_AGG 函数完全指南PostgreSQL 风格字符串聚合的语法、示例与底层实现【免费下载链接】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导读STRING_AGG是 StarRocks 提供的一个字符串聚合函数用于将分组内非 NULL 的多个行值按指定分隔符拼接为单个字符串其语法与 PostgreSQL、SQL Server 同名函数保持一致。本文将围绕 StarRocks 官方文档讲解它的完整语法、参数语义、返回值与长度限制给出 9 个可直接运行的实战示例并从 FE 语法解析与 BE 聚合算子两个层面剖析其底层实现原理帮助你准确理解它同GROUP_CONCAT的关系以及在迁移 PostgreSQL/SQL Server 应用时如何正确使用。STRING_AGG 是什么STRING_AGG的作用是把一个分组GROUP内的非 NULL 值用指定分隔符连接成一个字符串。例如一张员工表每个部门有多名员工使用STRING_AGG可以把同一个dept_id下的所有员工姓名拼成一行。它是GROUP_CONCAT的语法糖syntactic sugar官方文档明确指出其底层实现完全复用现有group_concat的逻辑参见 string_agg.md设计目标是与 PostgreSQL 和 SQL Server 中同名函数保持兼容。因此从 PostgreSQL / SQL Server 迁移到 StarRocks 的应用可以直接沿用原有的STRING_AGG(name, ,)写法而无需改写为 MySQL 风格的GROUP_CONCAT。从源码角度也可以印证这一点FE 解析器在遇到STRING_AGG时直接将函数名改写为GROUP_CONCAT的注册名并标记为isStringAggAstBuilder.java注释明确写着// STRING_AGG is an alias for GROUP_CONCAT with PostgreSQL syntax // STRING_AGG(expr, delimiter) - GROUP_CONCAT(expr SEPARATOR delimiter)即STRING_AGG(expr, delimiter)在内部被等价转换为GROUP_CONCAT(expr SEPARATOR delimiter)。语法官方语法定义如下VARCHAR STRING_AGG([DISTINCT] expr, delimiter [ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [,col_name ...]])其中delimiter作为第二个参数出现且是必填的这是 PostgreSQL/SQL Server 风格与 MySQL 风格最直观的区别MySQL 风格使用SEPARATOR关键字且可省略。语法层面同样有证据StarRocks 的 ANTLR 语法规则要求STRING_AGG必须有两个表达式参数StarRocks.g4| STRING_AGG ( setQuantifier? expression , expression (ORDER BY sortItem (, sortItem)*)? )参数说明参数类型必填说明exprVARCHAR是要拼接的值NULL 值会被忽略可通过DISTINCT关键字对输出值去重。如需直接拼接多个expr可先用 concat 或 concat_ws 组合成带格式的单个表达式delimiterVARCHAR是用于连接不同行非 NULL 值的分隔符传入空字符串可以取消分隔符使所有值直接相连ORDER BY项无符号整数 / 列名 / 表达式否无符号整数从 1 开始编号对应expr的序号默认按升序排序可显式写ASC需要降序时在排序列名后加DESC注意事项如果expr不是 VARCHAR 类型需要先做类型转换例如cast(salary as varchar)因为该函数的输入必须是字符串类型。若不希望出现分隔符分隔符参数显式传空串即可而不是省略参数。返回值与输出长度限制STRING_AGG对每个分组返回一个字符串如果组内没有非 NULL 值则返回NULL而不是空字符串。这一点与GROUP_CONCAT行为一致在 BE 侧finalize_to_column中当聚合状态为空没有任何有效输入时直接输出默认值NULL见 group_concat.h。返回字符串的长度可以通过会话变量group_concat_max_len限制详见 System_variable.md默认值1024单位字符最小值4设置方式作用于当前会话或全局SET [GLOBAL | SESSION] group_concat_max_len value;例如SET GLOBAL group_concat_max_len 1024; -- 全局生效 SET SESSION group_concat_max_len 2048; -- 仅当前会话生效底层实现中BE 的FunctionContext提供了对应接口get_group_concat_max_len()读取上限而set_group_concat_max_len()在赋值时对小于 4 的值强制钳制为 4默认值为 1024function_context.hFE 侧对应的会话变量定义于 SessionVariable.javaprivate long groupConcatMaxLen 1024;。此外BE 在拼接时如果遇到截断边界落在多字节 UTF-8 字符中间会按get_utf8_index计算出的合法字符边界截断保证输出永远是完整的 UTF-8 字符相关逻辑位于 group_concat.h 的finalize_to_column中所以长度单位字符而不是字节。完整实战示例以下示例基于employees表包含部门、姓名、薪资三列含 NULL 数据完整覆盖拼接、去重、排序、嵌套表达式、全局聚合、空结果与长度限制等场景。1. 准备测试数据CREATE TABLE employees ( dept_id int(11) NULL COMMENT , name varchar(255) NULL COMMENT , salary int(11) NULL COMMENT ) ENGINEOLAP DUPLICATE KEY(dept_id) DISTRIBUTED BY HASH(dept_id) BUCKETS 4 PROPERTIES ( replication_num 1 ); insert into employees values (1, Alice, 5000); insert into employees values (1, Bob, 6000); insert into employees values (2, Charlie, 5500); insert into employees values (2, David, 7000); insert into employees values (2, NULL, 8000); insert into employees values (3, Eve, NULL); select * from employees order by dept_id, name; -------------------------- | dept_id | name | salary | -------------------------- | 1 | Alice | 5000 | | 1 | Bob | 6000 | | 2 | Charlie | 5500 | | 2 | David | 7000 | | 2 | NULL | 8000 | | 3 | Eve | NULL | --------------------------2. 基础用法按部门拼接员工姓名忽略 NULLselect dept_id, string_agg(name, ,) as names from employees group by dept_id order by dept_id; ------------------------ | dept_id | names | ------------------------ | 1 | Alice,Bob | | 2 | Charlie,David | | 3 | Eve | ------------------------注意部门 2 中姓名为 NULL 的行NULL, 8000被自动忽略部门 3 只有Eve一行。3. 使用|作为分隔符select dept_id, string_agg(name, | ) as names from employees group by dept_id order by dept_id; -------------------------- | dept_id | names | -------------------------- | 1 | Alice | Bob | | 2 | Charlie | David | | 3 | Eve | --------------------------4. DISTINCT 去重先插入一条重复数据再使用DISTINCT去除重复值insert into employees values (1, Alice, 5500); select dept_id, string_agg(distinct name, ,) as names from employees group by dept_id order by dept_id; ------------------------ | dept_id | names | ------------------------ | 1 | Alice,Bob | | 2 | Charlie,David | | 3 | Eve | ------------------------部门 1 中现在有两条Alice5000与5500但输出里只保留了一个Alice。5. ORDER BY 升序排序按薪资升序拼接姓名select dept_id, string_agg(name, , order by salary) as names from employees group by dept_id order by dept_id; ------------------------ | dept_id | names | ------------------------ | 1 | Alice,Bob | | 2 | Charlie,David | | 3 | Eve | ------------------------部门 2 中Charlie5500的薪资低于David7000因此先拼接Charlie。6. ORDER BY 降序排序select dept_id, string_agg(name, , order by salary desc) as names from employees group by dept_id order by dept_id; ------------------------ | dept_id | names | ------------------------ | 1 | Bob,Alice | | 2 | David,Charlie | | 3 | Eve | ------------------------7. 嵌套 concat() 拼接多列并格式化将name:salary拼接为整体再聚合同时按薪资排序select dept_id, string_agg(concat(name, :, cast(salary as varchar)), ; order by salary) as employee_info from employees group by dept_id order by dept_id; -------------------------------------- | dept_id | employee_info | -------------------------------------- | 1 | Alice:5000; Bob:6000 | | 2 | Charlie:5500; David:7000 | | 3 | Eve:NULL | --------------------------------------这里先用 concat 把姓名、冒号与cast成字符串的薪资合成一个 VARCHAR 表达式再交给string_agg聚合——这正是官方文档建议的需要拼接多个expr时用concat/concat_ws指定格式的典型写法。注意部门 3 的Eve薪资为 NULLconcat会把 NULL 拼成NULL字样。8. 不使用 GROUP BY 的全局聚合不带GROUP BY时整个表作为一个分组select string_agg(name, , order by name) as all_names from employees; --------------------------- | all_names | --------------------------- | Alice,Bob,Charlie,David,Eve | ---------------------------9. 无匹配结果时返回 NULL当WHERE条件过滤后没有任何行时返回 NULL 而非空串select string_agg(name, ,) as names from employees where dept_id 100; ------- | names | ------- | NULL | -------10. 通过 group_concat_max_len 限制输出长度将长度上限调低到 10 个字符后观察截断效果set group_concat_max_len 10; select dept_id, string_agg(name, , order by name) as names from employees group by dept_id order by dept_id; --------------------- | dept_id | names | --------------------- | 1 | Alice,Bob | | 2 | Charlie,Da | | 3 | Eve | ---------------------部门 2 的输出被截断为Charlie,Da恰好 10 个字符且截断不会破坏 UTF-8 字符完整性。与 GROUP_CONCAT 的对比两者功能完全一致仅语法风格不同-- PostgreSQL 风格 (STRING_AGG) SELECT STRING_AGG(name, , ORDER BY name) FROM employees; -- MySQL 风格 (GROUP_CONCAT) SELECT GROUP_CONCAT(name ORDER BY name SEPARATOR ,) FROM employees;维度STRING_AGGGROUP_CONCAT兼容方言PostgreSQL / SQL ServerMySQL分隔符写法第二参数必填(expr, delimiter)SEPARATOR sep关键字可选默认,多个 expr 直接拼接不支持需借助concat/concat_ws合成一个表达式支持直接传多个expr是否支持 DISTINCT / ORDER BY支持支持3.0.6 与 3.1.3 版本起具体到GROUP_CONCAT的完整语法与更多示例含SEPARATOR行为变更说明可参考 group_concat.md。官方文档给出的结论是两者产生的结果完全一致选择哪一种取决于你偏好的 SQL 方言或应用兼容性要求。底层实现原理FE 侧语法解析与函数名改写词法与语法定义词法文件将STRING_AGG定义为关键字StarRocksLex.g4语法文件定义其专属产生式强制要求expr, delimiter两个表达式参数StarRocks.g4这是delimiter必填的语法保证。AST 构建在 AstBuilder.java 中遇到STRING_AGG时设置functionName FunctionSet.GROUP_CONCAT并标记isStringAgg true。由于isStringAgg为真后续针对传统GROUP_CONCAT的未写SEPARATOR时的参数兼容处理分支会被跳过见 AstBuilder.java从而保证 PG 风格的二参数语义不被改写。函数注册string_agg作为GROUP_CONCAT的别名记录在 FunctionSet.java 中。BE 侧聚合状态与序列化STRING_AGG最终复用GROUP_CONCAT的聚合算子实现在 group_concat.h并在 aggregate_resolver_others.cpp 中注册旧版本GroupConcatAggregateFunction以分隔符长度 分隔符 值的紧凑中间格式在内存中累积字符串update时首元素额外记录分隔符长度便于finalize时剥离新版本GroupConcatAggregateFunctionV2内部名group_concat2使用列集合保存输出列、分隔符与排序列finalize_to_column阶段统一执行排序根据ASC/DESC与NULLS FIRST/LAST构建SortDescs并调用sort_and_tie_columns生成置换permutation去重DISTINCT场景下对输出列逐行比较标记重复行只在最终输出时剔除长度限制与 UTF-8 安全截断逐字符拼接时以ctx-get_group_concat_max_len()为上限遇到超长字符串会跳过最后一个分隔符并保证不输出 NULL 元素。因此忽略 NULL、空分组返回 NULL、ORDER BY排序、DISTINCT去重、group_concat_max_len截断这些语义都有明确的执行单元支撑且 V2 版本通过提前释放排序列内存等方式控制中间结果占用。使用建议从 PostgreSQL / SQL Server 迁移直接使用STRING_AGG(expr, delimiter)无需改写为 MySQL 语法若应用需要兼容 MySQL 生态则保留GROUP_CONCAT(... SEPARATOR ...)。多列拼接STRING_AGG只接受一个expr多列格式化请先使用concat/concat_ws合成字符串表达式见示例 7。控制输出规模对可能产生超长字符串的聚合主动设置group_concat_max_len避免内存与传输开销过大注意最小值为 4单位是字符。排序与去重需要每个分组内有序的结果时务必显式使用ORDER BY否则拼接顺序不保证存在重复值时按需加DISTINCT。NULL 语义输入行中expr为 NULL 会被忽略分组内全部为 NULL 或空组时返回 NULL做下游判断时注意区分 NULL 与空字符串。相关文档string_agg 官方文档group_concat 官方文档concat 函数、concat_ws 函数系统变量说明含 group_concat_max_len【免费下载链接】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),仅供参考