【问题标题】:Concatenate row values T-SQL连接行值 T-SQL
【发布时间】:2010-12-24 21:38:54
【问题描述】:

我正在尝试为报告汇总一些数据,并且需要连接其中一个表的行值。这是基本的表结构:

评论

 ReviewID  
 ReviewDate  

评论者

 ReviewerID  
 ReviewID  
 UserID  

用户

UserID  
FName  
LName  

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

基本上,我只想查看 Review.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 tsql concatenation


【解决方案1】:

看看这个

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

【讨论】:

  • 这不能正确处理 XML 特殊字符,例如 >&。因此,如果数据包含Frank & Bill,您将在结果集中得到Frank & Bill。有一种很好的方法来处理这个问题,请参阅:stackoverflow.com/questions/5031204/…
【解决方案2】:

原来有一种更简单的方法可以做到这一点,不需要 UDF:

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

【讨论】:

  • 这是我见过的最干净的方法,不需要 UDF。就我而言,它非常快。谢谢!
  • 我一直在寻找这个纯金的 sn-p。谢谢。
【解决方案3】:

遇到类似问题,在玩代码 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 的形式返回结果

享受 ;)

【讨论】:

【解决方案4】:

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

【讨论】:

    【解决方案5】:

    正如您所描述的,我有 3 种处理汇总数据的方法,1. 使用游标,2. 使用 UDF 或 3. 使用自定义聚合(用 .NET CLR 编写)。
    光标和 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;
    

    并获得如上图所示的结果集

    【讨论】:

    • +1 .. 不幸的是,它需要弄乱 CLR 的东西。 (如果 UDF 可以成为自定义 AGGREGATE 函数的目标,那就太好了:-/)
    【解决方案6】:
    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,
    

    【讨论】:

      【解决方案7】:

      现在从 SQL Server 2017 开始,有一个名为 STRING_AGG 的新 T-SQL 函数:
      它是一个新的聚合函数,用于连接字符串表达式的值并在它们之间放置分隔符值。
      分隔符不加在字符串末尾。

      例子:

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

      结果集:

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

      【讨论】:

        【解决方案8】:

        UDF 是解决此问题的好方法。

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

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

        【讨论】:

        • 那个 UDF 会是什么样子?我认为这没有必要,真的。
        • @marc:是的。 UDF 只是解决这个问题的方法之一。我认为这是向 SQL n00b 呈现的一个很好的解决方案。
        • UDF 的问题是它要么需要使用动态 SQL,要么存在每个查询类型。SQL 服务器支持自定义 CLR AGGREGATE 函数。以使用额外的 CLR 程序集为代价。。跨度>
        【解决方案9】:

        试试这个:

         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
        

        【讨论】:

          【解决方案10】:
          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;
          

          【讨论】:

            【解决方案11】:

            似乎您需要 group_concat 的功能(来自 mysql)。此处已针对另一个测试数据集解决此问题:How to return multiple values in one column (T-SQL)?

            【讨论】:

              【解决方案12】:

              创建一个临时表来转储您的数据。然后使用 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
              

              【讨论】:

                【解决方案13】:
                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
                

                【讨论】:

                  【解决方案14】:

                  当项目数量较少时,可以使用 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
                  

                  【讨论】:

                    【解决方案15】:
                    STRING_AGG ( expression, separator ) [ <order_clause> ]
                    
                    <order_clause> ::=   
                        WITHIN GROUP ( ORDER BY <order_by_expression_list> [ ASC | DESC ] )
                    

                    我来到 Stackoverflow 寻找 SQL 服务器字符串聚合函数。

                    相关问题已关闭,标记为与此问题重复,因此我不得不在这里回答或根本不回答。

                    详情请见https://docs.microsoft.com/en-us/sql/t-sql/functions/string-agg-transact-sql?view=sql-server-2017

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2022-11-17
                      • 1970-01-01
                      • 2011-03-23
                      • 2011-07-15
                      相关资源
                      最近更新 更多