Como eu consigo:
id Name Value
1 A 4
1 B 8
2 C 9
para
id Column
1 A:4, B:8
2 C:9
Como eu consigo:
id Name Value
1 A 4
1 B 8
2 C 9
para
id Column
1 A:4, B:8
2 C:9
Respostas:
Não é necessário nenhum CURSOR, WHILE loop ou Função Definida pelo Usuário .
Só precisa ser criativo com FOR XML e PATH.
[Nota: Esta solução funciona apenas no SQL 2005 e posterior. A pergunta original não especificou a versão em uso.]
CREATE TABLE #YourTable ([ID] INT, [Name] CHAR(1), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'A',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'B',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT
[ID],
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
Se for o SQL Server 2017 ou o SQL Server Vnext, SQL Azure, você poderá usar string_agg como abaixo:
select id, string_agg(concat(name, ':', [value]), ', ')
from #YourTable
group by id
o uso do caminho XML não concatenará perfeitamente como seria de esperar ... ele substituirá "&" por "& amp;" e também vai mexer com <" and ">
... talvez algumas outras coisas, não tenho certeza ... mas você pode tentar isso
Me deparei com uma solução alternativa para isso ... você precisa substituir:
FOR XML PATH('')
)
com:
FOR XML PATH(''),TYPE
).value('(./text())[1]','VARCHAR(MAX)')
... ou NVARCHAR(MAX)
se é isso que você está usando.
por que diabos não SQL
tem uma função agregada concatenada? isso é uma PITA.
Corri para um par de problemas quando eu tentei converter a sugestão de Kevin Fairchild ao trabalho com cordas contendo espaços e caracteres XML especiais ( &
, <
, >
) que foram codificadas.
A versão final do meu código (que não responde à pergunta original, mas pode ser útil para alguém) fica assim:
CREATE TABLE #YourTable ([ID] INT, [Name] VARCHAR(MAX), [Value] INT)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'Oranges & Lemons',4)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (1,'1 < 2',8)
INSERT INTO #YourTable ([ID],[Name],[Value]) VALUES (2,'C',9)
SELECT [ID],
STUFF((
SELECT ', ' + CAST([Name] AS VARCHAR(MAX))
FROM #YourTable WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE
/* Use .value to uncomment XML entities e.g. > < etc*/
).value('.','VARCHAR(MAX)')
,1,2,'') as NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
Em vez de usar um espaço como delimitador e substituir todos os espaços por vírgulas, ele apenas pré-vírgula e espaço para cada valor usado STUFF
para remover os dois primeiros caracteres.
A codificação XML é resolvida automaticamente usando a diretiva TYPE .
Outra opção usando o Sql Server 2005 e superior
---- test data
declare @t table (OUTPUTID int, SCHME varchar(10), DESCR varchar(10))
insert @t select 1125439 ,'CKT','Approved'
insert @t select 1125439 ,'RENO','Approved'
insert @t select 1134691 ,'CKT','Approved'
insert @t select 1134691 ,'RENO','Approved'
insert @t select 1134691 ,'pn','Approved'
---- actual query
;with cte(outputid,combined,rn)
as
(
select outputid, SCHME + ' ('+DESCR+')', rn=ROW_NUMBER() over (PARTITION by outputid order by schme, descr)
from @t
)
,cte2(outputid,finalstatus,rn)
as
(
select OUTPUTID, convert(varchar(max),combined), 1 from cte where rn=1
union all
select cte2.outputid, convert(varchar(max),cte2.finalstatus+', '+cte.combined), cte2.rn+1
from cte2
inner join cte on cte.OUTPUTID = cte2.outputid and cte.rn=cte2.rn+1
)
select outputid, MAX(finalstatus) from cte2 group by outputid
Instale os agregados SQLCLR em http://groupconcat.codeplex.com
Em seguida, você pode escrever um código como esse para obter o resultado solicitado:
CREATE TABLE foo
(
id INT,
name CHAR(1),
Value CHAR(1)
);
INSERT INTO dbo.foo
(id, name, Value)
VALUES (1, 'A', '4'),
(1, 'B', '8'),
(2, 'C', '9');
SELECT id,
dbo.GROUP_CONCAT(name + ':' + Value) AS [Column]
FROM dbo.foo
GROUP BY id;
O SQL Server 2005 e versões posteriores permitem que você crie suas próprias funções agregadas personalizadas , inclusive para concatenação - veja o exemplo na parte inferior do artigo vinculado.
Oito anos depois ... O Microsoft SQL Server vNext Database Engine finalmente aprimorou o Transact-SQL para suportar diretamente a concatenação de cadeias agrupadas. O Community Technical Preview versão 1.0 adicionou a função STRING_AGG e o CTP 1.1 adicionou a cláusula WITHIN GROUP para a função STRING_AGG.
Referência: https://msdn.microsoft.com/en-us/library/mt775028.aspx
Esta é apenas uma adição ao post de Kevin Fairchild (muito inteligente a propósito). Eu o teria adicionado como um comentário, mas ainda não tenho pontos suficientes :)
Eu estava usando essa idéia para uma visão em que estava trabalhando, no entanto, os itens que eu concatinava continha espaços. Então, modifiquei o código levemente para não usar espaços como delimitadores.
Mais uma vez obrigado pela solução legal Kevin!
CREATE TABLE #YourTable ( [ID] INT, [Name] CHAR(1), [Value] INT )
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (1, 'A', 4)
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (1, 'B', 8)
INSERT INTO #YourTable ([ID], [Name], [Value]) VALUES (2, 'C', 9)
SELECT [ID],
REPLACE(REPLACE(REPLACE(
(SELECT [Name] + ':' + CAST([Value] AS VARCHAR(MAX)) as A
FROM #YourTable
WHERE ( ID = Results.ID )
FOR XML PATH (''))
, '</A><A>', ', ')
,'<A>','')
,'</A>','') AS NameValues
FROM #YourTable Results
GROUP BY ID
DROP TABLE #YourTable
No Oracle, você pode usar a função agregada LISTAGG.
Registros originais
name type
------------
name1 type1
name2 type2
name2 type3
Sql
SELECT name, LISTAGG(type, '; ') WITHIN GROUP(ORDER BY name)
FROM table
GROUP BY name
Resulta em
name type
------------
name1 type1
name2 type2; type3
Esse tipo de pergunta é feita aqui com muita frequência e a solução dependerá muito dos requisitos subjacentes:
https://stackoverflow.com/search?q=sql+pivot
e
https://stackoverflow.com/search?q=sql+concatenate
Normalmente, não há uma maneira somente SQL de fazer isso sem sql dinâmico, uma função definida pelo usuário ou um cursor.
Apenas para acrescentar ao que o Cade disse, isso geralmente é uma coisa de exibição de front-end e, portanto, deve ser tratado lá. Sei que às vezes é mais fácil escrever algo 100% no SQL para coisas como exportação de arquivos ou outras soluções "somente SQL", mas na maioria das vezes essa concatenação deve ser tratada na camada de exibição.
Não precisa de um cursor ... um loop while é suficiente.
------------------------------
-- Setup
------------------------------
DECLARE @Source TABLE
(
id int,
Name varchar(30),
Value int
)
DECLARE @Target TABLE
(
id int,
Result varchar(max)
)
INSERT INTO @Source(id, Name, Value) SELECT 1, 'A', 4
INSERT INTO @Source(id, Name, Value) SELECT 1, 'B', 8
INSERT INTO @Source(id, Name, Value) SELECT 2, 'C', 9
------------------------------
-- Technique
------------------------------
INSERT INTO @Target (id)
SELECT id
FROM @Source
GROUP BY id
DECLARE @id int, @Result varchar(max)
SET @id = (SELECT MIN(id) FROM @Target)
WHILE @id is not null
BEGIN
SET @Result = null
SELECT @Result =
CASE
WHEN @Result is null
THEN ''
ELSE @Result + ', '
END + s.Name + ':' + convert(varchar(30),s.Value)
FROM @Source s
WHERE id = @id
UPDATE @Target
SET Result = @Result
WHERE id = @id
SET @id = (SELECT MIN(id) FROM @Target WHERE @id < id)
END
SELECT *
FROM @Target
Vamos ser muito simples:
SELECT stuff(
(
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
FOR XML PATH('')
)
, 1, 2, '')
Substitua esta linha:
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
Com sua consulta.
não encontrou nenhuma resposta cruzada, também não há necessidade de extração de xml. Aqui está uma versão ligeiramente diferente do que Kevin Fairchild escreveu. É mais rápido e fácil de usar em consultas mais complexas:
select T.ID
,MAX(X.cl) NameValues
from #YourTable T
CROSS APPLY
(select STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = T.ID)
FOR XML PATH(''))
,1,2,'') [cl]) X
GROUP BY T.ID
Você pode melhorar o desempenho de maneira significativa da seguinte maneira, se agrupar por conter principalmente um item:
SELECT
[ID],
CASE WHEN MAX( [Name]) = MIN( [Name]) THEN
MAX( [Name]) NameValues
ELSE
STUFF((
SELECT ', ' + [Name] + ':' + CAST([Value] AS VARCHAR(MAX))
FROM #YourTable
WHERE (ID = Results.ID)
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,2,'') AS NameValues
END
FROM #YourTable Results
GROUP BY ID
Usando Substituir Função e FOR JSON PATH
SELECT T3.DEPT, REPLACE(REPLACE(T3.ENAME,'{"ENAME":"',''),'"}','') AS ENAME_LIST
FROM (
SELECT DEPT, (SELECT ENAME AS [ENAME]
FROM EMPLOYEE T2
WHERE T2.DEPT=T1.DEPT
FOR JSON PATH,WITHOUT_ARRAY_WRAPPER) ENAME
FROM EMPLOYEE T1
GROUP BY DEPT) T3
Para dados de amostra e mais maneiras, clique aqui
Se você tiver ativado o clr, poderá usar a biblioteca Group_Concat do GitHub
GROUP_CONCAT()
função agregada, mas resolvê-lo no Microsoft SQL Server é mais complicado. Consulte a seguinte pergunta do SO para obter ajuda: " Como obter vários registros em um registro com base na relação? "