Neste post vou falar sobre um recurso bem requisitado para a Microsoft, e que ela ainda não criou nativamente no SQL Server, que é concatenação de dados utilizando agrupamentos, já presente em outros SGBDs como MySQL (GROUP_CONCAT), Oracle (XMLAGG) e PostgreeSQL (STRING_AGG ou ARRAY_TO_STRING(ARRAY_AGG())).
Muita gente acha que esse recurso é a função CONCAT(), introduzida no SQL Server 2012, mas ela permite apenas a concatenação de várias colunas de uma linha em uma coluna e não a concatenação de várias linhas em uma coluna.
A concatenação de colunas em strings agrupadas, consiste em transformar linhas em uma string concatenada, de forma que você possa agrupar os valores por algum campo, e a outra coluna seja concatenada, conforme exemplificado abaixo:
Para criar a nossa tabela de testes, utilize os comandos abaixo:
Como concatenar colunas em strings agrupadas utilizando COALESCE
Transact-SQL
1
2
3
4
5
6
7
8
DECLARE@NomesVARCHAR(MAX)
SELECT
@Nomes=COALESCE(@Nomes+', ','')+Descricao
FROM
dbo.Teste_XML
SELECT@Nomes
Como podemos observar acima, utilizando a função COALESCE, precisamos armazenar os resultados sempre em uma variável, e por este motivo, não conseguimos obter os resultados de forma agrupada por categoria, conforme precisamos. Se você quer apenas converter colunas em uma string, sem agrupar, essa solução irá te atender, mas não ao que precisamos para esse post.
STUFF + FOR XML PATH
Como concatenar colunas em strings agrupadas utilizando STUFF + FOR XML PATH
Uma forma prática e performática de se resolver esse problema, é utilizar o FOR XML PATH e o STUFF para realizarmos a concatenação agrupada, de forma que o resultado é exatamente aquilo que esperávamos:
Mais uma solução que nos permitiu gerar os dados conforme nossa necessidade demanda.
LOOPING COM CURSOR
Como concatenar colunas em strings agrupadas utilizando LOOPING com CURSOR
Transact-SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
DECLARE@TabelaTABLE(
CategoriaVARCHAR(MAX),
DescricoesVARCHAR(MAX)
)
INSERT@Tabela (
Categoria,
Descricoes
)
SELECT
Categoria,
''
FROM
dbo.Teste_Group_Concat
GROUPBY
Categoria
DECLARE
@CategoriaVARCHAR(MAX),
@DescricaoVARCHAR(MAX),
@DescricoesVARCHAR(MAX)
DECLAREcCURSORLOCALFAST_FORWARD
FOR
SELECT
Categoria,
Descricao
FROM
dbo.Teste_Group_Concat
ORDERBY
Categoria,
Descricao
OPENc
FETCHcINTO@Categoria,@Descricao
WHILE@@FETCH_STATUS=0
BEGIN
UPDATE@Tabela
SETDescricoes+=', '+@Descricao
WHEREISNULL(Categoria,'')=ISNULL(@Categoria,'')
FETCHcINTO@Categoria,@Descricao
END
CLOSEc
DEALLOCATEc
SELECT
Categoria,
Descricoes=STUFF(Descricoes,1,1,'')
FROM
@Tabela
ORDERBY
Categoria
Essa solução também atendeu ao que precisamos, mas assim como todo looping, não é performático. Eu particularmente abomino a criação de cursores (a menos que você AINDA esteja no SQL Server 2000), em último caso, eu prefiro utilizar WHILE, mas temos soluções melhores aqui nesse post.
LOOPING COM WHILE
Como concatenar colunas em strings agrupadas utilizando LOOPING com WHILE
Essa solução utiliza da técnica de recursividade para resolver nosso problema sem precisar de looping. Embora não seja muito fácil de entender a princípio, é uma solução prática e muito interessante.
FUNÇÃO SCALAR (UDF)
Como concatenar colunas em strings agrupadas utilizando Função Scalar (UDF)
Transact-SQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
CREATEFUNCTIONdbo.fncConcatCategoria (
@Ds_CategoriaVARCHAR(MAX)
)
RETURNSVARCHAR(MAX)
WITHSCHEMABINDING
AS
BEGIN
DECLARE@RetornoVARCHAR(MAX);
SELECT
@Retorno=COALESCE(@Retorno+', ','')+Descricao
FROM
dbo.Teste_Group_Concat
WHERE
ISNULL(Categoria,'')=ISNULL(@Ds_Categoria,'')
ORDERBY
Descricao
RETURN@Retorno
END
GO
SELECT
Categoria,
dbo.fncConcatCategoria(Categoria)ASDescricao
FROM
dbo.Teste_Group_Concat
GROUPBY
Categoria
Função bem útil e após criada, se torna muito prática de utilizar. O problema dessa solução é que ela não é muito performática e exige que você crie uma função para cada tabela que você precise realizar essa solução, ou seja, acaba não sendo muito genérica e você precisaria criar várias funções que fazem praticamente a mesma coisa na sua base de dados.
SQL CLR (C#)
Como concatenar colunas em strings agrupadas utilizando SCL CLR (C#)
Minha solução favorita. Prática, extremamente rápida, genérica e resolve nosso problema. Não sabe o que é CLR ou sabe, mas não sabe como implementar na sua base de dados? Saiba mais acessando meu post Introdução ao SQL CLR (Common Language Runtime) no SQL Server.
Considerações de Performance
Vamos medir agora as nossas soluções e descobrir quais são as mais rápidas e as mais lentas.
CLR
STUFF + FOR XML PATH
UPDATE
CTE Recursivo
Função scalar UDF
Loop While
Loop com Cursor
De cara já podemos eliminar os loopings, que foram muito piores.. O que acontece se a gente inserir mais registros? Tipo umas 850.000 linhas.. Como será a performance?
CLR
STUFF + FOR XML PATH
Como nenhum outro método conseguiu terminar de processar em menos de 1 minuto, apesar de ter usado 850.000 linhas com o CLR e o FOR XML PATH, vou utilizar apenas 158.000 linhas (27%) para o restante dos métodos pra tentar analisar os resultados.. Vamos ver.
UPDATE
Até que o tempo com o UPDATE não foi tão ruim.. 5.7s para 158.000 linhas é um tempo razoável.
Função scalar UDF
CTE Recursivo
Bom, tentei esperar pelo CTE Recursivo.. Até fui almoçar e deixei rodando aqui, mas depois de 2 horas e 22 minutos não deu pra esperar mais terminar de processar as 158 mil linhas (lembrando que o CLR processou 850.000 em 3s e o FOR XML em 1.5s)..
Aparentemente, nosso vencedor foi o FOR XML PATH, mas se formos analisar os resultados, o CLR entregou o resultado correto, mesmo com toda essa quantidade de linhas absurda:
Já o FOR XML PATH… Acabou se perdendo e os resultados não ficaram na ordem correta.
Sendo assim, a solução que eu indico como a melhor de todas para esse tipo de situação, é o SQL CLR!
É isso aí, pessoal!
Obrigado pela visita e até o próximo post.
sql server clr function stuff for xml path recursive cte convert columns rows grouped concat concatenation string como converter concatenar colunas em string
sql server clr function stuff for xml path recursive cte convert columns rows grouped concat concatenation string como converter concatenar colunas em string
Parabéns Dirceu, por compartilhar essa informação tão valiosa, aproveito para acrescentar ao seu tópico um ‘plugin’ que encontrei após o seu blog, com quatro funções já prontas para o clr, bastando apenas adicionar ao banco.
http://groupconcat.codeplex.com/
André, obrigado pelo elogio e por compartilhar esse link. Vou dar uma olhada nele para aprender mais sobre.
Obrigado pela resposta, Dirceu!
Não me lembrava mais do SQL Sentry. Usei uma versão há muito tempo atrás, mas acho que nem era como essa. Já estou instalando aqui para avaliar de novo.
Valeu mesmo!
Dirceu,
Gostaria de dar os parabéns pelo artigo! Didático e bem prático, ficou tranquilo de entender e de comparar as diversas soluções.
Eu particularmente venho usando a solução do FOR XML com STUFF, que me atendeu bem até agora. Mas a solução SQL CLR ficou muito boa mesmo! Caso a se pensar para próximas implementações disso.
Só uma pergunta: você usou o SQL Server ou outro banco de dados? Na verdade, queria saber qual a ferramenta que você usou nessas comparações gráficas, que achei muito legal.
Obrigado!
Laércio
Laércio,
Boa tarde.
Muito obrigado pela visita e pelo feedback ?
Sobre a sua pergunta, o banco de dados é o SQL Server sim, mas a ferramenta que eu uso para analisar o plano de execução é o SQL Sentry Plan Explorer FREE (Vou fazer um post sobre essa ferramenta futuramente).
Na minha opinião, essa ferramenta oferece uma melhor visualização dos dados, além de alguns recursos para manipular as informações que o Management Studio do SQL Server não dispõe, mas daria para retornar algo parecido pelo próprio Management Studio sim.
Qualquer dúvida, é só falar.