A partir do SQL2017 uma função do tipo aggregate veio para facilitar as implementações de rotinas de banco de dados dos desenvolvedores: STRING_AGG.
Antes desta função, precisávamos construir uma sequência de updates dentro de while para chegar ao mesmo resultado.
O STRING_AGG(<expressão>, <separador>) concatena strings lado a lado dentro de uma sequência de registros agrupados pelo GROUP BY, separando-as por um separador definido pelo desenvolvedor no segundo parâmetro da função.
Os seguintes exemplos ilustram melhor o conceito:
i) Na tabela de estoque de fios (tabela Swd_Est_Fios) tenho uma lista de códigos de fios (campo CodFio) e quero agrupá-los para demonstrar em uma coluna apenas todos os códigos de etiquetas (campo Etiqueta) de cada fio que entraram em uma determinada data (campo Data):
SELECT CodFio, STRING_AGG(Etiqueta, ', ')
FROM Swd_Est_Fios
WHERE Data='2025-01-20'
GROUP BY CodFio
O resultado será:
Figura 1 - Resultado do script para agrupar etiquetas de cada fio em uma data:
ii) Agora imagine a mesma lista de estoque de fios, mas ao invés de relacionar o código da etiqueta da caixa de fio (que é único em cada entrada), eu relacione o documento (campo Documento) destas caixas. Neste caso, o mesmo documento (ou nota fiscal, se preferir) pode conter várias caixas relacionadas. Então haverá repetição dos documentos:
SELECT CodFio, STRING_AGG(Documento, ', ')
FROM Swd_Est_Fios
WHERE Data='2025-01-20'
GROUP BY CodFio
Figura 2 - Resultado do script para agrupar documentos (NFs) de cada fio em uma data
Observe o resultado da figura 2. Apesar do agrupamento conveniente do STRING_AGG em uma única coluna, não é um resultado tão esperado, visto que há repetição de valores (registros com documentos iguais). Na referência da Microsoft sobre a função é apresentada uma sintaxe adicional WITHIN GROUP, mas dependendo do nível de compatibilidade do banco de dados, não funciona.
Antes de apresentar uma solução (que o leitor já pode ir direto se quiser), quero relacionar mais um script para ficar bem claro o objetivo da STRING_AGG:
iii) Em cima da mesma lista de estoque de fios quero agrupar os fios por período de entrada no estoque. Este script irá demonstrar por dia o total em peso destes fios e também agrupar quais os códigos dos fios envolvidos. Neste caso, o SELECT ficaria desta forma:
SELECT Data, Sum(Peso) Peso_Total, STRING_AGG(CodFio, ', ') Fios
FROM Swd_Est_Fios
WHERE Data BETWEEN '2025-01-15' AND '2025-01-20'
GROUP BY Data
Figura 3 - Resultado do script para agrupar fios uma data
Mais uma vez temos fios repetidos por dia. O peso total dos fios (Peso_Total) somou todas as caixas de fios participantes, mas o STRING_AGG repetiu estes valores.
Solução
A solução para os casos (ii) e (iii) pode ser resolvida de algumas formas. Para solucionar o caso dos valores repetidos não somente da função STRING_AGG, mas também da antiga forma via WHILE ou mesmo de uma simples variável contendo valores repetidos, decidi construir uma função a qual chamei de FNC_Group_String. Segue o código:
If Exists(SELECT 'X' FROM SysObjects WHERE Upper(Name)='FNC_GROUP_STRING')
DROP FUNCTION dbo.FNC_Group_String
GO
CREATE FUNCTION dbo.FNC_Group_String(@Text varchar(2000), @Separator varchar(5))
RETURNS varchar(2000)
AS
BEGIN
/******************************************************************************
Criado em: 02/04/2026
Criado por: Willian Vieira dos Santos
Objetivo: Agrupar strings similares
Alterado em:
Alterado por:
Alterações:
******************************************************************************/
Declare @New_String varchar(2000) = '', @Word varchar(100), @pos int
SET @Text = RTrim(LTrim(@Text))
SET @pos = CharIndex(@Separator, @Text)
While (@pos <> 0)
Begin
SET @Word = Substring(@Text, 1, @pos-1)
If (RTrim(@Word)<>'') and (CharIndex(@Word, @New_String)=0)
If @New_String = ''
SET @New_String = @Word
Else
SET @New_String = @New_String + @Separator + @Word
SET @Text = RTrim(LTrim(Substring(@Text, @pos+1, len(@Text)-len(@Word))))
SET @pos = CharIndex(@Separator, @Text)
End
If (RTrim(@Word)<>'') and (CharIndex(@Word, @New_String)=0)
SET @New_String = @New_String + @Word
If (@New_String='')
SET @New_String = @Text
RETURN @New_String
END
GO
A função é de código livre, mas peço (por ética) que mantenha a minha autoria. Caso faça melhorias para seu uso, registre no cabeçalho da função suas alterações.
Como exemplo de uso, podemos iniciar com uma simples variável contendo repetidos valores:
-- Exemplo de uso:
Declare @lotes varchar(1000) = 'LOTE89, LOTE89, LOTE89, LOTE90, LOTE90, LOTE90'
Select @lotes Lotes, dbo.Fnc_Group_String(@lotes,', ') Lotes_Agrupados
GO
Figura 4 - Exemplo de uso da FNC_Group_String para um agrupamento de valores repetidos de uma variável (@lotes)
Mas como ficaria nos exemplos (ii) e (iii) ?
ii) Para agrupar os fios e documentos sem repetição, use:
-- com FNC_Group_String
SELECT CodFio, dbo.FNC_Group_String(STRING_AGG(Documento, ', '), ', ')
FROM Swd_Est_Fios
WHERE Data='2025-01-20'
GROUP BY CodFio
Figura 5 - Resultado do script para agrupar documentos (NFs) de cada fio em uma data sem repetição
iii) Para agrupar os fios e documentos sem repetição, use:
-- com FNC_Group_String
SELECT Data, Sum(Peso) Peso_Total, dbo.FNC_Group_String(STRING_AGG(CodFio, ', '), ', ') Fios
FROM Swd_Est_Fios
WHERE Data BETWEEN '2025-01-15' AND '2025-01-20'
GROUP BY Data
Figura 6 - Resultado do script para agrupar fios uma data, mas desta vez sem repetição do código
Referência:
Comentários
Postar um comentário