Como concatenar linhas com STRING_AGG no SQL Server
Juntar vários valores de linhas diferentes numa única string separada por vírgulas é um pedido muito comum em relatórios, listagens e exportações de dados. Durante anos, isto obrigava a truques pouco legíveis com FOR XML PATH. Desde o SQL Server 2017, a função STRING_AGG resolve o mesmo problema numa única expressão, com código mais simples, mais rápido de escrever e fácil de manter.
Pré-requisitos
- SQL Server 2017 ou superior, ou Azure SQL Database (a função STRING_AGG está disponível a partir dessa versão).
- Uma ferramenta de consulta como o SQL Server Management Studio (SSMS) ou o Azure Data Studio.
- Conhecimentos básicos de
SELECTeGROUP BY.
Passo 1: Preparar uma tabela de exemplo
Para acompanhar o exemplo, vamos criar uma pequena tabela com vendas por vendedor. Cada linha representa um produto vendido por uma pessoa, com um identificador numérico associado. Execute o seguinte script numa base de dados de testes — nunca em produção:
CREATE TABLE Vendas (
ProdutoId INT,
Vendedor NVARCHAR(50),
Produto NVARCHAR(50)
);
INSERT INTO Vendas (ProdutoId, Vendedor, Produto) VALUES
(101, 'Ana', 'Teclado'),
(102, 'Ana', 'Rato'),
(103, 'Ana', 'Webcam'),
(201, 'Bruno', 'Monitor'),
(202, 'Bruno', 'Cabo HDMI');
Repare que a Ana tem três produtos e o Bruno tem dois. O nosso objetivo é obter apenas uma linha por vendedor, com todos os produtos reunidos numa só coluna de texto.
Passo 2: Concatenar valores com STRING_AGG
A função STRING_AGG recebe dois argumentos: a expressão a concatenar e o separador a colocar entre os valores. Quando a combinamos com GROUP BY, cada grupo de linhas é reduzido a um único valor de texto:
SELECT
Vendedor,
STRING_AGG(Produto, ', ') AS Produtos
FROM Vendas
GROUP BY Vendedor;
O GROUP BY Vendedor define os grupos e o STRING_AGG junta todos os produtos de cada vendedor, separados por vírgula e espaço. Com apenas quatro linhas de SQL temos o resultado pretendido, sem subconsultas nem funções XML.
Passo 3: Ordenar os valores com WITHIN GROUP
Há um pormenor importante: por omissão, a ordem dos valores concatenados não é garantida pelo motor. Se precisa de um resultado previsível — por exemplo, por ordem alfabética — acrescente a cláusula WITHIN GROUP com um ORDER BY:
SELECT
Vendedor,
STRING_AGG(Produto, ', ') WITHIN GROUP (ORDER BY Produto) AS Produtos
FROM Vendas
GROUP BY Vendedor;
Agora os produtos aparecem sempre pela mesma ordem dentro de cada grupo. Isto é essencial quando o resultado é comparado entre execuções ou usado em testes automáticos.
Passo 4: Concatenar números e ignorar valores nulos
O STRING_AGG só opera sobre texto, por isso colunas numéricas ou de data têm de ser convertidas primeiro com CAST ou CONVERT. Um detalhe muito útil: a função ignora automaticamente os valores NULL, sem deixar separadores a mais. Veja como concatenar os identificadores numéricos:
SELECT
Vendedor,
STRING_AGG(CAST(ProdutoId AS VARCHAR(10)), ';') AS Ids
FROM Vendas
GROUP BY Vendedor;
Se algum ProdutoId fosse NULL, seria simplesmente ignorado, sem criar separadores duplicados nem exigir tratamento manual.
Dica: para gerar uma lista repartida por linhas em vez de valores lado a lado, use CHAR(10) como separador. Muitas ferramentas mostram então cada item na sua própria linha.
Verificar o resultado
Execute a consulta do Passo 3. Deve obter exatamente duas linhas, uma por cada vendedor:
Vendedor Produtos
-------- ---------------------------
Ana Rato, Teclado, Webcam
Bruno Cabo HDMI, Monitor
Se aparecer uma linha por cada produto em vez de uma por vendedor, então falta o GROUP BY na consulta. Se a ordem dos produtos não for a esperada, reveja a cláusula WITHIN GROUP (ORDER BY ...).
Conclusão
Com uma só função, o STRING_AGG substitui soluções complicadas e torna a concatenação de linhas legível e fácil de manter. A partir daqui, experimente combiná-lo com filtros WHERE para concatenar apenas parte dos dados, usá-lo dentro de vistas para relatórios, ou trocar o separador por uma quebra de linha para criar listas verticais. Qual vai ser a primeira consulta do seu projeto onde substitui um FOR XML PATH antigo por STRING_AGG?