如何用SQL Server按客户编号将多行客户信息聚合为单行?
解决SQL Server中按客户编号聚合多行数据为单行的问题
针对你的需求,我们可以通过SQL Server的聚合函数和字符串拼接功能来实现,下面分两种常见场景给出解决方案:
场景1:合并客户名称,产品列取存在的有效值(优先保留YES)
如果希望产品列只要有一行是YES就显示YES,有NO但没有YES就显示NO,否则显示NULL,可以用MAX()聚合函数(因为字符串排序中YES > NO > NULL),配合STRING_AGG()(SQL Server 2017及以上版本支持)来合并客户名称:
SELECT MIN(ID) AS ID, -- 取最小的ID作为结果行的ID CUST_NUM, STRING_AGG(CUSTOMER_NAME, ',') WITHIN GROUP (ORDER BY ID) AS CUSTOMER_NAME, -- 按ID顺序合并名称 MAX(BOUGHT_PRODUCT_A) AS BOUGHT_PRODUCT_A, MAX(BOUGHT_PRODUCT_B) AS BOUGHT_PRODUCT_B, MAX(BOUGHT_PRODUCT_C) AS BOUGHT_PRODUCT_C, MAX(BOUGHT_PRODUCT_D) AS BOUGHT_PRODUCT_D FROM YourTableName -- 替换成你的实际表名 GROUP BY CUST_NUM;
执行这个查询后,对于你的示例数据,产品列BOUGHT_PRODUCT_A会返回YES(因为第二、三行都是YES),BOUGHT_PRODUCT_D返回YES——这和你给出的期望结果略有不同,推测可能是示例的特殊需求?如果确实需要保留第一行的产品列值,可以看场景2。
场景2:合并客户名称,产品列保留第一行的对应值
如果希望完全匹配你给出的期望结果,即产品列取该客户第一行(ID最小的行)的数值,同时合并所有客户名称,可以用窗口函数先标记第一行,再结合字符串拼接:
WITH RankedData AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY CUST_NUM ORDER BY ID) AS RowRank, -- 按ID给每个客户的行排名 STRING_AGG(CUSTOMER_NAME, ',') OVER (PARTITION BY CUST_NUM ORDER BY ID) AS AggregatedName -- 提前合并名称 FROM YourTableName ) SELECT ID, CUST_NUM, AggregatedName AS CUSTOMER_NAME, BOUGHT_PRODUCT_A, BOUGHT_PRODUCT_B, BOUGHT_PRODUCT_C, BOUGHT_PRODUCT_D FROM RankedData WHERE RowRank = 1; -- 只取每个客户的第一行
这个查询会返回你给出的期望结果:ID为1,客户名称合并为IHOP,I.H.O.P.,IHOP INC.,产品列和第一行完全一致。
兼容旧版本SQL Server(2016及以下)
如果你的SQL Server版本低于2017,不支持STRING_AGG(),可以用STUFF() + FOR XML PATH()的方式来拼接字符串:
对应场景1的旧版本写法:
SELECT MIN(ID) AS ID, CUST_NUM, STUFF((SELECT ',' + CUSTOMER_NAME FROM YourTableName t2 WHERE t2.CUST_NUM = t1.CUST_NUM ORDER BY t2.ID FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS CUSTOMER_NAME, MAX(BOUGHT_PRODUCT_A) AS BOUGHT_PRODUCT_A, MAX(BOUGHT_PRODUCT_B) AS BOUGHT_PRODUCT_B, MAX(BOUGHT_PRODUCT_C) AS BOUGHT_PRODUCT_C, MAX(BOUGHT_PRODUCT_D) AS BOUGHT_PRODUCT_D FROM YourTableName t1 GROUP BY CUST_NUM;
对应场景2的旧版本写法:
WITH RankedData AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY CUST_NUM ORDER BY ID) AS RowRank FROM YourTableName ), AggregatedNames AS ( SELECT CUST_NUM, STUFF((SELECT ',' + CUSTOMER_NAME FROM YourTableName t2 WHERE t2.CUST_NUM = t1.CUST_NUM ORDER BY t2.ID FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') AS AggregatedName FROM YourTableName t1 GROUP BY CUST_NUM ) SELECT rd.ID, rd.CUST_NUM, an.AggregatedName AS CUSTOMER_NAME, rd.BOUGHT_PRODUCT_A, rd.BOUGHT_PRODUCT_B, rd.BOUGHT_PRODUCT_C, rd.BOUGHT_PRODUCT_D FROM RankedData rd JOIN AggregatedNames an ON rd.CUST_NUM = an.CUST_NUM WHERE rd.RowRank = 1;
你可以根据自己的SQL Server版本和实际需求选择对应的写法,记得把YourTableName替换成你的实际表名哦。
内容的提问来源于stack exchange,提问作者Teeko
相关产品推荐
相关产品推荐

