SQL Server 2016无STRING_AGG函数时,如何合并TSQL临时查询结果列数据以减少输出行数
在SQL Server 2016中实现字符串分组合并(替代STRING_AGG)
SQL Server 2016确实没有自带的STRING_AGG函数,不过我们可以用经典的FOR XML PATH + STUFF组合来实现这个需求,这是2017版本之前处理字符串合并的标准方案。
思路分解
首先我们需要优化原查询中获取客户最后一笔订单的逻辑(原查询的关联子查询性能较差),用CTE(公共表表达式)先筛选出每个客户的最新订单,再基于这个结果关联其他表,最后对OrderContent进行分组拼接。
完整实现代码
WITH CustomerLastOrder AS ( -- 先获取每个客户的最后一笔订单ID SELECT CustomerID, SalesOrderID FROM ( SELECT CustomerID, SalesOrderID, -- 按客户分组,按订单日期倒序、订单ID倒序排名,确保取到最新的一笔 ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY OrderDate DESC, SalesOrderID DESC) AS rn FROM Sales.SalesOrderHeader ) AS sub WHERE rn = 1 ) SELECT CAST(SOH.OrderDate AS DATE) AS DateYMD, P.LastName, P.FirstName, -- 分组合并OrderContent,用逗号分隔(可自行修改分隔符) STUFF( ( SELECT ', ' + PP.[Name] + ' Qty: ' + CAST(SOD.OrderQty AS VARCHAR(10)) FROM Sales.SalesOrderDetail AS SOD JOIN Production.Product AS PP ON SOD.ProductID = PP.ProductID WHERE SOD.SalesOrderID = SOH.SalesOrderID FOR XML PATH(''), TYPE ).value('.', 'NVARCHAR(MAX)'), 1, 2, '' -- 移除开头多余的", "分隔符 ) AS OrderContent FROM Person.Person AS P JOIN Sales.Customer AS SC ON P.BusinessEntityID = SC.PersonID JOIN CustomerLastOrder AS CLO ON SC.CustomerID = CLO.CustomerID JOIN Sales.SalesOrderHeader AS SOH ON CLO.SalesOrderID = SOH.SalesOrderID GROUP BY CAST(SOH.OrderDate AS DATE), P.LastName, P.FirstName, SOH.SalesOrderID;
关键部分解释
CustomerLastOrder CTE:
用ROW_NUMBER()函数按客户分组,对每个客户的订单按日期倒序、订单ID倒序排名,只保留排名为1的订单(即最新订单)。相比原查询的关联子查询,这个方式只需要扫描一次Sales.SalesOrderHeader表,性能更优。STUFF + FOR XML PATH:
FOR XML PATH(''), TYPE:将当前订单下的所有OrderContent行拼接成一个XML片段,TYPE参数可以避免特殊字符(如&、<、>)被转义成XML实体(比如&)。.value('.', 'NVARCHAR(MAX)'):把XML片段转换成普通的字符串类型。STUFF(..., 1, 2, ''):移除拼接字符串开头多余的,分隔符(第一个参数是目标字符串,第二个参数是起始位置,第三个是要删除的字符数,第四个是替换内容)。
注意事项
- 如果需要修改分隔符(比如用
;代替,),只需要修改子查询中的,和STUFF函数里的删除字符数(比如分隔符是;的话,删除字符数改成2即可)。 - 如果
OrderContent中包含大量特殊字符,TYPE+.value的方式能保证字符不被转义,比直接用FOR XML PATH('')拼接成VARCHAR更可靠。
内容的提问来源于stack exchange,提问作者ku4er99




