En este post hablaré de una característica muy solicitada por Microsoft, que aún no ha creado de forma nativa en SQL Server, que es la concatenación de datos mediante agrupaciones, ya presente en otros DBMS como MySQL (GROUP_CONCAT), Oracle (XMLAGG) y PostgreeSQL (STRING_AGG o ARRAY_TO_STRING(ARRAY_AGG())).
Mucha gente piensa que esta característica es la función CONCAT(), introducida en SQL Server 2012, pero solo permite la concatenación de varias columnas de una fila en una sola columna y no la concatenación de varias filas en una sola columna.
Concatenar columnas en cadenas agrupadas consiste en transformar líneas en una cadena concatenada, de manera que se pueden agrupar los valores por algún campo, y se concatena la otra columna, como se muestra a continuación:
SQL Server: la concatenación agrupada convierte filas en cadenas
Para crear nuestra tabla de prueba, use los siguientes comandos:
IF (OBJECT_ID('dbo.Teste_Group_Concat') IS NOT NULL) DROP TABLE dbo.Teste_Group_Concat
CREATE TABLE dbo.Teste_Group_Concat(
Categoria VARCHAR(100),
Descricao VARCHAR(100)
)
INSERT INTO dbo.Teste_Group_Concat( Categoria, Descricao )
VALUES ('Brinquedo', 'Bola'),
('Brinquedo', 'Carrinho'),
('Brinquedo', 'Boneco'),
('Brinquedo', 'Jogo'),
('Cama e Mesa', 'Toalha'),
('Cama e Mesa', 'Edredom'),
('Informatica', 'Teclado'),
('Informatica', 'Mouse'),
('Informatica', 'HD'),
('Informatica', 'CPU'),
('Informatica', 'Memoria'),
('Informatica', 'Placa-Mae'),
(NULL, 'TV')
JUNTARSE
Cómo concatenar columnas en cadenas agrupadas usando COALESCE
SQL Server: la concatenación agrupada convierte filas en cadenas - COALESCE
Como podemos ver arriba, usando la función COALESCE siempre necesitamos almacenar los resultados en una variable, y por esta razón no podemos obtener los resultados agrupados por categoría, como necesitamos. Si solo desea convertir columnas en una cadena, sin agruparlas, esta solución le conviene, pero no es la que necesitamos para esta publicación.
COSAS + PARA RUTA XML
Cómo concatenar columnas en cadenas agrupadas usando STUFF + FOR XML PATH
Una forma práctica y eficaz de resolver este problema es utilizar FOR XML PATH y STUFF para realizar una concatenación agrupada, de modo que el resultado sea exactamente el que esperábamos:
SELECT
Categoria,
STUFF((
SELECT ', ' + B.Descricao
FROM dbo.Teste_Group_Concat B
WHERE ISNULL(B.Categoria, '') = ISNULL(A.Categoria, '')
ORDER BY B.Descricao
FOR XML PATH('')), 1, 2, ''
) AS Descricao
FROM
dbo.Teste_Group_Concat A
GROUP BY
Categoria
ORDER BY
Categoria
SQL Server: la concatenación agrupada convierte filas en cadenas - COSAS PARA LA RUTA XML
ACTUALIZAR
Cómo concatenar columnas en cadenas agrupadas usando ACTUALIZAR
DECLARE
@Categoria VARCHAR(MAX),
@Descricoes VARCHAR(MAX)
DECLARE @Tabela TABLE (
Categoria VARCHAR(200),
Descricao VARCHAR(200),
Descricoes VARCHAR(200)
)
INSERT @Tabela (
Categoria,
Descricao
)
SELECT
Categoria,
Descricao
FROM
dbo.Teste_Group_Concat
ORDER BY
Categoria,
Descricao
UPDATE
@Tabela
SET
@Descricoes = Descricoes = COALESCE(CASE COALESCE(@Categoria, '')
WHEN Categoria THEN @Descricoes + ', ' + Descricao
ELSE Descricao
END, ''),
@Categoria = ISNULL(Categoria, '')
SELECT
Categoria,
Descricoes = MAX(Descricoes)
FROM
@Tabela
GROUP BY
Categoria
ORDER BY
Categoria
SQL Server: la concatenación agrupada convierte filas en cadenas - ACTUALIZACIÓN
Otra solución que nos permitió generar datos según lo demandan nuestras necesidades.
BUCLE CON EL CURSOR
Cómo concatenar columnas en cadenas agrupadas usando LOOPING con CURSOR
DECLARE @Tabela TABLE (
Categoria VARCHAR(MAX),
Descricoes VARCHAR(MAX)
)
INSERT @Tabela (
Categoria,
Descricoes
)
SELECT
Categoria,
''
FROM
dbo.Teste_Group_Concat
GROUP BY
Categoria
DECLARE
@Categoria VARCHAR(MAX),
@Descricao VARCHAR(MAX),
@Descricoes VARCHAR(MAX)
DECLARE c CURSOR LOCAL FAST_FORWARD
FOR
SELECT
Categoria,
Descricao
FROM
dbo.Teste_Group_Concat
ORDER BY
Categoria,
Descricao
OPEN c
FETCH c INTO @Categoria, @Descricao
WHILE @@FETCH_STATUS = 0
BEGIN
UPDATE @Tabela
SET Descricoes += ', ' + @Descricao
WHERE ISNULL(Categoria, '') = ISNULL(@Categoria, '')
FETCH c INTO @Categoria, @Descricao
END
CLOSE c
DEALLOCATE c
SELECT
Categoria,
Descricoes = STUFF(Descricoes, 1, 1, '')
FROM
@Tabela
ORDER BY
Categoria
SQL Server: la concatenación agrupada convierte filas en cadenas - CURSOR
Esta solución también cumplió con lo que necesitábamos, pero como todos los bucles, no tiene buen rendimiento. Personalmente detesto la creación de cursores (a menos que TODAVÍA estés en SQL Server 2000), en el último caso, prefiero usar WHILE, pero tenemos mejores soluciones aquí en esta publicación.
BUCLE CON MIENTRAS
Cómo concatenar columnas en cadenas agrupadas usando LOOPING con WHILE
DECLARE @Tabela_Original TABLE (
Id INT IDENTITY(1, 1),
Categoria VARCHAR(MAX),
Descricao VARCHAR(MAX)
)
INSERT @Tabela_Original (
Categoria,
Descricao
)
SELECT
Categoria,
Descricao
FROM
dbo.Teste_Group_Concat
ORDER BY
Categoria
DECLARE @Tabela TABLE (
Id INT IDENTITY(1, 1),
Categoria VARCHAR(MAX),
Descricoes VARCHAR(MAX)
)
INSERT @Tabela (
Categoria,
Descricoes
)
SELECT
Categoria,
''
FROM
dbo.Teste_Group_Concat
GROUP BY
Categoria
DECLARE
@Categoria VARCHAR(MAX),
@Descricao VARCHAR(MAX),
@Descricoes VARCHAR(MAX),
@Contador INT = 1,
@Numero_Linhas INT = (SELECT COUNT(*) FROM @Tabela_Original)
WHILE(@Contador <= @Numero_Linhas)
BEGIN
SELECT
@Categoria = ISNULL(Categoria, ''),
@Descricao = Descricao
FROM
@Tabela_Original
WHERE
Id = @Contador
ORDER BY
Categoria,
Descricao
UPDATE @Tabela
SET Descricoes += ', ' + @Descricao
WHERE ISNULL(Categoria, '') = ISNULL(@Categoria, '')
SET @Contador = @Contador + 1
END
SELECT
Categoria,
Descricoes = STUFF(Descricoes, 1, 1, '')
FROM
@Tabela
ORDER BY
Categoria
SQL Server: la concatenación agrupada convierte filas en cadenas - MIENTRAS
Se encontró la solución, pero como todos los bucles, no funciona.
CTE RECURSIVO
Cómo concatenar columnas en cadenas agrupadas usando CTE recursivo
;WITH Dados as
(
SELECT
Categoria,
CONVERT(NVARCHAR(MAX), Descricao) AS Descricao,
ROW_NUMBER() OVER (PARTITION BY Categoria ORDER BY Descricao) AS Linha
FROM
dbo.Teste_Group_Concat
),
Tabela1 AS
(
SELECT Categoria, Descricao, Linha FROM Dados WHERE Linha = 1
),
Resultado AS
(
SELECT Categoria, Descricao, Linha FROM Tabela1 WHERE Linha = 1
UNION ALL
SELECT Dados.Categoria, Resultado.Descricao + N', ' + Dados.Descricao, Dados.Linha
FROM Dados
JOIN Resultado ON Resultado.Categoria = Dados.Categoria AND Dados.Linha = Resultado.Linha + 1
)
SELECT
Categoria,
Descricoes = MAX(Descricao)
FROM
Resultado
GROUP BY
Categoria
ORDER BY
Categoria
OPTION (MAXRECURSION 0);
Esta solución utiliza la técnica de recursividad para resolver nuestro problema sin necesidad de realizar bucles. Aunque no es muy fácil de entender al principio, es una solución práctica y muy interesante.
FUNCIÓN ESCALAR (UDF)
Cómo concatenar columnas en cadenas agrupadas usando la función escalar (UDF)
CREATE FUNCTION dbo.fncConcatCategoria (
@Ds_Categoria VARCHAR(MAX)
)
RETURNS VARCHAR(MAX)
WITH SCHEMABINDING
AS
BEGIN
DECLARE @Retorno VARCHAR(MAX);
SELECT
@Retorno = COALESCE(@Retorno + ', ', '') + Descricao
FROM
dbo.Teste_Group_Concat
WHERE
ISNULL(Categoria, '') = ISNULL(@Ds_Categoria, '')
ORDER BY
Descricao
RETURN @Retorno
END
GO
SELECT
Categoria,
dbo.fncConcatCategoria(Categoria) AS Descricao
FROM
dbo.Teste_Group_Concat
GROUP BY
Categoria
Función muy útil y una vez creada, resulta muy práctica de utilizar. El problema de esta solución es que no tiene mucho rendimiento y requiere que crees una función para cada tabla que necesites para llevar a cabo esta solución, es decir, termina siendo poco genérica y necesitarías crear varias funciones que hagan prácticamente lo mismo en tu base de datos.
CLR de SQL (C#)
Cómo concatenar columnas en cadenas agrupadas usando SCL CLR (C#)
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Text;
[Serializable]
[SqlUserDefinedAggregate(
Format.UserDefined,
IsInvariantToNulls = false,
IsInvariantToDuplicates = false,
IsInvariantToOrder = true,
MaxByteSize = -1)]
public struct concat : IBinarySerialize, INullable
{
private StringBuilder _accumulator;
private string _delimiter;
public bool IsNull { get; private set; }
public void Init()
{
_accumulator = new StringBuilder();
_delimiter = string.Empty;
this.IsNull = true;
}
public void Accumulate([SqlFacet(MaxSize = -1)]SqlString Value, [SqlFacet(MaxSize = -1)]SqlString Delimiter)
{
if (Value.IsNull) return;
if (!Delimiter.IsNull & Delimiter.Value.Length > 0)
{
_delimiter = Delimiter.Value;
if (_accumulator.Length > 0)
_accumulator.Append(Delimiter.Value);
}
_accumulator.Append(Value.Value);
IsNull = false;
}
public void Merge(concat Group)
{
if (_accumulator.Length > 0
& Group._accumulator.Length > 0) _accumulator.Append(_delimiter);
_accumulator.Append(Group._accumulator.ToString());
}
public SqlString Terminate()
{
return new SqlString(_accumulator.ToString());
}
void IBinarySerialize.Read(System.IO.BinaryReader r)
{
_delimiter = r.ReadString();
_accumulator = new StringBuilder(r.ReadString());
if (_accumulator.Length != 0) this.IsNull = false;
}
void IBinarySerialize.Write(System.IO.BinaryWriter w)
{
w.Write(_delimiter);
w.Write(_accumulator.ToString());
}
}
SQL Server: la concatenación agrupada convierte filas en cadenas - SQL CLR CSHARP
Mi solución favorita. Práctico, extremadamente rápido, genérico y resuelve nuestro problema. ¿No sabes qué es CLR o lo sabes, pero no sabes cómo implementarlo en tu base de datos? Entérate más visitando mi post Introducción a SQL CLR (Common Language Runtime) en SQL Server.
Consideraciones de rendimiento
Midamos ahora nuestras soluciones y descubramos cuáles son las más rápidas y cuáles las más lentas.
CLR SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - CLR
COSAS + PARA RUTA XML SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - COSAS PARA LA RUTA XML
ACTUALIZAR SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - ACTUALIZACIÓN
CTE recursivo SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - CTE
Bucle mientras SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - MIENTRAS
Bucle con cursor SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - CURSOR
Inmediatamente podemos eliminar los bucles, que eran mucho peores. ¿Qué pasa si insertamos más registros? Como 850.000 líneas... ¿Cómo será la actuación?
CLR SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - CLR2
COSAS + PARA RUTA XML SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - COSAS PARA LA RUTA XML
Como ningún otro método fue capaz de terminar el procesamiento en menos de 1 minuto, a pesar de haber usado 850.000 líneas con el CLR y FOR XML PATH, solo usaré 158.000 líneas (27%) para el resto de métodos para intentar analizar los resultados. Vamos a ver.
ACTUALIZAR SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - ACTUALIZACIÓN
El tiempo de ACTUALIZACIÓN no fue tan malo... 5,7 segundos para 158.000 líneas es un tiempo razonable.
Bueno, intenté esperar el CTE Recursivo... Incluso fui a almorzar y lo dejé corriendo aquí, pero después de 2 horas y 22 minutos no pude esperar más para terminar de procesar las 158 mil líneas (recordando que el CLR procesó 850.000 en 3s y el FOR XML en 1,5s).
Aparentemente, nuestro ganador fue FOR XML PATH, pero si analizamos los resultados, CLR entregó el resultado correcto, incluso con toda esta absurda cantidad de líneas: SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - CLR4
En cuanto a FOR XML PATH… Terminó perdiéndose y los resultados no estaban en el orden correcto. SQL Server: la concatenación agrupada convierte filas en cadenas - Rendimiento - COSAS PARA XML PATH4
Por tanto, la solución que recomiendo como la mejor de todas para este tipo de situaciones es SQL CLR!
¡Eso es todo, amigos!
Gracias por visitarnos y nos vemos en el próximo post.
función clr del servidor sql cosas para ruta xml cte recursivo convertir columnas filas agrupadas cadena de concatenación concat cómo convertir columnas concatenadas en cadena
función clr del servidor sql cosas para ruta xml cte recursivo convertir columnas filas agrupadas cadena de concatenación concat cómo convertir columnas concatenadas en cadena
Concatenar muchas filas en una sola cadena de texto
Concatenar muchas filas en una sola cadena de texto
Dirceu Resende
Arquitecto de Bases de Datos y BI · Microsoft MVP · MCSE, MCSA, MCT, MTA, MCP.
Comentários (0)
Carregando comentários…