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
?
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
?
Respostas:
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 PATH
mé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 substring
para 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
<
ou &
. Veja o comentário de @ BenHinman.
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.
FOR XML
se destina a gerar XML, não concatenar seqüências arbitrárias. É por isso que escapa &
, <
e >
aos códigos de entidade XML ( &
, <
, >
). Presumo que também irá escapar "
e '
para "
e '
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.
string_agg
v.Next. e tudo isso pode ir embora.
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):
1) Não há necessidade de inicializar @Names
com um valor de sequência vazio.
2) Não é necessário retirar um separador extra no final.
@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ê)).
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.
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;
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
+ ', '
ele ainda adiciona um espaço único entre todos os elementos concatenados.
SELECT STUFF(REPLACE((SELECT '#!'+city AS 'data()' FROM #cityzip FOR XML PATH ('')),' #!',', '),1,2,'')
SELECT Stuff(
(SELECT N', ' + Name FROM Names FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'')
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
Você pode usar a nova função STRING_AGG
<
, >
, &
, etc, que FOR XML PATH('')
vai escapar automaticamente.
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
SEPARATOR '", "'
vou sentir falta de alguns caracteres no final da última entrada. por que isso pode acontecer?
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_len
como está escrito em stackoverflow.com/a/1278210/1498405
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
Declare @Numbers AS Nvarchar(MAX)
e funcionou bem. Você pode explicar por que recomenda não usá-lo, por favor?
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 .
select array_to_string(array_agg(name||'('||id||')'
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.
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.
LISTAGG
funciona perfeito! Basta ler o documento vinculado aqui. wm_concat
removido da versão 12c em diante.
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
<
ou &
.
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
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ' ' END + Name FROM Names
SELECT @names = @names + ISNULL(' ' + Name, '')
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
name
coluna em uma sequência separada por vírgula por 4 grupos de id
s. À primeira vista, acho que isso é mais trabalhoso do que a maioria das outras soluções para o SQL Server.
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
SELECT string_agg(non_text_type::text, ',') FROM table
varchar
ouchar
No SQL Server vNext, isso será incorporado com a função STRING_AGG, leia mais sobre isso aqui: https://msdn.microsoft.com/en-us/library/mt790580.aspx
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(''))
, ' ', ', ')
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.)
', '
por ','
se você não quiser o espaço extra.
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
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 :-)
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
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.
Para evitar valores nulos, você pode usar CONCAT ()
DECLARE @names VARCHAR(500)
SELECT @names = CONCAT(@names, ' ', name)
FROM Names
select @names
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)
SELECT @names = @names + CASE WHEN LEN(@names)=0 THEN '' ELSE ', ' END + Name FROM Names
, em seguida, você não tem de truncar-lo depois.
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!!!
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
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
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
No Oracle, é wm_concat
. Acredito que esta função esteja disponível na versão 10g e superior.
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