AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / dba / Perguntas / 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

Combine a coluna de várias linhas em uma única linha

  • 772

Eu tenho alguns customer_commentsdivididos em várias linhas devido ao design do banco de dados e, para um relatório, preciso combinar o commentsde cada exclusivo idem uma linha. Eu tentei anteriormente algo trabalhando com esta lista delimitada da cláusula SELECT e do truque COALESCE, mas não consigo recuperá-lo e não devo tê-lo salvo. Também não consigo fazê-lo funcionar neste caso, parece funcionar apenas em uma única linha.

Os dados ficam assim:

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

Meus resultados precisam ficar assim:

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

Então, para cada um row_numhá realmente apenas uma linha de resultados; os comentários devem ser combinados na ordem de row_num. O truque vinculado acima SELECTfunciona para obter todos os valores de uma consulta específica como uma linha, mas não consigo descobrir como fazê-lo funcionar como parte de uma SELECTinstrução que exibe todas essas linhas.

Minha consulta precisa percorrer toda a tabela por conta própria e gerar essas linhas. Não estou combinando-os em várias colunas, uma para cada linha, portanto PIVOT, não parece aplicável.

sql-server sql-server-2005
  • 4 4 respostas
  • 170549 Views

4 respostas

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

    Isso é relativamente trivial para fazer com uma subconsulta correlacionada. Você não pode usar o método COALESCE destacado na postagem do blog que você mencionou, a menos que você extraia isso para uma função definida pelo usuário (ou a menos que você queira retornar apenas uma linha por vez). Aqui está como eu normalmente faço isso:

    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;
    

    Se você tiver um caso em que os dados nos comentários possam conter caracteres não seguros para XML ( >, <, &), altere isso:

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

    Para esta abordagem mais elaborada:

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

    (Certifique-se de usar o tipo de dados de destino correto, varcharou nvarchar, e o comprimento correto, e prefixe todos os literais de string Nse estiver usando nvarchar.)

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

    Se você tiver permissão para usar o CLR em seu ambiente, esse é um caso sob medida para um agregado definido pelo usuário.

    Em particular, este é provavelmente o caminho a seguir se os dados de origem não forem muito grandes e/ou você precisar fazer muito esse tipo de coisa em seu aplicativo. Suspeito fortemente que o plano de consulta para a solução de Aaron não será dimensionado bem à medida que o tamanho da entrada aumentar. (Tentei adicionar um índice à tabela temporária, mas isso não ajudou.)

    Esta solução, como muitas outras coisas, é uma troca:

    • Política/política para até mesmo usar a Integração CLR em seu ambiente ou no ambiente de seu cliente.
    • A função CLR é provavelmente mais rápida e dimensionará melhor com um conjunto real de dados.
    • A função CLR será reutilizável em outras consultas e você não precisará duplicar (e depurar) uma subconsulta complexa toda vez que precisar fazer esse tipo de coisa.
    • Straight T-SQL é mais simples do que escrever e gerenciar um pedaço de código externo.
    • Talvez você não saiba programar em C# ou VB.
    • etc.

    EDIT: Bem, eu fui tentar ver se isso realmente era melhor, e acontece que o requisito de que os comentários estejam em uma ordem específica atualmente não é possível satisfazer usando uma função agregada. :(

    Consulte SqlUserDefinedAggregateAttribute.IsInvariantToOrder . Basicamente, o que você precisa fazer é, OVER(PARTITION BY customer_code ORDER BY row_num)mas ORDER BYnão é suportado na OVERcláusula ao agregar. Estou assumindo que adicionar essa funcionalidade ao SQL Server abre uma lata de worms, porque o que precisaria ser alterado no plano de execução é trivial. O link mencionado acima diz que isso está reservado para uso futuro, então isso pode ser implementado no futuro (em 2005 você provavelmente está sem sorte).

    Isso ainda pode ser feito empacotando e analisando o row_numvalor na string agregada e, em seguida, fazendo a classificação dentro do objeto CLR ... o que parece bastante hackish.

    De qualquer forma, abaixo está o código que usei no caso de alguém achar isso útil mesmo com a limitação. Vou deixar a parte de hacking como exercício para o leitor. Observe que usei o AdventureWorks (2005) para dados de teste.

    Montagem agregada:

    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 para teste ( CREATE ASSEMBLY, e sp_configurepara habilitar o CLR omitido):

    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

    Aqui está uma solução baseada em cursor que garante a ordem dos comentários por row_num. (Veja minha outra resposta para saber como a [dbo].[Comments]tabela foi preenchida.)

    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

relate perguntas

  • Preciso de índices separados para cada tipo de consulta ou um índice de várias colunas funcionará?

  • Quando devo usar uma restrição exclusiva em vez de um índice exclusivo?

  • Quais são as principais causas de deadlocks e podem ser evitadas?

  • Como determinar se um Índice é necessário ou necessário

  • Downgrade do SQL Server 2008 para 2005

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Como ver a lista de bancos de dados no Oracle?

    • 8 respostas
  • Marko Smith

    Quão grande deve ser o mysql innodb_buffer_pool_size?

    • 4 respostas
  • Marko Smith

    Listar todas as colunas de uma tabela especificada

    • 5 respostas
  • Marko Smith

    restaurar a tabela do arquivo .frm e .ibd?

    • 10 respostas
  • Marko Smith

    Como usar o sqlplus para se conectar a um banco de dados Oracle localizado em outro host sem modificar meu próprio tnsnames.ora

    • 4 respostas
  • Marko Smith

    Como você mysqldump tabela (s) específica (s)?

    • 4 respostas
  • Marko Smith

    Como selecionar a primeira linha de cada grupo?

    • 6 respostas
  • Marko Smith

    Listar os privilégios do banco de dados usando o psql

    • 10 respostas
  • Marko Smith

    Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL?

    • 4 respostas
  • Marko Smith

    Como faço para listar todos os bancos de dados e tabelas usando o psql?

    • 7 respostas
  • Martin Hope
    Mike Walsh Por que o log de transações continua crescendo ou fica sem espaço? 2012-12-05 18:11:22 +0800 CST
  • Martin Hope
    Stephane Rolland Listar todas as colunas de uma tabela especificada 2012-08-14 04:44:44 +0800 CST
  • Martin Hope
    haxney O MySQL pode realizar consultas razoavelmente em bilhões de linhas? 2012-07-03 11:36:13 +0800 CST
  • Martin Hope
    qazwsx Como posso monitorar o andamento de uma importação de um arquivo .sql grande? 2012-05-03 08:54:41 +0800 CST
  • Martin Hope
    markdorison Como você mysqldump tabela (s) específica (s)? 2011-12-17 12:39:37 +0800 CST
  • Martin Hope
    pedrosanta Listar os privilégios do banco de dados usando o psql 2011-08-04 11:01:21 +0800 CST
  • Martin Hope
    Jonas Como posso cronometrar consultas SQL usando psql? 2011-06-04 02:22:54 +0800 CST
  • Martin Hope
    Jonas Como inserir valores em uma tabela de uma consulta de seleção no PostgreSQL? 2011-05-28 00:33:05 +0800 CST
  • Martin Hope
    Jonas Como faço para listar todos os bancos de dados e tabelas usando o psql? 2011-02-18 00:45:49 +0800 CST
  • Martin Hope
    bernd_k Quando devo usar uma restrição exclusiva em vez de um índice exclusivo? 2011-01-05 02:32:27 +0800 CST

Hot tag

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

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve