Come utilizzare GROUP BY per concatenare le stringhe in SQL Server?
-
07-07-2019 - |
Domanda
Come posso ottenere:
id Name Value
1 A 4
1 B 8
2 C 9
a
id Column
1 A:4, B:8
2 C:9
Soluzione
Nessun CURSORE, ciclo WHILE o funzione definita dall'utente necessaria .
Devo solo essere creativo con FOR XML e PATH.
[Nota: questa soluzione funziona solo su SQL 2005 e versioni successive. La domanda originale non specificava la versione in 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
Altri suggerimenti
Se si tratta di SQL Server 2017 o SQL Server Vnext, SQL Azure è possibile utilizzare string_agg come indicato di seguito:
select id, string_agg(concat(name, ':', [value]), ', ')
from #YourTable
group by id
l'uso del percorso XML non si concatenerà perfettamente come ci si potrebbe aspettare ... sostituirà " & amp; " con " & amp; amp; " e anche pasticciare con <" and ">
... forse alcune altre cose, non sono sicuro ... ma puoi provare questo
Mi sono imbattuto in una soluzione alternativa per questo ... è necessario sostituire:
FOR XML PATH('')
)
con:
FOR XML PATH(''),TYPE
).value('(./text())[1]','VARCHAR(MAX)')
... o NVARCHAR(MAX)
se è quello che stai utilizzando.
perché diavolo SQL
non ha una funzione aggregata concatenata? questa è una PITA.
Ho riscontrato un paio di problemi quando ho provato a convertire il suggerimento di Kevin Fairchild per lavorare con stringhe contenenti spazi e caratteri XML speciali (&
, <
, >
) che sono stati codificati.
La versione finale del mio codice (che non risponde alla domanda originale ma può essere utile a qualcuno) è simile a questa:
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
Anziché utilizzare uno spazio come delimitatore e sostituire tutti gli spazi con virgole, anticipa solo una virgola e uno spazio per ciascun valore, quindi utilizza STUFF
per rimuovere i primi due caratteri.
La codifica XML viene gestita automaticamente utilizzando il TYPE direttiva.
Un'altra opzione che utilizza Sql Server 2005 e versioni successive
---- 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
Installa gli aggregati SQLCLR da http://groupconcat.codeplex.com
Quindi puoi scrivere codice come questo per ottenere il risultato che hai richiesto:
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;
SQL Server 2005 e versioni successive ti consentono di creare le tue funzioni di aggregazione personalizzate , incluso per cose come la concatenazione, vedere l'esempio in fondo all'articolo collegato.
Otto anni dopo ... Microsoft SQL Server vNext Database Engine ha finalmente migliorato Transact-SQL per supportare direttamente la concatenazione di stringhe raggruppate. L'anteprima tecnica della community versione 1.0 ha aggiunto la funzione STRING_AGG e CTP 1.1 ha aggiunto la clausola WITHIN GROUP per la funzione STRING_AGG.
Riferimento: https://msdn.microsoft.com/en-us/ biblioteca / mt775028.aspx
Un esempio potrebbe essere
In Oracle è possibile utilizzare la funzione di aggregazione LISTAGG.
Record originali
name type
------------
name1 type1
name2 type2
name2 type3
Sql
SELECT name, LISTAGG(type, '; ') WITHIN GROUP(ORDER BY name)
FROM table
GROUP BY name
Risultato
name type
------------
name1 type1
name2 type2; type3
Questo tipo di domanda viene posta qui molto spesso e la soluzione dipenderà molto dai requisiti sottostanti:
https://stackoverflow.com/search?q=sql+pivot
e
https://stackoverflow.com/search?q=sql+concatenate
In genere, non esiste un solo modo SQL per farlo senza sql dinamico, una funzione definita dall'utente o un cursore.
Solo per aggiungere a ciò che ha detto Cade, questa è di solito una cosa di visualizzazione front-end e dovrebbe quindi essere gestita lì. So che a volte è più facile scrivere qualcosa al 100% in SQL per cose come l'esportazione di file o altri & Quot; SQL solo & Quot; soluzioni, ma la maggior parte delle volte questa concatenazione dovrebbe essere gestita nel tuo livello di visualizzazione.
Questa è solo un'aggiunta al post di Kevin Fairchild (molto intelligente tra l'altro). Lo avrei aggiunto come commento, ma non ho ancora abbastanza punti :)
Stavo usando questa idea per una vista su cui stavo lavorando, tuttavia gli oggetti che stavo concatenando contenevano spazi. Quindi ho modificato leggermente il codice per non usare spazi come delimitatori.
Ancora grazie per la bella soluzione 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
Non è necessario un cursore ... un ciclo while è sufficiente.
------------------------------
-- 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
Diventiamo molto semplici:
SELECT stuff(
(
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
FOR XML PATH('')
)
, 1, 2, '')
Sostituisci questa riga:
select ', ' + x from (SELECT 'xxx' x union select 'yyyy') tb
Con la tua query.
non ha visto risposte incrociate, inoltre non è necessario estrarre xml. Ecco una versione leggermente diversa di ciò che ha scritto Kevin Fairchild. È più veloce e più facile da usare nelle query più complesse:
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
Puoi migliorare in modo significativo le prestazioni nel modo seguente se raggruppa per contiene principalmente un elemento:
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