【问题标题】:SQL Server: combining multiple rows into one rowSQL Server:将多行合并为一行
【发布时间】:2011-12-21 19:03:42
【问题描述】:

我有一个这样的 SQL 查询;

SELECT * 
FROM Jira.customfieldvalue
WHERE CUSTOMFIELD = 12534
AND ISSUE = 19602

结果就是这样;

我想要的是;显示在一行(单元格)中,将所有 STRINGVALUE 组合在一起,并用逗号分隔。像这样;

SELECT --some process with STRINGVALUE--
FROM Jira.customfieldvalue
WHERE CUSTOMFIELD = 12534
AND ISSUE = 19602

Araç Listesi (C2, K1 vb.Belgeler; yoksa Ruhsat Fotokopileri), Min. 5
araç plakası için İnternet Sorgusu, Son 3 Yıla Ait Onaylı Yıl Sonu
Bilanço + Gelir Tablosu, Son Yıl (Yıl Sonuna ait) Detay Mizanı, İçinde
Bulunduğumuz Yıla ait Ara Dönem Geçici Vergi Beyannamesi, Bayi Yorum
E-Maili, Proforma Fatura

我该怎么做?

【问题讨论】:

  • 又名如何违反 1NF,即这在 SQL(一种气味)中很难通过设计实现,因为它涉及生成非关系结果。而是在报告工具、前端代码等中执行此操作。

标签: sql-server sql-server-2008 tsql


【解决方案1】:

这是一个老问题,但在 Microsoft SQL Server 2017 发布后,您现在可以使用 STRING_AGG() 函数,这很像 MySQL 中的 GROUP_CONCAT 函数。

STRING_AGG (Transact-SQL) Documentation

示例

USE AdventureWorks2016
GO
SELECT STRING_AGG (CONVERT(NVARCHAR(max),FirstName), ',') AS csv 
FROM Person.Person; 

返回

Syed,Catherine,Kim,Kim,Kim,Hazem

【讨论】:

    【解决方案2】:

    declare @maxColumnCount int=0;
     declare @Query varchar(max)='';
     declare @DynamicColumnName nvarchar(MAX)='';
    
    -- table type variable that store all values of column row no
     DECLARE @TotalRows TABLE( row_count int)
     INSERT INTO @TotalRows (row_count)
     SELECT (ROW_NUMBER() OVER(PARTITION BY InvoiceNo order by InvoiceNo Desc)) as row_no FROM tblExportPartProforma
    
    -- Get the MAX value from @TotalRows table
     set @maxColumnCount= (select max(row_count) from @TotalRows)
     
    -- loop to create Dynamic max/case and store it into local variable 
     DECLARE @cnt INT = 1;
     WHILE @cnt <= @maxColumnCount
     BEGIN
       set @DynamicColumnName= @DynamicColumnName + ', Max(case when row_no= '+cast(@cnt as varchar)+' then InvoiceType end )as InvoiceType'+cast(@cnt as varchar)+''
          set @DynamicColumnName= @DynamicColumnName + ', Max(case when row_no= '+cast(@cnt as varchar)+' then BankRefno end )as BankRefno'+cast(@cnt as varchar)+''
                set @DynamicColumnName= @DynamicColumnName + ', Max(case when row_no= '+cast(@cnt as varchar)+' then AmountReceived end )as AmountReceived'+cast(@cnt as varchar)+''
    
          set @DynamicColumnName= @DynamicColumnName + ', Max(case when row_no= '+cast(@cnt as varchar)+' then AmountReceivedDate end )as AmountReceivedDate'+cast(@cnt as varchar)+''
    
    
       SET @cnt = @cnt + 1;
    END;
    
    -- Create dynamic CTE and store it into local variable @query 
      set @Query='
         with CTE_tbl as
         (
           SELECT InvoiceNo,InvoiceType,BankRefno,AmountReceived,AmountReceivedDate,
           ROW_NUMBER() OVER(PARTITION BY InvoiceNo order by InvoiceNo Desc) as row_no
           FROM tblExportPartProforma
          )
      select
         InvoiceNo
         '+@DynamicColumnName+'
         FROM CTE_tbl
         group By InvoiceNo'
    
    -- Execute the Query
     execute (@Query)

    【讨论】:

      【解决方案3】:
      CREATE VIEW  [dbo].[ret_vwSalariedForReport]
      AS
           WITH temp1 AS (SELECT
           salaried.*,
           operationalUnits.Title as OperationalUnitTitle
      FROM
          ret_vwSalaried salaried LEFT JOIN
          prs_operationalUnitFeatures operationalUnitFeatures on salaried.[Guid] = operationalUnitFeatures.[FeatureGuid] LEFT JOIN 
          prs_operationalUnits operationalUnits ON operationalUnits.id = operationalUnitFeatures.OperationalUnitID 
          ), 
      temp2 AS (SELECT
          t2.*,
          STUFF ((SELECT ' - ' + t1.OperationalUnitTitle
              FROM
                  temp1 t1 
              WHERE t1.[ID] = t2.[ID]  
              For XML PATH('')), 2, 2, '') OperationalUnitTitles from temp1 t2) 
      SELECT 
          [Guid],
          ID,
          Title,
          PersonnelNo,
          FirstName,
          LastName,
          FullName,
          Active,
          SSN,
          DeathDate,
          SalariedType,
          OperationalUnitTitles
      FROM 
          temp2
      GROUP BY 
          [Guid],
          ID,
          Title,
          PersonnelNo,
          FirstName,
          LastName,
          FullName,
          Active,
          SSN,
          DeathDate,
          SalariedType,
          OperationalUnitTitles
      

      【讨论】:

      • 我认为使用 STUFF 和 FOR XML 是解决问题的更好方法
      【解决方案4】:

      我相信对于支持listagg功能的数据库,你可以这样做:

      select id, issue, customfield, parentkey, listagg(stingvalue, ',') within group (order by id)
      from jira.customfieldvalue
      where customfield = 12534 and issue = 19602
      group by id, issue, customfield, parentkey
      

      【讨论】:

        【解决方案5】:

        使用 MySQL 内置函数 group_concat() 将是获得所需结果的不错选择。语法将是 -

        SELECT group_concat(STRINGVALUE) 
        FROM Jira.customfieldvalue
        WHERE CUSTOMFIELD = 12534
        AND ISSUE = 19602
        

        在执行上述命令之前,请确保增加 group_concat_max_len 的大小,否则整个输出可能不适合该单元格。

        要设置 group_concat_max_len 的值,请执行以下命令-

        SET group_concat_max_len = 50000;
        

        您可以相应地更改值 50000,根据需要将其增加到更高的值。

        【讨论】:

        【解决方案6】:

        有几种方法。

        如果您只想返回合并后的字符串值,这是一种快速简便的方法

        DECLARE @combinedString VARCHAR(MAX)
        SELECT @combinedString = COALESCE(@combinedString + ', ', '') + stringvalue
        FROM jira.customfieldValue
        WHERE customfield = 12534
            AND ISSUE = 19602
        
        SELECT @combinedString as StringValue 
        

        这将返回您的组合字符串。

        您也可以尝试其中一种 XML 方法,例如

        SELECT DISTINCT Issue, Customfield, StringValues
        FROM Jira.customfieldvalue v1
        CROSS APPLY ( SELECT StringValues + ',' 
                      FROM jira.customfieldvalue v2
                      WHERE v2.Customfield = v1.Customfield 
                          AND v2.Issue = v1.issue 
                      ORDER BY ID 
                          FOR XML PATH('') )  D ( StringValues )
        WHERE customfield = 12534
            AND ISSUE = 19602
        

        【讨论】:

        • 这里有很好的教程mssqltips.com/sqlservertip/2914/…
        • 如果您要从 PHP 运行此查询,请务必将 @combinedstring 声明为固定长度的 varchar:DECLARE @combinedString VARCHAR(255)
        【解决方案7】:

        在 MySql 中有一个方便的方法,叫做 GROUP_CONCAT。不存在 SQL Server 的等效项,但您可以使用 SQLCLR 编写自己的。幸运的是,someone 已经为你做到了。

        然后您的查询变成这样(顺便说一句,这是一个更好的语法):

        SELECT CUSTOMFIELD, ISSUE, dbo.GROUP_CONCAT(STRINGVALUE)
        FROM Jira.customfieldvalue
        WHERE CUSTOMFIELD = 12534 AND ISSUE = 19602
        GROUP BY CUSTOMFIELD, ISSUE
        

        但请注意,此方法适用于组内最多 100 行。除此之外,您将遇到主要的性能问题。 SQLCLR 聚合必须对任何中间结果进行序列化,这很快就会堆积很多工作。请记住这一点!

        有趣的是,FOR XML 并没有遇到同样的问题,而是使用了可怕的语法。

        【讨论】:

        • 我有一种情况,它最多只能将两到三行分组,所以它的简单性对我来说是完美的。看到这个答案已经有几年了,这种方法的性能有提高吗?
        • 我知道这是在 2011 年发布的,但是从 SQL 2017 开始,您现在可以使用 STRING_AGG() 函数,就像 MySQL GROUP_CONCAT() 一样,您想更新您的答案。 ;)
        【解决方案8】:

        你可以通过如下方式组合 For XML Path 和 STUFF 来实现:

        SELECT (STUFF((
                SELECT ', ' + StringValue
                FROM Jira.customfieldvalue
                WHERE CUSTOMFIELD = 12534
                AND ISSUE = 19602
                FOR XML PATH('')
                ), 1, 2, '')
            ) AS StringValue
        

        【讨论】:

          猜你喜欢
          • 2012-05-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-04-22
          • 2017-09-24
          • 2016-12-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多