STRING_AGG e função de melhoria FNC_Group_String

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

Postagens mais visitadas deste blog

Importação de arquivos CSV

Performance SQL Server - tempdb

Tabelas temporárias - Local x Global