AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / dba / 问题 / 17921
Accepted
Ben Brocka
Ben Brocka
Asked: 2012-05-16 13:58:42 +0800 CST2012-05-16 13:58:42 +0800 CST 2012-05-16 13:58:42 +0800 CST

将多行中的列合并为单行

  • 772

customer_comments由于数据库设计,我将一些拆分为多行,对于报告,我需要将comments每个唯一的行合并id为一行。我之前尝试过使用SELECT 子句和 COALESCE 技巧中的这个分隔列表,但我不记得它并且一定没有保存它。在这种情况下,我似乎也无法让它工作,似乎只能在一行上工作。

数据如下所示:

id  row_num  customer_code comments
-----------------------------------
1   1        Dilbert        Hard
1   2        Dilbert        Worker
2   1        Wally          Lazy

我的结果需要如下所示:

id  customer_code comments
------------------------------
1   Dilbert        Hard Worker
2   Wally          Lazy

所以对于每个row_num实际上只有一行结果;注释应按row_num. 上面的链接SELECT技巧可以将特定查询的所有值作为一行获取,但我不知道如何使它作为SELECT吐出所有这些行的语句的一部分工作。

我的查询必须自己遍历整个表并输出这些行。我没有将它们组合成多列,每行一个,所以PIVOT似乎不适用。

sql-server sql-server-2005
  • 4 4 个回答
  • 170549 Views

4 个回答

  • Voted
  1. Best Answer
    Aaron Bertrand
    2012-05-16T14:02:47+08:002012-05-16T14:02:47+08:00

    这对于相关子查询来说是相对微不足道的。您不能使用您提到的博客文章中突出显示的 COALESCE 方法,除非您将其提取到用户定义的函数中(或者除非您一次只想返回一行)。以下是我通常这样做的方式:

    DECLARE @x TABLE 
    (
      id INT, 
      row_num INT, 
      customer_code VARCHAR(32), 
      comments VARCHAR(32)
    );
    
    INSERT @x SELECT 1,1,'Dilbert','Hard'
    UNION ALL SELECT 1,2,'Dilbert','Worker'
    UNION ALL SELECT 2,1,'Wally','Lazy';
    
    SELECT id, customer_code, comments = STUFF((SELECT ' ' + comments 
        FROM @x AS x2 WHERE id = x.id
         ORDER BY row_num
         FOR XML PATH('')), 1, 1, '')
    FROM @x AS x
    GROUP BY id, customer_code
    ORDER BY id;
    

    如果您遇到注释中的数据可能包含不安全的 XML 字符 ( >, <, &) 的情况,您应该更改此:

         FOR XML PATH('')), 1, 1, '')
    

    对于这种更精细的方法:

         FOR XML PATH(''), TYPE).value(N'(./text())[1]', N'varchar(max)'), 1, 1, '')
    

    (确保使用正确的目标数据类型,varchar或nvarchar,和正确的长度,并在所有字符串文字前加上Nif using nvarchar。)

    • 18
  2. Jon Seigel
    2012-05-18T09:49:23+08:002012-05-18T09:49:23+08:00

    如果您被允许在您的环境中使用 CLR,这是为用户定义的聚合量身定制的案例。

    特别是,如果源数据非常大和/或您需要在应用程序中做很多此类事情,这可能是要走的路。我强烈怀疑Aaron 解决方案的查询计划不会随着输入大小的增长而很好地扩展。(我尝试向临时表添加索引,但这没有帮助。)

    与许多其他事情一样,此解决方案是一种权衡:

    • 甚至在您或您客户的环境中使用 CLR 集成的政治/政策。
    • CLR 函数可能更快,并且在给定一组真实数据的情况下会更好地扩展。
    • CLR 函数将可在其他查询中重用,并且您不必在每次需要执行此类操作时都复制(和调试)复杂的子查询。
    • 直接的 T-SQL 比编写和管理一段外部代码更简单。
    • 也许你不知道如何用 C# 或 VB 编程。
    • 等等

    编辑:好吧,我去尝试看看这是否真的更好,事实证明,使用聚合函数目前无法满足评论按特定顺序的要求。:(

    请参阅SqlUserDefinedAggregateAttribute.IsInvariantToOrder。基本上,您需要做的是OVER(PARTITION BY customer_code ORDER BY row_num)聚合时子句ORDER BY不支持。OVER我假设将此功能添加到 SQL Server 会引发大量蠕虫,因为在执行计划中需要更改的内容是微不足道的。前面提到的链接说这是为将来使用而保留的,所以这可以在将来实现(不过,在 2005 年你可能不走运)。

    这仍然可以通过将row_num值打包并解析到聚合字符串中来完成,然后在 CLR 对象中进行排序……这看起来很不自然。

    无论如何,下面是我使用的代码,以防其他人发现这很有用,即使有限制。我将把黑客部分作为练习留给读者。请注意,我使用 AdventureWorks (2005) 作为测试数据。

    聚合组装:

    using System;
    using System.IO;
    using System.Data.SqlTypes;
    using Microsoft.SqlServer.Server;
    
    namespace MyCompany.SqlServer
    {
        [Serializable]
        [SqlUserDefinedAggregate
        (
            Format.UserDefined,
            IsNullIfEmpty = false,
            IsInvariantToDuplicates = false,
            IsInvariantToNulls = true,
            IsInvariantToOrder = false,
            MaxByteSize = -1
        )]
        public class StringConcatAggregate : IBinarySerialize
        {
            private string _accum;
            private bool _isEmpty;
    
            public void Init()
            {
                _accum = string.Empty;
                _isEmpty = true;
            }
    
            public void Accumulate(SqlString value)
            {
                if (!value.IsNull)
                {
                    if (!_isEmpty)
                        _accum += ' ';
                    else
                        _isEmpty = false;
    
                    _accum += value.Value;
                }
            }
    
            public void Merge(StringConcatAggregate value)
            {
                Accumulate(value.Terminate());
            }
    
            public SqlString Terminate()
            {
                return new SqlString(_accum);
            }
    
            public void Read(BinaryReader r)
            {
                this.Init();
    
                _accum = r.ReadString();
                _isEmpty = _accum.Length == 0;
            }
    
            public void Write(BinaryWriter w)
            {
                w.Write(_accum);
            }
        }
    }
    

    用于测试的 T-SQL(省略了启用 CLR 和)CREATE ASSEMBLY:sp_configure

    CREATE TABLE [dbo].[Comments]
    (
        CustomerCode int NOT NULL,
        RowNum int NOT NULL,
        Comments nvarchar(25) NOT NULL
    )
    
    INSERT INTO [dbo].[Comments](CustomerCode, RowNum, Comments)
        SELECT
            DENSE_RANK() OVER(ORDER BY FirstName),
            ROW_NUMBER() OVER(PARTITION BY FirstName ORDER BY ContactID),
            Phone
            FROM [AdventureWorks].[Person].[Contact]
    GO
    
    CREATE AGGREGATE [dbo].[StringConcatAggregate]
    (
        @input nvarchar(MAX)
    )
    RETURNS nvarchar(MAX)
    EXTERNAL NAME StringConcatAggregate.[MyCompany.SqlServer.StringConcatAggregate]
    GO
    
    
    SELECT
        CustomerCode,
        [dbo].[StringConcatAggregate](Comments) AS AllComments
        FROM [dbo].[Comments]
        GROUP BY CustomerCode
    
    • 6
  3. Jon Seigel
    2012-06-03T10:29:53+08:002012-06-03T10:29:53+08:00

    这是一个基于游标的解决方案,可以保证评论的顺序row_num。(有关表格的填充方式,请参阅我的其他答案。)[dbo].[Comments]

    SET NOCOUNT ON
    
    DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
        SELECT
            CustomerCode,
            Comments
            FROM [dbo].[Comments]
            ORDER BY
                CustomerCode,
                RowNum
    
    DECLARE @curCustomerCode int
    DECLARE @lastCustomerCode int
    DECLARE @curComment nvarchar(25)
    DECLARE @comments nvarchar(MAX)
    
    DECLARE @results table
    (
        CustomerCode int NOT NULL,
        AllComments nvarchar(MAX) NOT NULL
    )
    
    
    OPEN cur
    
    FETCH NEXT FROM cur INTO
        @curCustomerCode, @curComment
    
    SET @lastCustomerCode = @curCustomerCode
    
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
    
        IF (@lastCustomerCode != @curCustomerCode)
        BEGIN
            INSERT INTO @results(CustomerCode, AllComments)
                VALUES(@lastCustomerCode, @comments)
    
            SET @lastCustomerCode = @curCustomerCode
            SET @comments = NULL
        END
    
        IF (@comments IS NULL)
            SET @comments = @curComment
        ELSE
            SET @comments = @comments + N' ' + @curComment
    
        FETCH NEXT FROM cur INTO
            @curCustomerCode, @curComment
    
    END
    
    IF (@comments IS NOT NULL)
    BEGIN
        INSERT INTO @results(CustomerCode, AllComments)
            VALUES(@curCustomerCode, @comments)
    END
    
    CLOSE cur
    DEALLOCATE cur
    
    
    SELECT * FROM @results
    
    • 1
  4. Gary
    2013-10-25T11:40:04+08:002013-10-25T11:40:04+08:00
    -- solution avoiding the cursor ...
    
    DECLARE @idMax INT
    DECLARE @idCtr INT
    DECLARE @comment VARCHAR(150)
    
    SELECT @idMax = MAX(id)
    FROM [dbo].[CustomerCodeWithSeparateComments]
    
    IF @idMax = 0
        return
    DECLARE @OriginalTable AS Table
    (
        [id] [int] NOT NULL,
        [row_num] [int] NULL,
        [customer_code] [varchar](50) NULL,
        [comment] [varchar](120) NULL
    )
    
    DECLARE @FinalTable AS Table
    (
        [id] [int] IDENTITY(1,1) NOT NULL,
        [customer_code] [varchar](50) NULL,
        [comment] [varchar](120) NULL
    )
    
    INSERT INTO @FinalTable 
    ([customer_code])
    SELECT [customer_code]
    FROM [dbo].[CustomerCodeWithSeparateComments]
    GROUP BY [customer_code]
    
    INSERT INTO @OriginalTable
               ([id]
               ,[row_num]
               ,[customer_code]
               ,[comment])
    SELECT [id]
          ,[row_num]
          ,[customer_code]
          ,[comment]
    FROM [dbo].[CustomerCodeWithSeparateComments]
    ORDER BY id, row_num
    
    SET @idCtr = 1
    SET @comment = ''
    
    WHILE @idCtr < @idMax
    BEGIN
    
        SELECT @comment = @comment + ' ' + comment
        FROM @OriginalTable 
        WHERE id = @idCtr
        UPDATE @FinalTable
           SET [comment] = @comment
        WHERE [id] = @idCtr 
        SET @idCtr = @idCtr + 1
        SET @comment = ''
    
    END 
    
    SELECT @comment = @comment + ' ' + comment
            FROM @OriginalTable 
            WHERE id = @idCtr
    
    UPDATE @FinalTable
       SET [comment] = @comment
    WHERE [id] = @idCtr
    
    SELECT *
    FROM @FinalTable
    
    • 0

相关问题

  • 我需要为每种类型的查询使用单独的索引,还是一个多列索引可以工作?

  • 什么时候应该使用唯一约束而不是唯一索引?

  • 死锁的主要原因是什么,可以预防吗?

  • 如何确定是否需要或需要索引

  • 从 SQL Server 2008 降级到 2005

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    如何查看 Oracle 中的数据库列表?

    • 8 个回答
  • Marko Smith

    mysql innodb_buffer_pool_size 应该有多大?

    • 4 个回答
  • Marko Smith

    列出指定表的所有列

    • 5 个回答
  • Marko Smith

    从 .frm 和 .ibd 文件恢复表?

    • 10 个回答
  • Marko Smith

    如何在不修改我自己的 tnsnames.ora 的情况下使用 sqlplus 连接到位于另一台主机上的 Oracle 数据库

    • 4 个回答
  • Marko Smith

    你如何mysqldump特定的表?

    • 4 个回答
  • Marko Smith

    如何选择每组的第一行?

    • 6 个回答
  • Marko Smith

    使用 psql 列出数据库权限

    • 10 个回答
  • Marko Smith

    如何从 PostgreSQL 中的选择查询中将值插入表中?

    • 4 个回答
  • Marko Smith

    如何使用 psql 列出所有数据库和表?

    • 7 个回答
  • Martin Hope
    Mike Walsh 为什么事务日志不断增长或空间不足? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland 列出指定表的所有列 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney MySQL 能否合理地对数十亿行执行查询? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx 如何监控大型 .sql 文件的导入进度? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison 你如何mysqldump特定的表? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    pedrosanta 使用 psql 列出数据库权限 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 对 SQL 查询进行计时? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas 如何从 PostgreSQL 中的选择查询中将值插入表中? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas 如何使用 psql 列出所有数据库和表? 2011-02-18 00:45:49 +0800 CST
  • Martin Hope
    bernd_k 什么时候应该使用唯一约束而不是唯一索引? 2011-01-05 02:32:27 +0800 CST

热门标签

sql-server mysql postgresql sql-server-2014 sql-server-2016 oracle sql-server-2008 database-design query-performance sql-server-2017

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve