Como concatenar texto de várias linhas em uma string único texto no servidor SQL?
-
10-07-2019 - |
Pergunta
Considere uma tabela de banco de dados com nomes, com três linhas:
Peter
Paul
Mary
Existe uma maneira fácil de transformar isso em uma única seqüência de Peter, Paul, Mary
?
Solução
Se você estiver em SQL Server 2017 ou Azure, consulte Mathieu Renda resposta .
Eu tive um problema semelhante quando eu estava tentando unir duas tabelas com relacionamentos um-para-muitos. Em SQL 2005 Achei que o método XML PATH
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
Resultado 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 forma mais compacta se você pode concat as vírgulas no início e no uso substring
para ignorar o primeiro, assim você não precisa fazer uma sub-consulta:
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
Outras dicas
Esta resposta pode retornar para obter resultados consistentes, use um dos pARA XML métodos PATH detalhados em outras respostas.
Use COALESCE
:
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(@Names + ', ', '') + Name
FROM People
Apenas alguma explicação (uma vez que esta resposta parece obter vistas relativamente regulares):
- Coalesce é realmente apenas uma fraude útil que realiza duas coisas:
1) Não há necessidade de @Names
initialize com um valor de cadeia vazia.
2) Não há necessidade de retirar um separador extra no final.
- A solução acima irá dar resultados incorretos se uma linha tem um NULL valor Nome (se houver um NULL , o NULL fará
@Names
NULL , depois dessa linha e a próxima linha vai começar de novo como uma cadeia vazia novamente facilmente corrigido com uma das duas 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 que o comportamento que você quer (a primeira opção apenas filtros NULL s para fora, a segunda opção mantém-los na lista com um marcador mensagem [substituir 'N / A' com o que é apropriado para você]).
Um método ainda não mostrado através do comando XML
data()
em MS SQL Server é:
Suponha tabela chamada NameList com uma coluna chamada FName,
SELECT FName + ', ' AS 'data()'
FROM NameList
FOR XML PATH('')
retornos:
"Peter, Paul, Mary, "
Apenas a vírgula extra deve ser tratada.
Editar: Como adotado a partir @ comentário de NReilingh, você pode usar o seguinte método para remover a vírgula no final. Assumindo que os mesmos nomes de tabelas e colunas:
STUFF(REPLACE((SELECT '#!' + LTRIM(RTRIM(FName)) AS 'data()' FROM NameList
FOR XML PATH('')),' #!',', '), 1, 2, '') as Brands
SQL Server 2017+ e SQL Azure: STRING_AGG
A partir da próxima versão do SQL Server, podemos finalmente concatenate em linhas sem ter que recorrer a qualquer variável ou XML witchery.
Sem agrupamento
SELECT STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department;
Com agrupamento:
SELECT GroupName, STRING_AGG(Name, ', ') AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
Com o agrupamento e sub-ordenação
SELECT GroupName, STRING_AGG(Name, ', ') WITHIN GROUP (ORDER BY Name ASC) AS Departments
FROM HumanResources.Department
GROUP BY GroupName;
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 PARA JSON sintaxe
i.
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
Isto irá funcionar até mesmo seus dados contém caracteres XML inválidos
o '"},{"_":"'
é seguro, porque se você dados contêm '"},{"_":"',
será escapou para "},{\"_\":\"
Você pode substituir ', '
com qualquer separador corda
e no SQL Server 2017, SQL Azure banco de dados
Você pode usar o novo STRING_AGG função
No MySQL existe uma função, GROUP_CONCAT () , que permite que você 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
Use COALESCE - Saiba mais a partir daqui
Para um exemplo:
102
103
104
Em seguida, escreva o código abaixo no sql server,
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
Saída seria:
102,103,104
matrizes Postgres são impressionantes. Exemplo:
Criar 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)
agregá-los em uma matriz:
test=# select array_agg(name) from names;
array_agg
-------------------
{Peter,Paul,Mary}
(1 row)
Converter a matriz para uma string delimitada por vírgulas:
test=# select array_to_string(array_agg(name), ', ') from names;
array_to_string
-------------------
Peter, Paul, Mary
(1 row)
Concluído
Desde o PostgreSQL 9.0 é ainda mais fácil .
Oracle 11g Release 2 oferece suporte 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.
Aviso
Tenha cuidado para implementar esta função se não houver possibilidade da string resultante vai mais de 4000 caracteres. Ele vai lançar uma exceção. Se for esse o caso, então você precisa se quer tratar a exceção ou rolar sua própria função que impede que a corda se juntou de ir mais de 4000 caracteres.
No SQL Server 2005 e, mais tarde, utilizar a consulta a seguir 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
Eu não tenho acesso a um SQL Server em casa, por isso estou palpite sobre a sintaxe aqui, mas é mais ou menos:
DECLARE @names VARCHAR(500)
SELECT @names = @names + ' ' + Name
FROM Names
Uma solução CTE recursiva foi sugerido, mas nenhum código fornecido. O código abaixo é um exemplo de uma CTE recursiva - nota que, embora os resultados corresponder a questão, os dados não muito coincidir com a descrição dada, como eu suponho que você realmente quer fazer isso em grupos de linhas, nem todas as linhas na tabela. Mudá-lo para corresponder a todas as linhas na tabela é deixado 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
Você precisa criar uma variável que irá realizar o seu resultado final e selecione para ele, assim.
mais fácil Solução
DECLARE @char VARCHAR(MAX);
SELECT @char = COALESCE(@char + ', ' + [column], [column])
FROM [table];
PRINT @char;
A partir do PostgreSQL 9.0 este é bastante simples:
select string_agg(name, ',')
from names;
Em versões antes array_agg()
9,0 pode ser usado, como mostrado por hgmnz
No SQL Server vNext isso será construído com a função STRING_AGG, leia mais sobre ele aqui: https://msdn.microsoft.com/en-us/library/mt790580.aspx
Usando XML me ajudou na obtenção de linhas separados por vírgulas. Para a vírgula extra que pode usar a função substituir do SQL Server. Em vez de adicionar uma vírgula, o uso de 'dados ()' o AS irá concatenar as linhas com espaços, que mais tarde podem ser substituídos com vírgulas como a sintaxe escrito abaixo.
REPLACE(
(select FName AS 'data()' from NameList for xml path(''))
, ' ', ', ')
Uma solução pronto-a-uso, sem vírgulas extras:
select substring(
(select ', '+Name AS 'data()' from Names for xml path(''))
,3, 255) as "MyList"
Uma lista vazia irá resultar em valor NULL. Normalmente, você irá inserir a lista em uma variável de coluna de tabela ou programa: ajustar o comprimento máximo 255 com sua necessidade
.(Diwakar e Jens Frandsen proporcionou boas respostas, mas a melhoria necessidade.)
SELECT STUFF((SELECT ', ' + name FROM [table] FOR XML PATH('')), 1, 2, '')
Aqui está um exemplo:
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
DECLARE @Names VARCHAR(8000)
SELECT @name = ''
SELECT @Names = @Names + ',' + Names FROM People
SELECT SUBSTRING(2, @Names, 7998)
Isso coloca a vírgula perdida no início.
No entanto, se você precisar de outras colunas, ou para CSV uma tabela filho que você precisa para quebrar isso em um usuário escalar campo definido (UDF).
Você pode usar o caminho XML como uma subconsulta correlacionada na cláusula SELECT também (mas eu teria que esperar até que eu voltar ao trabalho porque o Google não fazer coisas trabalho em casa: -)
Com as outras respostas, a pessoa que lê a resposta deve estar ciente de uma tabela de domínio específico, como veículo ou estudante. A tabela deve ser criado e preenchido com os dados para testar uma solução.
A seguir é um exemplo que usa SQL Server "INFORMATION_SCHEMA.COLUMNS" mesa. Ao utilizar esta solução, há mesas precisam ser criados ou dados acrescentou. Este exemplo cria uma vírgula lista de nomes de colunas separadas 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
Para o Oracle bancos de dados, consulte esta pergunta: Como várias linhas ser concatenados em um no Oracle sem criar um procedimento armazenado?
A melhor resposta parece ser por @Emmanuel, usando a função built-in LISTAGG (), 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 @ 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 contra a usá-lo para qualquer SQL aplicativo, para uso em seu próprio risco.
Além disso, você terá que escrever sua própria função; o documento do Oracle acima tem um guia sobre como fazer isso.
Eu realmente gostei elegancy de resposta de Dana . Só queria torná-la completa.
DECLARE @names VARCHAR(MAX)
SET @names = ''
SELECT @names = @names + ', ' + Name FROM Names
-- Deleting last two symbols (', ')
SET @sSql = LEFT(@sSql, LEN(@sSql) - 1)
Para evitar valores nulos você pode usar CONCAT ()
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
Esta resposta vai exigir algum privilégio no servidor ao trabalho.
Assembléias são uma boa opção para você. Há um monte de sites que explicam como criá-la. O que eu acho que é muito bem explicado é este um
Se você quiser, eu já criou a montagem, e é possível fazer o download do DLL aqui .
Depois de ter baixado, você precisará executar o script a seguir no 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 ser acessível ao servidor. Desde que você tenha feito com sucesso todas as etapas, você pode usar a função como:
SELECT dbo.Concat(field1, ',')
FROM Table1
Espero que ajude !!!
Eu costumo usar escolha como este para cordas concatenar em 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
Se você quer lidar com valores nulos você pode fazê-lo através da adição de uma cláusula onde ou adicionar outro aglutinam em torno do primeiro.
DECLARE @Names VARCHAR(8000)
SELECT @Names = COALESCE(COALESCE(@Names + ', ', '') + Name, @Names) FROM People
Temos usuários que podem ter muitos dados e nós queremos ter uma saída, onde podemos ver todos os usuários Dados em uma lista:
Resultado:
___________________________
| id | rowList |
|-------------------------|
| 0 | 6, 9 |
| 1 | 1,2,3,4,5,7,8,1 |
|_________________________|
Configuração de 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);
Inquérito:
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
No Oracle, é wm_concat
. Eu acredito que esta função está disponível no 10g Release e superior.
Isto pode ser útil também
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
volta ??p>
Peter,Paul,Mary
Este método aplica-se ao banco de dados Teradata Aster apenas como ele utiliza a sua função nPath.
Mais uma vez, temos alunos de mesa
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
Em seguida, com nPath é apenas único SELECT:
SELECT * FROM npath(
ON Students
PARTITION BY SubjectID
ORDER BY StudentName
MODE(nonoverlapping)
PATTERN('A*')
SYMBOLS(
'true' as A
)
RESULT(
FIRST(SubjectID of A) as SubjectID,
ACCUMULATE(StudentName of A) as StudentName
)
);
Resultado:
SubjectID StudentName
---------- -------------
1 [John, Mary, Sam]
2 [Alaina, Edward]