String_agg para SQL Server antes de 2017
¿Alguien puede ayudarme a hacer que esta consulta funcione para SQL Server 2014?
Esto funciona en Postgresql y probablemente en SQL Server 2017. En Oracle es listagg
en lugar de string_agg
.
Aquí está el SQL:
select
string_agg(t.id,',') AS id
from
Table t
Revisé en el sitio que se debería usar alguna opción xml pero no pude entenderla.
En SQL Server anterior a 2017, puede hacer:
select stuff( (select ',' + cast(t.id as varchar(max))
from tabel t
for xml path ('')
), 1, 1, ''
);
El único propósito de stuff()
es eliminar la coma inicial. El trabajo lo está realizando for xml path
.
Tenga en cuenta que para algunos caracteres, los valores se escaparán cuando se utilice FOR XML PATH
, por ejemplo:
SELECT STUFF((SELECT ',' + V.String
FROM (VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String)
FOR XML PATH('')),1,1,'');
Esto devuelve la siguiente cadena:
7 > 5,Salt & pepper,2
lines'
Es poco probable que esto sea deseado. Puedes solucionar esto usando TYPE
y luego obteniendo el valor del XML:
SELECT STUFF((SELECT ',' + V.String
FROM (VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String)
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'');
Esto devuelve la siguiente cadena:
7 > 5,Salt & pepper,2
lines
Esto replicaría el comportamiento de lo siguiente:
SELECT STRING_AGG(V.String,',')
FROM VALUES('7 > 5'),('Salt & pepper'),('2
lines'))V(String);
Por supuesto, puede haber ocasiones en las que desee agrupar los datos, lo que lo anterior no demuestra. Para lograr esto, necesitaría utilizar una subconsulta correlacionada. Tome los siguientes datos de muestra:
CREATE TABLE dbo.MyTable (ID int IDENTITY(1,1),
GroupID int,
SomeCharacter char(1));
INSERT INTO dbo.MyTable (GroupID, SomeCharacter)
VALUES (1,'A'), (1,'B'), (1,'D'),
(2,'C'), (2,NULL), (2,'Z');
De esto queríamos los siguientes resultados:
Identificación del grupo | Caracteres |
---|---|
1 | A,B,D |
2 | C,Z |
Para lograr esto necesitarías hacer algo como esto:
SELECT MT.GroupID,
STUFF((SELECT ',' + sq.SomeCharacter
FROM dbo.MyTable sq
WHERE sq.GroupID = MT.GroupID --This is your correlated join and should be on the same columns as your GROUP BY
--You "JOIN" on the columns that would have been in the PARTITION BY
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')
FROM dbo.MyTable MT
GROUP BY MT.GroupID; --I use GROUP BY rather than DISTINCT as we are technically aggregating here
Entonces, si estuviera agrupando en 2 columnas, entonces tendría 2 cláusulas en su subconsulta WHERE
: WHERE MT.SomeColumn = sq.SomeColumn AND MT.AnotherColumn = sq.AnotherColumn
y su exterior GROUP BY
sería GROUP BY MT.SomeColumn, MT.AnotherColumn
.
Finalmente, agreguemos un ORDER BY
a esto, que también define en la subconsulta. Supongamos, por ejemplo, que desea ordenar los datos por el valor descendente ID
en la agregación de cadenas:
SELECT MT.GroupID,
STUFF((SELECT ',' + sq.SomeCharacter
FROM dbo.MyTable sq
WHERE sq.GroupID = MT.GroupID
ORDER BY sq.ID DESC --This is identical to the ORDER BY you would have in your OVER clause
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(MAX)'),1,1,'')
FROM dbo.MyTable MT
GROUP BY MT.GroupID;
Para produciría los siguientes resultados:
Identificación del grupo | Caracteres |
---|---|
1 | D,B,A |
2 | Z,C |
Como era de esperar, esto nunca será tan eficiente como STRING_AGG
, debido a que la tabla hace referencia varias veces (si necesita realizar múltiples agregaciones, entonces necesita múltiples subconsultas), pero una tabla bien indexada será de gran ayuda para el RDBMS. Si el rendimiento realmente es un problema, porque está haciendo múltiples agregaciones de cadenas en una sola consulta, entonces le sugeriría que reconsidere si necesita la agregación, o ya es hora de que considere actualizar.