Como concatenar texto de várias linhas em uma única seqüência de texto no SQL server?

1913

Considere uma tabela de banco de dados contendo nomes, com três linhas:

Peter
Paul
Mary

Existe uma maneira fácil de transformar isso em uma única sequência de Peter, Paul, Mary?

JohnnyM
fonte
26
Para respostas específicas ao SQL Server, tente esta pergunta .
Matt Hamilton
17
Para o MySQL, veja GROUP_CONCAT de esta resposta
Pykler
26
Desejo que a próxima versão do SQL Server ofereça um novo recurso para resolver a concatinação de cadeias de linhas múltiplas com elegância, sem a bobagem de FOR XML PATH.
Pete Alvin
4
Não SQL, mas se isso é uma vez só de coisa, você pode colar a lista para esta ferramenta no navegador convert.town/column-to-comma-separated-list
Man Stack
3
No Oracle, você pode usar o LISTAGG (COLUMN_NAME) de 11g r2 antes de haver uma função não suportada chamada WM_CONCAT (COLUMN_NAME) que faz o mesmo.
Richard

Respostas:

1432

Se você estiver no SQL Server 2017 ou Azure, consulte Mathieu Renda reply .

Eu tive um problema semelhante ao tentar juntar duas tabelas com relacionamentos um para muitos. No SQL 2005, descobri que o XML PATHmétodo pode lidar com a concatenação das linhas com muita facilidade.

Se houver uma tabela chamada STUDENTS

SubjectID       StudentName
----------      -------------
1               Mary
1               John
1               Sam
2               Alaina
2               Edward

O resultado que eu esperava era:

SubjectID       StudentName
----------      -------------
1               Mary, John, Sam
2               Alaina, Edward

Eu usei o seguinte T-SQL:

SELECT Main.SubjectID,
       LEFT(Main.Students,Len(Main.Students)-1) As "Students"
FROM
    (
        SELECT DISTINCT ST2.SubjectID, 
            (
                SELECT ST1.StudentName + ',' AS [text()]
                FROM dbo.Students ST1
                WHERE ST1.SubjectID = ST2.SubjectID
                ORDER BY ST1.SubjectID
                FOR XML PATH ('')
            ) [Students]
        FROM dbo.Students ST2
    ) [Main]

Você pode fazer a mesma coisa de uma maneira mais compacta se puder concaturar as vírgulas no início e usar substringpara pular a primeira para não precisar fazer uma subconsulta:

SELECT DISTINCT ST2.SubjectID, 
    SUBSTRING(
        (
            SELECT ','+ST1.StudentName  AS [text()]
            FROM dbo.Students ST1
            WHERE ST1.SubjectID = ST2.SubjectID
            ORDER BY ST1.SubjectID
            FOR XML PATH ('')
        ), 2, 1000) [Students]
FROM dbo.Students ST2
StefanJCollier
fonte
13
Ótima solução. A seguir, pode ser útil se você precisar manipular caracteres especiais como os do HTML: Rob Farley: Manipulando caracteres especiais com FOR XML PATH ('') .
10
Aparentemente, isso não funciona se os nomes contiverem caracteres XML como <ou &. Veja o comentário de @ BenHinman.
Sam
23
NB: Este método depende de comportamentos não documentados de FOR XML PATH (''). Isso significa que não deve ser considerado confiável, pois qualquer patch ou atualização pode alterar a forma como isso funciona. Basicamente, depende de um recurso obsoleto.
Bacon Bits
26
@ Whelkaholism A linha inferior é que FOR XMLse destina a gerar XML, não concatenar seqüências arbitrárias. É por isso que escapa &, <e >aos códigos de entidade XML ( &amp;, &lt;, &gt;). Presumo que também irá escapar "e 'para &quot;e &apos;em atributos também. É não GROUP_CONCAT() , string_agg(), array_agg(), listagg(), etc., mesmo se você pode tipo de fazê-lo fazer isso. Nós deve estar gastando nosso tempo exigindo Microsoft implementar uma função adequada.
Bacon Bits
13
Boas notícias: o MS SQL Server adicionará o string_aggv.Next. e tudo isso pode ir embora.
Jason C
1009

Esta resposta pode retornar resultados inesperados Para obter resultados consistentes, use um dos métodos FOR XML PATH detalhados em outras respostas.

Use COALESCE:

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + Name 
FROM People

Apenas uma explicação (já que esta resposta parece ter visualizações relativamente regulares):

  • Coalesce é realmente apenas uma trapaça útil que realiza duas coisas:

1) Não há necessidade de inicializar @Namescom um valor de sequência vazio.

2) Não é necessário retirar um separador extra no final.

  • A solução acima fornecerá resultados incorretos se uma linha tiver um valor NULL Name (se houver um NULL , o NULL criará @Names NULL após essa linha e a próxima linha começará novamente como uma string vazia novamente. soluções:
DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
WHERE Name IS NOT NULL

ou:

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(@Names + ', ', '') + 
    ISNULL(Name, 'N/A')
FROM People

Dependendo do comportamento desejado (a primeira opção filtra apenas NULL s, a segunda opção os mantém na lista com uma mensagem de marcador [substitua 'N / A' pelo que for apropriado para você)).

Chris Shaffer
fonte
72
Para ser claro, a coalescência não tem nada a ver com a criação da lista, apenas garante que os valores NULL não sejam incluídos.
Graeme Perrow
17
@Graeme Perrow Não exclui valores NULL (um WHERE é necessário para isso - isso perderá resultados se um dos valores de entrada for NULL) e é necessário nessa abordagem porque: NULL + não NULL -> NULL e não NULL + NULL -> NULL; também @Name é NULL por padrão e, de fato, essa propriedade é usada como sentinela implícita aqui para determinar se um ',' deve ser adicionado ou não.
62
Observe que esse método de concatenação depende do SQL Server executando a consulta com um plano específico. Fui pego usando esse método (com a adição de um ORDER BY). Quando lidava com um pequeno número de linhas, funcionava bem, mas com mais dados, o SQL Server escolheu um plano diferente que resultou na seleção do primeiro item sem concatenação. Veja este artigo de Anith Sen.
fbarber
17
Este método não pode ser usado como uma subconsulta em uma lista de seleção ou cláusula where, porque usa uma variável tSQL. Nesses casos, você pode usar os métodos oferecidos por @Ritesh
R. Schreurs
11
Este não é um método confiável de concatenação. Não é suportado e não deve ser usado (por Microsoft, por exemplo , support.microsoft.com/en-us/kb/287515 , connect.microsoft.com/SQLServer/Feedback/Details/704389 ). Pode mudar sem aviso prévio. Use a técnica PATH XML discutido em stackoverflow.com/questions/5031204/... eu escrevi mais aqui: marc.durdin.net/2015/07/...
Marc Durdin
454

SQL Server 2017 ou superior e SQL Azure: STRING_AGG

Começando com a próxima versão do SQL Server, podemos finalmente concatenar as linhas sem precisar recorrer a nenhuma variável ou bruxaria XML.

STRING_AGG (Transact-SQL)

Sem agrupamento

SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;

Com o agrupamento:

SELECT GroupName, STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department
GROUP BY GroupName;

Com agrupamento e sub-classificação

SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments
FROM HumanResources.Department 
GROUP BY GroupName;
Mathieu Renda
fonte
2
E, diferentemente das soluções CLR, você tem controle sobre a classificação.
canon
Parece haver uma limitação de exibição de 4000 caracteres em STRING_AGG
InspiradoDe
Existe uma maneira de fazer a classificação caso não exista GROUP BY (assim, no exemplo "Sem agrupar")?
RuudvK
Atualização: consegui fazer o seguinte, mas existe uma maneira mais limpa? SELECT STRING_AGG (Nome, ',') COMO Departamentos FROM (SELECT TOP 100000 Nome FROM HumanResources.Department ORDER BY Name) D;
RuudvK
362

Um método ainda não mostrado por meio do XML data()comando no MS SQL Server é:

Suponha que a tabela chamada NameList com uma coluna chamada FName,

SELECT FName + ', ' AS 'data()' 
FROM NameList 
FOR XML PATH('')

retorna:

"Peter, Paul, Mary, "

Somente a vírgula extra deve ser tratada.

Editar: conforme adotado no comentário de @ NReilingh, você pode usar o seguinte método para remover a vírgula à direita. Supondo os mesmos nomes de tabela e coluna:

STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(FName)) AS 'data()' FROM NameList
FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands
jens frandsen
fonte
15
santo s ** isso é incrível! Quando executado por conta própria, como no exemplo, o resultado é formatado como um hiperlink, que quando clicado (no SSMS) abre uma nova janela contendo os dados, mas quando usado como parte de uma consulta maior, ele aparece apenas como uma sequência. É uma string? ou é xml que eu preciso tratar de maneira diferente no aplicativo que usará esses dados?
Ben
10
Essa abordagem também escapa XML caracteres como <e>. Assim, a seleção '<b>' + FName + '</ b>' resulta em "& lt; b & gt; John & lt; / b & gt; & lt; b & gt; Paul ..."
Lukáš Lansky
8
Solução pura. Percebo que, mesmo quando não adiciono, + ', 'ele ainda adiciona um espaço único entre todos os elementos concatenados.
Baodad
8
@ Baodad Isso parece fazer parte do acordo. Você pode solucionar o problema substituindo um caractere de token adicionado. Por exemplo, este faz uma lista delimitada por vírgula perfeito para qualquer comprimento:SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
NReilingh
1
Uau, na verdade, nos meus testes usando data () e uma substituição, é MUITO mais eficiente do que não. Super esquisito.
NReilingh
306

No SQL Server 2005

SELECT Stuff(
  (SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
  .value('text()[1]','nvarchar(max)'),1,2,N'')

No SQL Server 2016

você pode usar o sintaxe FOR JSON

ie

SELECT per.ID,
Emails = JSON_VALUE(
   REPLACE(
     (SELECT _ = em.Email FROM Email em WHERE em.Person = per.ID FOR JSON PATH)
    ,'"},{"_":"',', '),'$[0]._'
) 
FROM Person per

E o resultado se tornará

Id  Emails
1   abc@gmail.com
2   NULL
3   def@gmail.com, xyz@gmail.com

Isso funcionará mesmo que seus dados contenham caracteres XML inválidos

o '"},{"_":"'é seguro porque se você contém dados'"},{"_":"', eles serão escapados para"},{\"_\":\"

Você pode substituir ', 'por qualquer separador de cadeia


E no SQL Server 2017, Banco de Dados SQL do Azure

Você pode usar a nova função STRING_AGG

Steven Chong
fonte
3
Bom uso da função STUFF para separar os dois caracteres iniciais.
David
3
Eu gosto mais desta solução, porque posso usá-la facilmente em uma lista de seleção adicionando 'como <rótulo>'. Não sei como fazer isso com a solução do @Ritesh.
R. Schreurs
13
Isto é melhor do que a resposta aceita porque esta opção também lida com personagens reserverd XML-escapar da ONU, como <, >, &, etc, que FOR XML PATH('')vai escapar automaticamente.
BateTech 07/04
Esta é uma resposta incrível como ele resolveu o problema e fornece as melhores maneiras de fazer as coisas em diferentes versões do SQL agora eu gostaria de poder usar 2.017 / Azure
Chris Ward
120

No MySQL, há uma função, GROUP_CONCAT () , que permite concatenar os valores de várias linhas. Exemplo:

SELECT 1 AS a, GROUP_CONCAT(name ORDER BY name ASC SEPARATOR ', ') AS people 
FROM users 
WHERE id IN (1,2,3) 
GROUP BY a
Darryl Hein
fonte
funciona bem. Mas quando eu uso, SEPARATOR '", "'vou sentir falta de alguns caracteres no final da última entrada. por que isso pode acontecer?
Gooleem #
@ gooleem Não sei ao certo o que você quer dizer, mas essa função coloca apenas o separador entre os itens, não depois. Se essa não for a resposta, recomendo postar uma nova pergunta.
Darryl Hein
@DarrylHein para minhas necessidades eu usei o separador como acima. Mas isso me corta alguns caracteres no final da saída. Isso é muito estranho e parece ser um bug. Eu não tenho uma solução, eu apenas solução alternativa.
Gooleem 6/12/16
Funciona basicamente. Duas coisas a considerar: 1) se a sua coluna não é a CHAR, você precisa convertê-la, por exemplo, via GROUP_CONCAT( CAST(id AS CHAR(8)) ORDER BY id ASC SEPARATOR ',')2) se você tiver muitos valores chegando, você deve aumentar o valor group_concat_max_lencomo está escrito em stackoverflow.com/a/1278210/1498405
hardmooth
58

Use COALESCE - Saiba mais aqui

Por exemplo:

102

103

104

Em seguida, escreva o código abaixo no servidor sql,

Declare @Numbers AS Nvarchar(MAX) -- It must not be MAX if you have few numbers 
SELECT  @Numbers = COALESCE(@Numbers + ',', '') + Number
FROM   TableName where Number IS NOT NULL

SELECT @Numbers

A saída seria:

102,103,104
pedram
fonte
2
Esta é realmente a melhor solução IMO, pois evita os problemas de codificação que FOR XML apresenta. Eu usei Declare @Numbers AS Nvarchar(MAX)e funcionou bem. Você pode explicar por que recomenda não usá-lo, por favor?
EvilDr
7
Esta solução já foi publicada há 8 anos! stackoverflow.com/a/194887/986862
Andre Figueiredo
Por que essa consulta retorna ??? símbolos em vez dos cirílicos? Isso é apenas um problema de saída?
Akmal Salikhov
48

As matrizes do Postgres são incríveis. Exemplo:

Crie alguns dados de teste:

postgres=# \c test
You are now connected to database "test" as user "hgimenez".
test=# create table names (name text);
CREATE TABLE                                      
test=# insert into names (name) values ('Peter'), ('Paul'), ('Mary');                                                          
INSERT 0 3
test=# select * from names;
 name  
-------
 Peter
 Paul
 Mary
(3 rows)

Agregue-os em uma matriz:

test=# select array_agg(name) from names;
 array_agg     
------------------- 
 {Peter,Paul,Mary}
(1 row)

Converta a matriz em uma cadeia de caracteres delimitada por vírgula:

test=# select array_to_string(array_agg(name), ', ') from names;
 array_to_string
-------------------
 Peter, Paul, Mary
(1 row)

FEITO

Desde o PostgreSQL 9.0, é ainda mais fácil .

hgmnz
fonte
Se você precisar de mais de uma coluna, por exemplo, a identificação de funcionário entre colchetes use o operador concat: select array_to_string(array_agg(name||'('||id||')'
Richard Fox
Não aplicável a sql-server , apenas para mysql
GoldBishop
46

O Oracle 11g Release 2 suporta a função LISTAGG. Documentação aqui .

COLUMN employees FORMAT A50

SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM   emp
GROUP BY deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

3 rows selected.

Atenção

Cuidado ao implementar esta função se houver possibilidade de a sequência resultante ter mais de 4000 caracteres. Isso lançará uma exceção. Se for esse o caso, você precisará manipular a exceção ou rolar sua própria função que evita que a sequência de caracteres junte mais de 4000 caracteres.

Alex
fonte
1
Para versões mais antigas do Oracle, o wm_concat é perfeito. Seu uso é explicado no link do presente de Alex. Obrigado Alex!
21715 Toscanelli
LISTAGGfunciona perfeito! Basta ler o documento vinculado aqui. wm_concatremovido da versão 12c em diante.
ASGs
34

No SQL Server 2005 e posterior, use a consulta abaixo para concatenar as linhas.

DECLARE @t table
(
    Id int,
    Name varchar(10)
)
INSERT INTO @t
SELECT 1,'a' UNION ALL
SELECT 1,'b' UNION ALL
SELECT 2,'c' UNION ALL
SELECT 2,'d' 

SELECT ID,
stuff(
(
    SELECT ','+ [Name] FROM @t WHERE Id = t.Id FOR XML PATH('')
),1,1,'') 
FROM (SELECT DISTINCT ID FROM @t ) t
Yogesh Bhadauirya
fonte
2
Acredito que isso falhe quando os valores contiverem símbolos XML como <ou &.
Sam
28

Como não tenho acesso a um SQL Server em casa, acho que tenho a sintaxe aqui, mas é mais ou menos:

DECLARE @names VARCHAR(500)

SELECT @names = @names + ' ' + Name
FROM Names
Dana
fonte
11
Você precisaria iniciar @names para algo não nulo, caso contrário, você obterá NULL o tempo todo; você também precisaria para lidar com o delimitador (incluindo o desnecessário)
Marc Gravell
3
o único problema com essa abordagem (que eu uso o tempo todo) é que você não pode incorporá-lo
ekkis
1
Para se livrar do espaço à esquerda alterar a consulta paraSELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names
Tian van Heerden
Além disso, você tem que verificar esse nome não é nulo, você pode fazê-lo fazendo:SELECT @names = @names + ISNULL(' ' + Name, '')
Vita1ij
28

Uma solução CTE recursiva foi sugerida, mas nenhum código foi fornecido. O código abaixo é um exemplo de uma CTE recursiva. Observe que, embora os resultados correspondam à pergunta, os dados não correspondem exatamente à descrição fornecida, pois presumo que você realmente queira fazer isso em grupos de linhas, nem todas as linhas da tabela. A alteração para corresponder a todas as linhas da tabela é deixada como um exercício para o leitor.

;WITH basetable AS (
    SELECT
        id,
        CAST(name AS VARCHAR(MAX)) name, 
        ROW_NUMBER() OVER (Partition BY id ORDER BY seq) rw, 
        COUNT(*) OVER (Partition BY id) recs 
    FROM (VALUES
        (1, 'Johnny', 1),
        (1, 'M', 2), 
        (2, 'Bill', 1),
        (2, 'S.', 4),
        (2, 'Preston', 5),
        (2, 'Esq.', 6),
        (3, 'Ted', 1),
        (3, 'Theodore', 2),
        (3, 'Logan', 3),
        (4, 'Peter', 1),
        (4, 'Paul', 2),
        (4, 'Mary', 3)
    ) g (id, name, seq)
),
rCTE AS (
    SELECT recs, id, name, rw
    FROM basetable
    WHERE rw = 1

    UNION ALL

    SELECT b.recs, r.ID, r.name +', '+ b.name name, r.rw + 1
    FROM basetable b
    INNER JOIN rCTE r ON b.id = r.id AND b.rw = r.rw + 1
)
SELECT name
FROM rCTE
WHERE recs = rw AND ID=4
jmoreno
fonte
1
Para os espantados: essa consulta insere 12 linhas (3 colunas) em uma tabela de base temporária, depois cria uma Expressão de Tabela Comum recursiva (rCTE) e depois alisa a name coluna em uma sequência separada por vírgula por 4 grupos de ids. À primeira vista, acho que isso é mais trabalhoso do que a maioria das outras soluções para o SQL Server.
knb
2
@ knb: não tenho certeza se isso é elogio, condenação ou apenas surpresa. A tabela base é porque eu gosto que meus exemplos funcionem de verdade, não tem nada a ver com a pergunta.
jmoreno
26

A partir do PostgreSQL 9.0, isso é bastante simples:

select string_agg(name, ',') 
from names;

Nas versões anteriores à 9.0, array_agg()pode ser usado como mostrado por hgmnz

um cavalo sem nome
fonte
Para fazer isso com colunas que não são do tipo texto, é necessário adicionar uma SELECT string_agg(non_text_type::text, ',') FROM table
conversão de
@TorbenKohlmeier: você só precisa disso para colunas sem caracteres (por exemplo, número inteiro, decimal). Funciona muito bem para varcharouchar
a_horse_with_no_name
26

Você precisa criar uma variável que mantenha seu resultado final e selecione-o, assim.

Solução mais fácil

DECLARE @char VARCHAR(MAX);

SELECT @char = COALESCE(@char + ', ' + [column], [column]) 
FROM [table];

PRINT @char;
Tigerjz32
fonte
18

O uso de XML me ajudou a obter linhas separadas por vírgulas. Para a vírgula extra, podemos usar a função de substituição do SQL Server. Em vez de adicionar uma vírgula, o uso do AS 'data ()' concatenará as linhas com espaços, que posteriormente poderão ser substituídos por vírgulas conforme a sintaxe escrita abaixo.

REPLACE(
        (select FName AS 'data()'  from NameList  for xml path(''))
         , ' ', ', ') 
Diwakar
fonte
2
Esta é a melhor resposta aqui na minha opinião. O uso da variável declare não é bom quando você precisa ingressar em outra tabela, e isso é bom e curto. Bom trabalho.
David Roussel
7
que não está funcionando bem, se os dados FName tem espaços já, por exemplo, "My Name"
binball
Realmente está funcionando para mim no ms-sql 2016 Selecione REPLACE ((selecione Name AS 'data ()' em Brand Where Id IN (1,2,3,4) para o caminho xml ('')), '', ' , ') como AllBrands
Rejwanul Reja
17

Uma solução pronta para uso, sem vírgulas extras:

select substring(
        (select ', '+Name AS 'data()' from Names for xml path(''))
       ,3, 255) as "MyList"

Uma lista vazia resultará em valor NULL. Geralmente, você inserirá a lista em uma coluna da tabela ou variável de programa: ajuste o comprimento máximo de 255 conforme sua necessidade.

(Diwakar e Jens Frandsen forneceram boas respostas, mas precisam de melhorias.)

Daniel Reis
fonte
Existe um espaço antes da vírgula ao usar isso :(
slayernoah
1
Substitua ', 'por ','se você não quiser o espaço extra.
Daniel Reis
13
SELECT STUFF((SELECT ', ' + name FROM [table] FOR XML PATH('')), 1, 2, '')

Aqui está uma amostra:

DECLARE @t TABLE (name VARCHAR(10))
INSERT INTO @t VALUES ('Peter'), ('Paul'), ('Mary')
SELECT STUFF((SELECT ', ' + name FROM @t FOR XML PATH('')), 1, 2, '')
--Peter, Paul, Mary
Max Szczurek
fonte
10
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)

Isso coloca a vírgula dispersa no início.

No entanto, se você precisar de outras colunas, ou para CSV uma tabela filha, precisará agrupá-la em um campo escalar definido pelo usuário (UDF).

Você também pode usar o caminho XML como uma subconsulta correlacionada na cláusula SELECT (mas precisaria esperar até voltar ao trabalho porque o Google não faz coisas em casa :-)

gbn
fonte
10

Com as outras respostas, a pessoa que está lendo a resposta deve estar ciente de uma tabela de domínio específica, como veículo ou aluno. A tabela deve ser criada e preenchida com dados para testar uma solução.

Abaixo está um exemplo que usa a tabela "Information_Schema.Columns" do SQL Server. Ao usar esta solução, nenhuma tabela precisa ser criada ou dados adicionados. Este exemplo cria uma lista separada por vírgula de nomes de colunas para todas as tabelas no banco de dados.

SELECT
    Table_Name
    ,STUFF((
        SELECT ',' + Column_Name
        FROM INFORMATION_SCHEMA.Columns Columns
        WHERE Tables.Table_Name = Columns.Table_Name
        ORDER BY Column_Name
        FOR XML PATH ('')), 1, 1, ''
    )Columns
FROM INFORMATION_SCHEMA.Columns Tables
GROUP BY TABLE_NAME 
Mike Barlow - BarDev
fonte
7

Para bancos de dados Oracle, consulte esta pergunta: Como várias linhas podem ser concatenadas em uma no Oracle sem criar um procedimento armazenado?

A melhor resposta parece ser de @Emmanuel, usando a função LISTAGG () interna, disponível no Oracle 11g Release 2 e posterior.

SELECT question_id,
   LISTAGG(element_id, ',') WITHIN GROUP (ORDER BY element_id)
FROM YOUR_TABLE;
GROUP BY question_id

como o @ user762952 apontou e de acordo com a documentação da Oracle http://www.oracle-base.com/articles/misc/string-aggregation-techniques.php , a função WM_CONCAT () também é uma opção. Parece estável, mas a Oracle recomenda explicitamente não usá-lo para qualquer aplicativo SQL, portanto, use por seu próprio risco.

Fora isso, você terá que escrever sua própria função; o documento Oracle acima tem um guia sobre como fazer isso.

ZeroK
fonte
7

Para evitar valores nulos, você pode usar CONCAT ()

DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name) 
FROM Names
select @names
Rapunzo
fonte
Seria bom saber por que o CONCAT funciona. Um link para o MSDN seria bom.
Reversed Engineer
7

Eu realmente gostei da elegância da resposta de Dana . Só queria torná-lo completo.

DECLARE @names VARCHAR(MAX)
SET @names = ''

SELECT @names = @names + ', ' + Name FROM Names 

-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
Oleg Sakharov
fonte
Se você estiver excluindo os dois últimos símbolos ',', será necessário adicionar ',' após o Nome ('SELECT \ @names = \ @names + Name +', 'FROM Names'). Dessa forma, os dois últimos caracteres sempre serão ','.
JT_
No meu caso eu precisava para se livrar do líder vírgula assim alterar a consulta para SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names, em seguida, você não tem de truncar-lo depois.
Tian van Heerden
6

Esta resposta exigirá algum privilégio no servidor para funcionar.

Assemblies são uma boa opção para você. Existem muitos sites que explicam como criá-lo. O que eu acho que é muito bem explicado é este um

Se você quiser, eu já criei o assembly e é possível baixar a DLL aqui .

Depois de baixá-lo, você precisará executar o seguinte script no seu SQL Server:

CREATE Assembly concat_assembly 
   AUTHORIZATION dbo 
   FROM '<PATH TO Concat.dll IN SERVER>' 
   WITH PERMISSION_SET = SAFE; 
GO 

CREATE AGGREGATE dbo.concat ( 

    @Value NVARCHAR(MAX) 
  , @Delimiter NVARCHAR(4000) 

) RETURNS NVARCHAR(MAX) 
EXTERNAL Name concat_assembly.[Concat.Concat]; 
GO  

sp_configure 'clr enabled', 1;
RECONFIGURE

Observe que o caminho para a montagem pode estar acessível ao servidor. Como você executou todas as etapas com êxito, você pode usar a função como:

SELECT dbo.Concat(field1, ',')
FROM Table1

Espero que ajude!!!

Nizam
fonte
1
O link da DLL é um erro 404. Usar uma montagem para isso é um exagero. Veja a melhor resposta para o SQL Server.
Protiguous
6

Exemplo completo do MySQL:

Temos usuários que podem ter muitos dados e queremos ter uma saída, onde podemos ver todos os dados dos usuários em uma lista:

Resultado:

___________________________
| id   |  rowList         |
|-------------------------|
| 0    | 6, 9             |
| 1    | 1,2,3,4,5,7,8,1  |
|_________________________|

Configuração da tabela:

CREATE TABLE `Data` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=latin1;


INSERT INTO `Data` (`id`, `user_id`) VALUES
(1, 1),
(2, 1),
(3, 1),
(4, 1),
(5, 1),
(6, 0),
(7, 1),
(8, 1),
(9, 0),
(10, 1);


CREATE TABLE `User` (
  `id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;


INSERT INTO `User` (`id`) VALUES
(0),
(1);

Inquerir:

SELECT User.id, GROUP_CONCAT(Data.id ORDER BY Data.id) AS rowList FROM User LEFT JOIN Data ON User.id = Data.user_id GROUP BY User.id
user1767754
fonte
5

Eu costumo usar select assim para concatenar seqüências de caracteres no SQL Server:

with lines as 
( 
  select 
    row_number() over(order by id) id, -- id is a line id
    line -- line of text.
  from
    source -- line source
), 
result_lines as 
( 
  select 
    id, 
    cast(line as nvarchar(max)) line 
  from 
    lines 
  where 
    id = 1 
  union all 
  select 
    l.id, 
    cast(r.line + N', ' + l.line as nvarchar(max))
  from 
    lines l 
    inner join 
    result_lines r 
    on 
      l.id = r.id + 1 
) 
select top 1 
  line
from
  result_lines
order by
  id desc
Vladimir Nesterovsky
fonte
5

Se você deseja lidar com nulos, pode fazê-lo adicionando uma cláusula where ou adicione outro COALESCE ao redor da primeira.

DECLARE @Names VARCHAR(8000) 
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
Pramod
fonte
5

Isso funcionou para mim ( SqlServer 2016 ):

SELECT CarNamesString = STUFF((
         SELECT ',' + [Name]
            FROM tbl_cars 
            FOR XML PATH('')
         ), 1, 1, '')

Aqui está a fonte: https://www.mytecbits.com/

E uma solução para o MySql (já que esta página aparece no Google para MySql)

SELECT [Name],
       GROUP_CONCAT(DISTINCT [Name]  SEPARATOR ',')
       FROM tbl_cars

De documentação do MySql

Arash.Zandi
fonte
4

No Oracle, é wm_concat. Acredito que esta função esteja disponível na versão 10g e superior.

user762952
fonte
4

Isso também pode ser útil

create table #test (id int,name varchar(10))
--use separate inserts on older versions of SQL Server
insert into #test values (1,'Peter'), (1,'Paul'), (1,'Mary'), (2,'Alex'), (3,'Jack')

DECLARE @t VARCHAR(255)
SELECT @t = ISNULL(@t + ',' + name, name) FROM #test WHERE id = 1
select @t
drop table #test

retorna

Peter,Paul,Mary
endo64
fonte
5
Infelizmente, esse comportamento parece não ser oficialmente suportado. O MSDN diz: "Se uma variável for referenciada em uma lista de seleção, ele deverá receber um valor escalar ou a instrução SELECT deverá retornar apenas uma linha". E há pessoas que observaram problemas: sqlmag.com/sql-server/multi-row-variable-assignment-and-order
blueling