如何将 T-SQL 中多条记录的字段按另一个字段分组?

问题描述 投票:0回答:15

我正在尝试连接其中一个表的行。这是基本的表结构:

评论

 ReviewID  
 ReviewDate  

审稿人

 ReviewerID  
 ReviewID  
 UserID  

用户

UserID  
FName  
LName  

这是一种 M:M 关系。每个Review可以有多个Reviewer;每个用户可以与许多评论相关联。

基本上,我想看到的是 Reviews.ReviewID、Reviews.ReviewDate 以及该评论的所有关联用户的 FName 的串联字符串(以逗号分隔)。

代替:

ReviewID---ReviewDate---User  
1----------12/1/2009----Bob  
1----------12/1/2009----Joe  
1----------12/1/2009----Frank  
2----------12/9/2009----Sue  
2----------12/9/2009----Alice  

显示这个:

ReviewID---ReviewDate----Users  
1----------12/1/2009-----Bob, Joe, Frank  
2----------12/9/2009-----Sue, Alice

我发现this文章描述了一些方法来做到这一点,但其中大多数似乎只处理一个表,而不是多个;不幸的是,我的 SQL-fu 不够强大,无法使这些适应我的情况。我对该网站上使用 FOR XML PATH() 的示例特别感兴趣,因为它看起来最干净、最直接。

SELECT p1.CategoryId,
( SELECT ProductName + ', '
  FROM Northwind.dbo.Products p2
  WHERE p2.CategoryId = p1.CategoryId
  ORDER BY ProductName FOR XML PATH('')
) AS Products
FROM Northwind.dbo.Products p1
GROUP BY CategoryId;
sql sql-server t-sql concatenation
15个回答
33
投票

看看这个

DECLARE @Reviews TABLE(
        ReviewID INT,
        ReviewDate DATETIME
)

DECLARE @Reviewers TABLE(
        ReviewerID   INT,
        ReviewID   INT,
        UserID INT
)

DECLARE @Users TABLE(
        UserID  INT,
        FName  VARCHAR(50),
        LName VARCHAR(50)
)

INSERT INTO @Reviews SELECT 1, '12 Jan 2009'
INSERT INTO @Reviews SELECT 2, '25 Jan 2009'

INSERT INTO @Users SELECT 1, 'Bob', ''
INSERT INTO @Users SELECT 2, 'Joe', ''
INSERT INTO @Users SELECT 3, 'Frank', ''
INSERT INTO @Users SELECT 4, 'Sue', ''
INSERT INTO @Users SELECT 5, 'Alice', ''

INSERT INTO @Reviewers SELECT 1, 1, 1
INSERT INTO @Reviewers SELECT 2, 1, 2
INSERT INTO @Reviewers SELECT 3, 1, 3
INSERT INTO @Reviewers SELECT 4, 2, 4
INSERT INTO @Reviewers SELECT 5, 2, 5

SELECT  *,
        ( 
            SELECT  u.FName + ','
            FROM    @Users u INNER JOIN 
                    @Reviewers rs ON u.UserID = rs.UserID
            WHERE   rs.ReviewID = r.ReviewID
            FOR XML PATH('')
        ) AS Products
FROM    @Reviews r

22
投票

事实证明,有一种更简单的方法可以做到这一点,并且不需要 UDF:

select replace(replace(replace((cast((
        select distinct columnName as X
        from tableName 
        for xml path('')) as varchar(max))), 
   '</X><X>', ', '),'<X>', ''),'</X>','')

10
投票

遇到了类似的问题,在玩了 15 分钟代码后找到了一个不错的解决方案

declare @result varchar(1000)
select @result = COALESCE(@result+','+A.col1, A.col1)
                FROM (  select  col1
                        from [table] 
                ) A
select @result

返回结果为 value1,value2,value3,value4

享受;)


7
投票

SqlServer 2017 现在具有 STRING_AGG,可以使用给定的分隔符将多个字符串聚合为一个。


6
投票

正如您所描述的,我有 3 种处理滚动数据的方法,1.使用游标,2.使用 UDF 或 3.使用自定义聚合(用 .NET CLR 编写)。
Cursor 和 UDF 相当慢。 (每行大约 0.1 秒)。 CLR 自定义聚合速度快得惊人。 (每行约 0.001 秒)

Microsoft 将代码(准确地执行您想要的操作)作为 SQL 2005 SDK 的一部分提供。如果您安装了它,您应该能够在此文件夹中找到代码: C:\Program Files\Microsoft SQL Server\90\Samples\Engine\Programmability\CLR\StringUtilities。 您可能还需要 MSDN 中的这篇文章。它讨论了安装自定义聚合并启用它: http://msdn.microsoft.com/en-us/library/ms161551(SQL.90).aspx

编译并安装自定义聚合后,您应该能够像这样查询:

SELECT Reviews.ReviewID, ReviewDate, dbo.StringUtilities.Concat(FName) AS [User]
FROM Reviews INNER JOIN Reviewers ON Reviews.ReviewID = Reviewers.ReviewID
   INNER JOIN Users ON Reviews.UserID = Users.UserID
GROUP BY ReviewID, ReviewDate;

并获得如上所示的结果集


5
投票
select p1.Availability ,COUNT(*),
(select  name+','  from AdventureWorks2008.Production.Location p2 where 
p1.Availability=p2.Availability for XML path(''),type).value('.','varchar(max)') 
as Name  from AdventureWorks2008.Production.Location p1 group by Availability

结果

Availability  COUNT     Name  
---------------------------------------------------------------------------------
0.00    7   Tool Crib,Sheet Metal Racks,Paint Shop,Paint Storage,Metal 
                    Storage,Miscellaneous Storage,Finished Goods Storage,
80.00   1   Specialized Paint,
96.00   1   Frame Forming,
108.00  1   Frame Welding,
120.00  4   Debur and Polish,Paint,Subassembly,Final Assembly,

5
投票

现在从 SQL Server 2017 开始有一个新的 T-SQL 函数,称为

STRING_AGG
:
它是一个新的聚合函数,用于连接字符串表达式的值并在它们之间放置分隔符值。
字符串末尾不添加分隔符。

示例:

SELECT STRING_AGG ( ISNULL(FirstName,'N/A'), ',') AS csv 
FROM Person.Person; 

结果集:

John,N/A,Mike,Peter,N/A,N/A,Alice,Bob

3
投票

A UDF 将是解决这个问题的好方法。

只需定义一个 T-SQL 函数 (UDF),它接受一个 int 参数(产品 ID)并返回一个字符串(与产品关联的名称的串联)。如果您的方法的名称是 GetProductNames,那么您的查询可能如下所示:

SELECT p1.CategoryId, dbo.GetProductNames(p1.CategoryId)
FROM Northwind.dbo.Products p1
GROUP BY CategoryId

3
投票

试试这个:

 Declare @Revs Table 
 (RevId int Priimary Key Not Null,
  RevDt DateTime Null,
  users varChar(1000) default '')

 Insert @Revs (RevId, RevDt)
 Select Distinct ReviewId, ReviewDate
 From Reviews
 Declare @UId Integer
 Set @Uid = 0
 While Exists (Select * From Users
               Where UserID > @Uid)
 Begin
    Update @Revs Set
      users = users + u.fName + ', '
    From @Revs R 
       Join Reviewers uR On ur.ReviewId = R.RId
       Join users u On u.UserId = uR.UserId 
    Where uR.UserId = @UId
    Select @Uid = Min(UserId)
    From users
    Where UserId > @UId
  End
  Select * From @Revs

3
投票
Select R.ReviewID, ReviewDate
, (Select  FName + ', ' 
   from Users 
   where UserID = R.UserID 
   order by FName FOR XML PATH(')
) as [Users]
from Reviews
inner join Reviewers AS R
  On Reviews.ReviewID = R.ReviewID
Group By R.ReviewID, ReviewDate;

2
投票

似乎您需要 group_concat 的功能(来自 mysql)。此处已针对另一个测试数据集解决了这个问题:如何在一个列中返回多个值 (T-SQL)?


2
投票

创建一个临时表来转储数据。然后使用 FOR XML PATH 方法。需要外部查询来删除列表中的最后一个逗号。

CREATE TABLE #ReviewInfo (
ReviewId INT,
ReviewDate DATETIME,
Reviewer VARCHAR(1000))

INSERT INTO #ReviewInfo (ReviewId, ReviewDate, Reviewer)
SELECT r.ReviewId, r.ReviewDate, u.FName
FROM Reviews r
JOIN Reviewers rs ON r.ReviewId = rs.ReviewId
JOIN Users u ON u.UserId = rs.UserId

SELECT ReviewId, ReviewDate, LEFT(Users, LEN(Users)-1)
FROM (
SELECT ReviewId, ReviewDate, 
(
    SELECT Reviewer + ', '
    FROM #ReviewInfo ri2
    WHERE ri2.ReviewId = ri1.ReviewId
    ORDER BY Reviewer
    FOR XML PATH('')
) AS Users
FROM #ReviewInfo ri1
GROUP BY ReviewId, ReviewDate
) a

DROP TABLE #ReviewInfo

2
投票
select 
      p1.Availability,
      COUNT(*),
      (
          select  name+',' 
          from AdventureWorks2008.Production.Location p2 
          where p1.Availability=p2.Availability 
          for XML path(''),type
      ).value('.','varchar(max)') as Name  
 from AdventureWorks2008.Production.Location p1 
 group by Availability

0
投票

当项目数量较小时,可以使用 ROW_NUMBER() OVER PARTITION BY 来完成:

declare @t table (col1 int, col2 varchar)
insert into @t VALUES (1,'A')
insert into @t VALUES (1,'B')
insert into @t VALUES (1,'C')
insert into @t VALUES (1,'D')
insert into @t VALUES (1,'E')
insert into @t VALUES (2,'X')
insert into @t VALUES (3,'Y')

select col1,
    MAX(CASE seq WHEN 1 THEN        col2 ELSE '' END ) + 
    MAX(CASE seq WHEN 2 THEN ', ' + col2 ELSE '' END ) + 
    MAX(CASE seq WHEN 3 THEN ', ' + col2 ELSE '' END ) +
    MAX(CASE seq WHEN 4 THEN ', ' + col2 ELSE '' END ) +
    MAX(CASE seq WHEN 5 THEN ',...' ELSE '' END ) 
    as col2
from (
    select col1, col2, ROW_NUMBER() OVER ( PARTITION BY col1 ORDER BY col2 ) seq
    from @t
    group by col1, col2
) x
group by col1

0
投票
STRING_AGG ( expression, separator ) [ <order_clause> ]

<order_clause> ::=   
    WITHIN GROUP ( ORDER BY <order_by_expression_list> [ ASC | DESC ] )

我来到 Stackoverflow 寻找 SQL Server 字符串聚合函数。

相关问题已被关闭,标记为该问题的重复项,因此我被迫在这里回答或根本不回答。

有关详细信息,请参阅 https://learn.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017

© www.soinside.com 2019 - 2024. All rights reserved.