group by 仅分组而不合并结果;真正的“合并”对应拼接字符串、聚合数值或拼接多维分组结果三种需求;sql server 2017+ 应优先使用 string_agg() 进行字符串拼接,因其语义清晰、支持排序且无 xml 转义风险。

直接说结论:GROUP BY 本身不“合并结果”,它只是分组;所谓“合并”,实际是三种常见需求——拼接字符串、聚合数值、或把多个不同维度的 GROUP BY 结果拼成一张表。选错方法会导致语义错误、性能下降或语法报错。
SQL Server 字符串拼接用 STRING_AGG(),不是 FOR XML PATH
SQL Server 2017+ 必须优先用 STRING_AGG(),它语义清晰、支持排序、无 XML 转义风险。FOR XML PATH 是兼容旧版本的权宜之计,容易出问题:
- 遇到特殊字符(如
&、)会自动转义成 <code>&、,拼出来是乱码 - 不能直接指定分隔符顺序,得靠子查询 +
ORDER BY嵌套,写法绕且易漏 -
STUFF()+FOR XML组合在空值多时可能返回 NULL,而STRING_AGG()默认忽略 NULL(加WITHIN GROUP (ORDER BY ...)可控)
正确写法示例:
SELECT dept, STRING_AGG(emp_name, ', ') WITHIN GROUP (ORDER BY hire_date) AS staff_list FROM employees GROUP BY dept;
多个 GROUP BY 结果垂直拼接用 UNION ALL,不是 JOIN
想把「按部门汇总」和「按城市汇总」的结果塞进同一张结果集,必须用 UNION ALL,不是 JOIN 或临时表。JOIN 会笛卡尔爆炸,临时表要额外 DDL 开销,而 UNION ALL 是最轻量的行追加方式:
- 所有子查询列数必须严格一致,类型尽量对齐(比如都用
TEXT或都用VARCHAR(100)) - 用
NULL::TEXT补位,别用空字符串——空字符串和 NULL 在后续聚合中行为完全不同 - 务必加
source字段标识来源,否则根本分不清哪行是部门统计、哪行是城市统计
示例:
SELECT 'by_dept' AS source, dept AS key_name, NULL::TEXT AS location, SUM(sales) AS amount FROM orders GROUP BY dept UNION ALL SELECT 'by_city', NULL::TEXT, city, SUM(sales) FROM orders GROUP BY city;
多维分组合并用 GROUPING SETS,别写三个 GROUP BY
需要同时输出「按国家」「按国家+省份」「按国家+省份+城市」三份聚合结果?别写三个查询再应用层合并。用 GROUPING SETS 一次扫描搞定,性能提升明显,且 NULL 值天然表示该层级被聚合:
- 数据库只扫一遍表,避免重复 I/O 和内存开销
- 结果中某列为 NULL,说明该列不在当前分组维度里(比如
province为 NULL,表示这是国家级汇总) - 不能在
GROUPING SETS内部用WHERE过滤行——它作用于分组前;要用HAVING过滤分组后结果
示例:
SELECT country, province, city, SUM(revenue) AS total FROM sales GROUP BY GROUPING SETS ( (country), (country, province), (country, province, city) );
数值字段合并必须明确聚合逻辑,不能只靠 GROUP BY
GROUP BY 本身不决定怎么“合并”数值,它只划分组;真正起作用的是你写的聚合函数。选错函数等于业务逻辑错误:
- 想加总就用
SUM(),别用MAX()或AVG()替代——后者改变业务含义 - 如果源数据有重复主键但数值应累加(比如多条订单明细),
UNION ALL+ 外层GROUP BY比JOIN更安全 - 用
AVG()时注意 NULL:NULL 行会被自动排除,但若你补了NULL::NUMERIC占位,AVG()仍会跳过它,导致分母变小
典型场景:合并多个临时表的销量
SELECT id, SUM(qty) AS total_qty FROM ( SELECT id, qty FROM #tmp_today UNION ALL SELECT id, qty FROM #tmp_yesterday ) t GROUP BY id;
最容易被忽略的是:字符串拼接和数值聚合的语义完全不可互换,强行混用(比如用 STRING_AGG() 处理金额字段)会导致结果无法参与后续计算;而 GROUPING SETS 的 NULL 不是脏数据,是维度折叠的明确信号——别急着用 COALESCE 盖掉它。











