在业务系统里,经常遇到这样的需求:一个订单对应多条商品记录,产品经理希望报表里直接显示“商品A,商品B,商品C”这样的一行文本;或者一个部门下所有员工姓名要合并显示。如果每次都写一长串子查询加拼接逻辑,SQL会变得又长又难维护。STRING_AGG函数配合视图使用,可以把这些复杂逻辑一次性封装起来,后续查询直接SELECT视图即可,代码简洁度和可维护性都会大幅提升。

一、STRING_AGG函数的基本语法与工作原理
STRING_AGG是现代SQL标准中专门用于分组字符串聚合的函数,在PostgreSQL、SQL Server 2017及以上版本、以及部分国产数据库中都已支持。它的基本语法形式为:STRING_AGG(表达式, 分隔符),其中表达式是要拼接的列或表达式,分隔符是拼接时插入在两个值之间的字符串。
与传统的GROUP_CONCAT(MySQL)或FOR XML PATH方式(老版本SQL Server)相比,STRING_AGG的优势在于语义清晰、性能更好,且是SQL标准的一部分。它必须与GROUP BY子句配合使用,数据库引擎会按照分组字段把多行数据归拢,然后对每一组内的目标列执行拼接,最终每组只返回一行结果。
下面是一个基础示例,展示如何按部门拼接员工姓名:
SELECT
department,
STRING_AGG(employee_name, ', ') AS employees
FROM employee
GROUP BY department;
执行后,每个部门会返回一行,employees列就是该部门所有姓名用逗号加空格连接后的结果。整个过程不需要任何子查询或自连接,逻辑一目了然。
二、拼接过程中的排序、去重与NULL处理
实际使用中有三个细节必须处理好,否则结果可能不符合预期。
第一是排序问题。默认情况下STRING_AGG不保证拼接顺序,不同执行计划可能产生不同结果。如果需要按入职时间或姓名排序拼接,可以使用WITHIN GROUP子句显式指定:
SELECT
department,
STRING_AGG(employee_name, ', ')
WITHIN GROUP (ORDER BY hire_date) AS employees
FROM employee
GROUP BY department;
第二是去重问题。如果目标列存在重复值,拼接结果会出现重复文本。标准SQL中可以在表达式内使用DISTINCT,写作STRING_AGG(DISTINCT employee_name, ','),注意某些数据库要求DISTINCT与排序同时使用时排序字段必须和去重字段一致,否则会报错。
第三是NULL值处理。STRING_AGG会自动忽略NULL值,这一点比手工拼接友好很多。但如果一组内全是NULL,函数会返回NULL而不是空字符串。为了报表展示统一,通常配合COALESCE处理:
SELECT
department,
COALESCE(STRING_AGG(employee_name, ', '), '暂无员工') AS employees
FROM employee
GROUP BY department;
三、用视图封装拼接逻辑的完整实践
掌握了函数用法后,封装到视图是提升复用性的关键一步。先准备一套完整的表结构和测试数据:
CREATE TABLE department (
id INT PRIMARY KEY,
dept_name VARCHAR(50)
);
CREATE TABLE employee (
id INT PRIMARY KEY,
emp_name VARCHAR(50),
hire_date DATE,
dept_id INT
);
INSERT INTO department VALUES (1, '技术部'), (2, '市场部');
INSERT INTO employee VALUES
(1, '张三', '2021-03-15', 1),
(2, '李四', '2020-07-01', 1),
(3, '王五', '2022-01-10', 2),
(4, '赵六', '2019-11-20', 2);
接下来创建视图,把分组、排序、拼接的全部细节都封装进去:
CREATE VIEW v_dept_employee AS
SELECT
d.dept_name,
COUNT(e.id) AS employee_count,
STRING_AGG(e.emp_name, ', ')
WITHIN GROUP (ORDER BY e.hire_date) AS employee_list
FROM department d
LEFT JOIN employee e ON e.dept_id = d.id
GROUP BY d.dept_name;
这里使用LEFT JOIN是刻意为之:即使某个部门暂时没有员工,视图也会返回该部门一行,拼接列为NULL,配合外层查询可以进一步处理。创建视图之后,业务方查询时只需要一句简单的SELECT:
SELECT dept_name, employee_count, employee_list FROM v_dept_employee ORDER BY dept_name;
查询人员完全不需要了解底层有几张表、如何分组、如何排序,只需要面对一张逻辑上的结果表。当业务规则变化时,比如分隔符从逗号改成顿号,只需修改视图定义一次,所有依赖该视图的报表同时生效,这就是封装的价值所在。
四、兼容性注意事项与替代方案
STRING_AGG虽好,但并非所有环境都支持。MySQL直到8.0版本仍然使用GROUP_CONCAT,写法为GROUP_CONCAT(emp_name ORDER BY hire_date SEPARATOR ',');老版本SQL Server则常用FOR XML PATH加STUFF的组合技巧。如果系统需要跨数据库兼容,建议把这些差异也封装在视图内部,视图对外暴露统一的列名,底层实现按数据库调整。
另外要注意性能问题。STRING_AGG在分组数据量极大时可能产生超长字符串,SQL Server默认结果长度上限是8000字节,超过会报错,需要显式指定NVARCHAR(MAX)类型:写法为STRING_AGG(emp_name, ',') :: NVARCHAR(MAX)或使用WITHIN GROUP的变长声明。PostgreSQL则需要关注text类型的内存占用,必要时在视图层限制拼接行数,例如先用ROW_NUMBER筛选前N条再拼接。
最后一点建议:视图虽然好用,但不要把过于复杂的业务规则全部塞进一层视图,多层视图嵌套会让排查问题变得困难。合理的做法是保持每个视图职责单一,拼接逻辑封装在底层视图,上层视图或查询负责过滤和展示,层次清晰才能长期维护。
STRING_AGGSQL视图字符串拼接修改时间:2026-09-02 07:38:25