Esses dias surgiu uma questão aqui no trabalho que acho que vale o registro…
Me questionaram sobre a possibilidade de concatenar o resultado de diversas linhas em uma única e, imediatamente, me veio o bom e velho FOR XML PATH(”) em mente.
O problema é que o resultado deveria ser concatenado de acordo com o resultado proveniente de uma outra tabela, algo como:
1 |
Status1 (Codigo1, Codigo2, Codigo3) Status 2 (Codigo4) Status 3 (Codigo5) |
A criação da estrutura seria algo assim:
1 2 3 |
create table status (Codigo int identity(1,1) primary key, Descricao varchar(50) ) create table Produto (Codigo int identity(1,1) primary key, Nome varchar(50), Sigla char(5), CodigoProdutoPai int) create table historico (Codigo int identity(1,1) primary key, CodigoPessoa int, CodigoProduto int, CodigoStatus int) |
Abstraiam aqui qualquer boa prática ou regra de normalização… a ideia é mostrar a solução dada. 🙂
Inserindo alguns poucos registros…
1 2 3 |
insert into status values ('Ok'), ('Defeito'), ('Conserto'),('Testar') insert into produto values ('produto 1', 'PROD1',null),('produto 2', 'PROD2',1),('produto 3', 'PROD3',null) insert into historico values (1,1,4),(1,1,2),(1,1,1),(1,2,1),(2,3,1),(1,3,4),(1,3,1) |
Com base nisso, vamos voltar à ideia…
Para cada status existente, eu preciso retornar os produtos que não possuem sub-produtos e que passaram por esse status, deixando os produtos entre parênteses.
A primeira ideia ao ler a descrição pode ser fazer usando um cursor, varrendo registro a registro e concatenando o valor.
Porém, ao invés de pensar por registro, vamos pensar diferente….
A consulta que vai retornar o que eu preciso inicialmente:
1 2 3 4 5 6 7 8 9 10 11 |
SELECT STH.Descricao, PRD.Sigla FROM Produto PRD JOIN Historico HIS ON (HIS.CodigoProduto = PRD.Codigo) join Status STH ON (STH.Codigo = HIS.CodigoStatus) WHERE (HIS.CodigoPessoa = 1) AND (PRD.CodigoProdutoPai IS NULL) ORDER BY descricao |
O retorno será:
Agora, como deixar a sigla em uma linha, por descrição?
Para eu concatenar os registros, faço uso do FOR XML PATH… Assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
SELECT CONVERT(VARCHAR(100), TMP.Descricao + ' ' + TMP.Sigla + ', ' ) as [text()] FROM ( SELECT STH.Descricao, DSS.Sigla FROM Produto DSS JOIN Historico HSS ON (HSS.CodigoProduto = DSS.Codigo) JOIN Status STH ON(STH.Codigo = HSS.CodigoStatus) WHERE (HSS.CodigoPessoa = 1) AND (DSS.CodigoProdutoPai IS NULL) ) AS TMP FOR XML PATH('') |
Resultado:
Concatenou… Mas não era bem isso…
Como o que eu preciso é executar uma vez o FOR XML PATH para cada status existente, vamos usar o Operador APPLY (CROSS APPLY, no meu caso), que realiza esse comportamento, mas aí vamos ter que mudar algumas coisas na query.
Em tempo… Lembrem que a tabela que eu tenho como base é a Status e que é o retorno dos produtos que eu quero concatenar.
A consulta fica assim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
SELECT CONVERT(VARCHAR(100), STH.Descricao + ' (' + ltrim(temp.lista) + ' ) ' ) as [text()] FROM status STH CROSS APPLY ( SELECT CONVERT(VARCHAR(100), ', ' + TMP.Sigla ) as [text()] FROM ( SELECT DSS.Sigla FROM Produto DSS JOIN Historico HSS ON (HSS.CodigoProduto = DSS.Codigo) WHERE (STH.Codigo = HSS.CodigoStatus) AND (HSS.CodigoPessoa = 1) AND (DSS.CodigoProdutoPai IS NULL) ) AS TMP FOR XML PATH('') ) temp (lista) WHERE temp.lista IS NOT NULL |
O resultado:
Opa! Estamos quase lá… Agora, basicamente, o que precisamos é reaplicar o FOR XML PATH nesse resultado e formatar a vírgula que ficou no primeiro parênteses, ficando:
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 |
DECLARE @Resultado varchar(200) SET @Resultado = ( select * from ( SELECT CONVERT(VARCHAR(100), STH.Descricao + ' (' + ltrim(temp.lista) + ' ) ' ) as [text()] FROM status STH CROSS APPLY ( SELECT CONVERT(VARCHAR(100), ', ' + TMP.Sigla ) as [text()] FROM ( SELECT DSS.Sigla FROM Produto DSS JOIN Historico HSS ON (HSS.CodigoProduto = DSS.Codigo) WHERE (STH.Codigo = HSS.CodigoStatus) AND (HSS.CodigoPessoa = 1) AND (DSS.CodigoProdutoPai IS NULL) ) AS TMP FOR XML PATH('') ) temp (lista) WHERE temp.lista IS NOT NULL ) AS tmp2 FOR XML PATH('') ) SELECT replace(@Resultado, '(,','(') |
O que me retorna, finalmente:
Bom, talvez essa não seja a solução mais elegante ou performática… E é aí que começa a brincadeira… Como você faria? Adoraria ver outras soluções para o mesmo caso. 😀
Abraços e até uma próxima!
PS: Caso queira pegar o script com todo o passo a passo para executar na tua máquina, veja no meu gitHub: https://github.com/LoganMerazzi/SQLServerScripts/blob/master/FOR%20XML%20e%20APPLY