
GROUP_CONCAT 函数用户手册
1. 函数概述
1.1 功能说明
GROUP_CONCAT 函数是一个聚合函数,用于将分组中的多行字符串值连接成一个字符串。它在需要将分组数据汇总为单个字符串表示时非常有用,例如将同一电表在不同时间点的状态信息合并展示。
核心特点:
- 属于聚合函数,配合
GROUP BY使用 - 支持多列字段连接
- 支持自定义分隔符
- 自动跳过 NULL 值
1.2 语法
sql
GROUP_CONCAT(expr1 [, expr2, ..., exprN, separator])
1.3 参数说明
| 参数 | 类型 | 说明 | 是否必需 |
|---|---|---|---|
| expr1, expr2, ... | VARCHAR/NCHAR | 要连接的字符串字段或表达式 | 必需(至少1个) |
| separator | VARCHAR/NCHAR | 分隔符,作为最后一个参数 | 必需 |
1.4 返回值
- 类型:VARCHAR
- 说明 :返回连接后的字符串,最大长度不超过
TSDB_MAX_FIELD_LEN - 如果所有输入值均为 NULL,则返回 NULL
- 如果输入包含 NCHAR 类型,会自动进行字符集转换
1.5 适用版本
自 TDengine v3.3.8.0 开始支持
1.6 适用范围
- ✅ 普通表
- ✅ 超级表
- ✅ 支持嵌套子查询
- ✅ 可与 GROUP BY 结合使用
2. GROUP_CONCAT 与 CONCAT 的区别
2.1 核心差异对比
| 对比项 | GROUP_CONCAT | CONCAT |
|---|---|---|
| 函数类型 | 聚合函数(Aggregate) | 标量函数(Scalar) |
| 作用范围 | 多行数据 → 单个结果 | 单行数据 → 单个结果 |
| 使用场景 | 分组汇总,将多行合并 | 行内字段拼接 |
| GROUP BY | 通常配合使用 | 不需要 |
| NULL 处理 | 自动跳过 NULL 值 | 任何参数为 NULL 则返回 NULL |
| 参数个数 | 至少2个(字段+分隔符) | 2-8个 |
| 分隔符 | 最后一个参数作为分隔符 | 无分隔符(CONCAT_WS 支持) |
2.2 示例对比
CONCAT - 单行字段拼接
sql
-- 拼接单行的多个字段
SELECT CONCAT(location, '-', tbname) AS device_info
FROM meters
LIMIT 1;
-- 输出:
-- device_info
-- ===================
-- Beijing.Chaoyang-d1001
GROUP_CONCAT - 多行数据聚合
sql
-- 将同一位置的所有电表名连接起来
SELECT
location,
GROUP_CONCAT(tbname, ',') AS all_meters
FROM meters
GROUP BY location;
-- 输出:
-- location | all_meters
-- =======================================
-- Beijing.Chaoyang | d1001,d1002,d1003
-- Beijing.Haidian | d2001,d2002
2.3 适用场景选择
| 场景 | 推荐函数 | 说明 |
|---|---|---|
| 单行多列拼接 | CONCAT |
如:姓+名、城市+区域 |
| 多行汇总 | GROUP_CONCAT |
如:同组数据合并显示 |
| 需要分隔符的单行拼接 | CONCAT_WS |
带分隔符的 CONCAT |
| 需要分隔符的多行汇总 | GROUP_CONCAT |
带分隔符的聚合 |
3. 基础使用示例
3.1 智能电表表结构
sql
-- 创建智能电表超级表
CREATE STABLE meters (
ts TIMESTAMP,
current FLOAT,
voltage INT,
phase FLOAT
) TAGS (
groupid INT,
location VARCHAR(64)
);
-- 创建子表
CREATE TABLE d1001 USING meters TAGS (1, 'Beijing.Chaoyang');
CREATE TABLE d1002 USING meters TAGS (1, 'Beijing.Chaoyang');
CREATE TABLE d1003 USING meters TAGS (1, 'Beijing.Haidian');
CREATE TABLE d1004 USING meters TAGS (2, 'Shanghai.Pudong');
-- 插入测试数据
INSERT INTO d1001 VALUES
('2024-01-15 00:00:00', 10.2, 220, 0.95),
('2024-01-15 12:00:00', 15.8, 219, 0.94);
INSERT INTO d1002 VALUES
('2024-01-15 00:00:00', 9.8, 221, 0.96),
('2024-01-15 12:00:00', 14.5, 220, 0.95);
3.2 简单字符串连接
sql
-- 连接两个字符串字段
SELECT GROUP_CONCAT(location, tbname, '-') AS device_list
FROM (
SELECT DISTINCT location, tbname FROM meters LIMIT 3
);
预期输出:
device_list |
===========================================
Beijing.Chaoyangd1001-Beijing.Chaoyangd1002-Beijing.Haiدياند1003 |
3.3 配合 GROUP BY 使用
sql
-- 按区域分组,列出每个区域的所有电表
SELECT
SUBSTRING_INDEX(location, '.', 1) AS city,
GROUP_CONCAT(tbname, ',') AS meter_list,
COUNT(*) AS meter_count
FROM meters
GROUP BY city;
预期输出:
city | meter_list | meter_count |
====================================================
Beijing | d1001,d1002,d1003 | 3 |
Shanghai | d1004 | 1 |
4. 智能电表应用场景
场景 1:按区域汇总电表设备清单
业务需求:运维人员需要快速查看每个区域部署了哪些电表设备。
sql
-- 按位置分组,列出所有电表编号
SELECT
location,
COUNT(*) AS total_meters,
GROUP_CONCAT(tbname, ', ') AS meter_ids
FROM meters
GROUP BY location
ORDER BY total_meters DESC;
预期输出:
location | total_meters | meter_ids |
========================================================
Beijing.Chaoyang | 2 | d1001, d1002 |
Beijing.Haidian | 1 | d1003 |
Shanghai.Pudong | 1 | d1004 |
业务价值:
- 快速了解设备分布情况
- 便于运维人员制定巡检计划
- 支持设备清单导出
场景 2:多维度数据展示
业务需求:将电表的多个状态信息合并为一条记录便于展示。
sql
-- 将电表的关键指标连接成摘要信息
SELECT
tbname,
location,
GROUP_CONCAT(
CONCAT(
TO_CHAR(ts, 'HH24:MI'),
':',
CAST(current AS VARCHAR),
'A'
),
' | '
) AS hourly_current_summary
FROM meters
WHERE ts >= '2024-01-15 00:00:00'
AND ts < '2024-01-15 06:00:00'
GROUP BY tbname, location
LIMIT 3;
预期输出:
tbname | location | hourly_current_summary |
==================================================================
d1001 | Beijing.Chaoyang | 00:00:10.2A | 12:00:15.8A |
d1002 | Beijing.Chaoyang | 00:00:9.8A | 12:00:14.5A |
业务价值:
- 紧凑的数据展示
- 便于移动端查看
- 减少数据传输量
5. 与其他数据库的兼容性
5.1 MySQL 中的 GROUP_CONCAT
MySQL 原生支持 GROUP_CONCAT 函数,是该函数的标准实现。
MySQL 语法
sql
GROUP_CONCAT([DISTINCT] expr [,expr ...]
[ORDER BY {unsigned_integer | col_name | expr}
[ASC | DESC] [,col_name ...]]
[SEPARATOR str_val])
MySQL 特有功能
- DISTINCT 去重
sql
-- MySQL 支持
SELECT dept, GROUP_CONCAT(DISTINCT name SEPARATOR ', ')
FROM employees
GROUP BY dept;
- ORDER BY 排序
sql
-- MySQL 支持
SELECT dept, GROUP_CONCAT(name ORDER BY salary DESC SEPARATOR ', ')
FROM employees
GROUP BY dept;
- 自定义分隔符(可选)
sql
-- MySQL 分隔符可选,默认为逗号
SELECT GROUP_CONCAT(name SEPARATOR ' | ') FROM users;
SELECT GROUP_CONCAT(name) FROM users; -- 默认用逗号
- 长度限制
- 受
group_concat_max_len系统变量控制 - 默认 1024 字节,可动态调整
sql
SET SESSION group_concat_max_len = 10000;
MySQL 示例
sql
-- MySQL 完整示例
SELECT
department,
GROUP_CONCAT(
DISTINCT employee_name
ORDER BY salary DESC
SEPARATOR ' | '
) AS top_employees
FROM employees
WHERE salary > 5000
GROUP BY department;
5.2 PostgreSQL 中的 STRING_AGG
PostgreSQL 不支持 GROUP_CONCAT,但提供了功能相似的 STRING_AGG 函数(SQL标准函数)。
PostgreSQL 语法
sql
STRING_AGG(expression, delimiter [ORDER BY ...])
PostgreSQL 特点
- 必须指定分隔符
sql
-- PostgreSQL (9.0+)
SELECT dept, STRING_AGG(name, ', ' ORDER BY name)
FROM employees
GROUP BY dept;
- 支持 ORDER BY
sql
SELECT STRING_AGG(name, ', ' ORDER BY salary DESC)
FROM employees;
- 支持 DISTINCT(PostgreSQL 9.0+)
sql
SELECT STRING_AGG(DISTINCT city, ', ')
FROM customers;
- 无长度限制(除了内存限制)
PostgreSQL 替代方案
sql
-- 使用 ARRAY_AGG + ARRAY_TO_STRING
SELECT ARRAY_TO_STRING(ARRAY_AGG(name ORDER BY name), ', ')
FROM employees;
5.3 TDengine vs MySQL vs PostgreSQL
| 特性 | TDengine | MySQL | PostgreSQL |
|---|---|---|---|
| 函数名 | GROUP_CONCAT |
GROUP_CONCAT |
STRING_AGG |
| 分隔符 | 必需(最后参数) | 可选(SEPARATOR) | 必需 |
| DISTINCT | ❌ 不支持 | ✅ 支持 | ✅ 支持 |
| ORDER BY | ❌ 不支持 | ✅ 支持 | ✅ 支持 |
| 长度限制 | TSDB_MAX_FIELD_LEN | group_concat_max_len | 无限制 |
| 版本 | v3.3.8.0+ | 原生支持 | 9.0+ (STRING_AGG) |
| NULL 处理 | 跳过 | 跳过 | 跳过 |
| 多列连接 | ✅ 支持 | ✅ 支持 | ❌ 单列(需嵌套) |
5.4 跨数据库迁移建议
从 MySQL 迁移到 TDengine
sql
-- MySQL 原始查询
SELECT GROUP_CONCAT(DISTINCT name ORDER BY name SEPARATOR '|')
FROM users;
-- TDengine 等效查询(需要调整)
SELECT GROUP_CONCAT(name, '|')
FROM (SELECT DISTINCT name FROM users ORDER BY name);
注意事项:
- ❌ TDengine 不支持
DISTINCT和ORDER BY子句 - ✅ 需要通过子查询实现去重和排序
- ✅ 分隔符位置不同(TDengine 在最后)
从 PostgreSQL 迁移到 TDengine
sql
-- PostgreSQL 原始查询
SELECT STRING_AGG(name, ', ' ORDER BY name)
FROM users;
-- TDengine 等效查询
SELECT GROUP_CONCAT(name, ', ')
FROM (SELECT name FROM users ORDER BY name);
注意事项:
- ✅ 函数名不同但语义相似
- ✅ 分隔符参数位置调整
- ✅ 排序需通过子查询实现
6. 使用注意事项
6.1 NULL 值处理
sql
-- NULL 值会被自动跳过
CREATE TABLE test_null (ts TIMESTAMP, name VARCHAR(20));
INSERT INTO test_null VALUES
('2024-01-01 00:00:00', 'A'),
('2024-01-01 00:00:01', NULL),
('2024-01-01 00:00:02', 'B');
SELECT GROUP_CONCAT(name, ',') FROM test_null;
-- 输出:A,B (NULL 被跳过)
6.2 去重需要使用子查询
sql
-- ❌ 错误:TDengine 不支持 DISTINCT 直接用在 GROUP_CONCAT 中
SELECT GROUP_CONCAT(DISTINCT location, ',') FROM meters;
-- ✅ 正确:通过子查询去重
SELECT GROUP_CONCAT(location, ',')
FROM (SELECT DISTINCT location FROM meters);
6.3 字符集处理
sql
-- 自动处理 NCHAR 和 VARCHAR 混合
SELECT GROUP_CONCAT(varchar_col, nchar_col, '-')
FROM mixed_charset_table;
-- 会自动进行字符集转换
6.4 结果长度限制
sql
-- 注意:结果字符串不能超过 TSDB_MAX_FIELD_LEN
-- 对于大量数据,可能需要分批处理或使用 LIMIT
SELECT
location,
GROUP_CONCAT(tbname, ',') AS meters
FROM meters
GROUP BY location
HAVING COUNT(*) < 1000; -- 避免结果过长
6.5 分隔符必需性
sql
-- ❌ 错误:缺少分隔符
SELECT GROUP_CONCAT(name) FROM users;
-- ✅ 正确:必须提供分隔符
SELECT GROUP_CONCAT(name, ',') FROM users;
7. 性能优化建议
7.1 限制分组大小
sql
-- ✅ 推荐:对大表使用 WHERE 过滤
SELECT
location,
GROUP_CONCAT(tbname, ',') AS meters
FROM meters
WHERE ts >= NOW - 1d -- 限制时间范围
GROUP BY location;
7.2 避免过大的结果集
sql
-- ✅ 使用 HAVING 限制组大小
SELECT
location,
GROUP_CONCAT(tbname, ',') AS meters
FROM meters
GROUP BY location
HAVING COUNT(*) <= 100; -- 限制每组记录数
7.3 合理使用子查询
sql
-- ✅ 先过滤再聚合
SELECT
location,
GROUP_CONCAT(tbname, '|') AS active_meters
FROM (
SELECT DISTINCT location, tbname
FROM meters
WHERE groupid = 1
LIMIT 1000
)
GROUP BY location;
8. 常见问题 FAQ
Q1: GROUP_CONCAT 和 CONCAT 有什么区别?
A:
CONCAT是标量函数,用于单行内多个字段的拼接GROUP_CONCAT是聚合函数,用于多行数据的汇总连接CONCAT不需要 GROUP BY,GROUP_CONCAT通常配合 GROUP BY 使用
Q2: 如何实现 MySQL 的 DISTINCT 和 ORDER BY 功能?
A: 通过子查询实现:
sql
-- 去重 + 排序
SELECT GROUP_CONCAT(name, ',')
FROM (
SELECT DISTINCT name
FROM users
ORDER BY name
);
Q3: 分隔符可以省略吗?
A : 不可以。TDengine 的 GROUP_CONCAT 必须提供分隔符作为最后一个参数,这与 MySQL 不同。
Q4: 如何处理结果字符串过长的情况?
A:
- 使用
LIMIT限制输入行数 - 使用
HAVING过滤大组 - 缩短分隔符长度
- 分批查询
Q5: 能否连接数值类型字段?
A: 需要先转换为字符串类型:
sql
SELECT GROUP_CONCAT(CAST(id AS VARCHAR), ',')
FROM users;
Q6: COUNT(DISTINCT ...) 不支持怎么办?
A: 使用子查询实现:
sql
-- ❌ 不支持
SELECT COUNT(DISTINCT tbname) FROM meters;
-- ✅ 使用子查询
SELECT COUNT(*) FROM (SELECT DISTINCT tbname FROM meters);
Q7: PostgreSQL 用户如何适应 TDengine?
A:
- 将
STRING_AGG改为GROUP_CONCAT - 分隔符参数移到最后
- 通过子查询实现排序
Q8: 空字符串和 NULL 有什么区别?
A:
- NULL 值会被跳过,不影响结果
- 空字符串
''会被保留,参与连接
Q9: 可以连接多少个字段?
A: 理论上没有字段数量限制,但要注意:
- 总结果长度不能超过
TSDB_MAX_FIELD_LEN - 字段越多,性能可能下降
9. 相关函数
| 函数 | 类型 | 说明 | 关系 |
|---|---|---|---|
| CONCAT | 标量函数 | 单行多列拼接 | 行级操作,不聚合 |
| CONCAT_WS | 标量函数 | 带分隔符的单行拼接 | 类似 CONCAT |
| GROUP_CONCAT | 聚合函数 | 多行汇总连接 | 分组聚合 |
| STRING_AGG | 聚合函数(PG) | PostgreSQL 等效函数 | 功能相似 |
文档版本 :v3.3.8.0
最后更新 :2024-01-15
适用场景:智能电表数据分析、设备清单汇总、多行数据展示
重要提示:
- TDengine 不支持
WITH子句(CTE) - TDengine 的
COUNT函数不支持DISTINCT,需使用子查询实现 GROUP_CONCAT必须提供分隔符参数
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。