Aproveitando um recente trabalho realizado, gostaria de descrever um relatório que rapidamente desenvolvi em uma base de dados SQL Server. A situação era a seguinte: eu havia importado a estrutura de um produto, e precisava exibir esta estrutura na forma de um relatório. Primeiro, vamos entender o termo “estrutura de um produto”. A empresa em questão é uma fábrica de impressoras de plotagem (1,8m a 3,2m). A ficha técnica da impressora é composta por vários níveis de componentes. Vou ilustrar uma composição:
- Impressora XPTO modelo 1
- Conjunto eletrônica XPTO 1804
- Tomada de embutir 3 pinos
- Cinta nylon média
- Fusível pequeno 10A
- Sensor de temperatura
- Ventilador – secagem
- Espaguete termo retrátil
- Ventilador 120x120x38 c/ rosca
- Conector modular miniatura H89F09
- Blower – vácuo
- Blower ( Siroco ) AD xxxx 24v
- Conector modular miniatura H90F23
- Aquecimento – cabeças
- Resistor Fio 120R x 10W
- Cabo paralelo 0,20mm
- Apoio de silicone 13mm x 4mm
- Terminal KK pequeno importado
O relatório precisa aparecer formatado como está acima (indentado nos níveis). Porém, a quantidade de níveis é indefinida. Desta forma, utilizei o conceito de “recursividade”.
Definições de recursividade na internet:
- Recursão é um método de programação no qual uma função pode chamar a si mesma. O termo é usado de maneira mais geral para descrever o processo de repetição de um objeto de um jeito similar ao que já fora mostrado. ...
pt.wikipedia.org/wiki/Recursividade
- A recursividade na programação de computadores envolve a definição de uma função que pode invocar a si própria. Um exemplo de aplicação da recursividade pode ser encontrado nos analisadores gramaticais recursivos para linguagens de programação. ...
pt.wikipedia.org/wiki/Recursividade_(ciência_da_computação)
- iteratividade; propriedade de formar infinitos elementos à partir de finitos aplicando-se regras lógicas ou matemáticas
pt.wiktionary.org/wiki/Recursividade
- que chama a si mesmo (programa ou função)
pt.wiktionary.org/wiki/Recursivo
Para desenvolver este relatório, eu utilizei este conceito de recursividade. Eu criei um procedimento insere os componentes, e para cada componente que está inserindo, chama a si mesmo para repetir a explosão, independente da quantidade de níveis que houver na estrutura.
O primeiro código que vamos analisar é o que retorna o relatório ao usuário. Ele somente recebe os parâmetros, e chama o procedimento de explosão da estrutura, chamado spcAdhExpEst (procedimento recursivo).
- CREATE proc spcAdhExplodeProduto
- @cd_item char(13)
- as
- /*
- Autor: Fabiano Cores
- Data : 23/12/2010
- Site : http://sqlburger.blogspot.com
- Desenvolvido para demonstrar a utilização de procedimentos
- recursivos no SQL Server. A reprodução deste artigo só é
- permitida se forem mantidos os créditos do autor.
- */
- -- Esta tabela possui papel fundamental na recursividade. Esta
- -- tabela irá existir durante toda a execução deste procedimento
- -- mesmo quando chamamos executamos um segundo procedimento.
- create table #retorno (
- cd_componente char(18) collate database_default null
- , qt_aplicada decimal(10,4) null
- , nivel smallint null
- , ordem int not null identity)
- -- Insiro o item pai, para que o usuário saiba de qual produto
- -- está tirando o relatório
- insert into #retorno (cd_componente, qt_aplicada, nivel)
- select @cd_item, 1, 1
- -- Este é o procedimento recursivo. Ele fará referencia a ele
- -- mesmo, enquanto preenche a tabela #retorno
- exec spcAdhExpEst @cd_item
- -- Aqui, eu pego os registros da tabela #retorno, e retorno
- -- ao software .NET, para exibição ao usuário.
- select convert(char(18), space((nivel-1) * 2) + a.cd_Componente) as 'Código'
- , convert(char(65), space((nivel-1) * 2) + b.descricao) as 'Descrição'
- , a.qt_aplicada 'Quantidade'
- , a.nivel -1 as 'Nível'
- from #retorno a, item b
- where a.cd_componente = b.cd_item
- order by a.ordem
- --
Este é o código do procedimento recursivo. Ele insere na tabela #retorno, que foi criada pelo seu procedimento antecessor (spcAdhExplodeEstrutura), e faz referência a si mesma:
- --
- CREATE proc spcAdhExpEst
- @cd_item char(13)
- as
- /*
- Autor: Fabiano Cores
- Data : 23/12/2010
- Site : http://sqlburger.blogspot.com
- Desenvolvido para demonstrar a utilização de procedimentos
- recursivos no SQL Server. A reprodução deste artigo só é
- permitida se forem mantidos os créditos do autor.
- */
- declare @vcd_componente char(13)
- declare @vqt_aplicada decimal(19,4)
- -- Insere os componentes simples (não possuem estrutura abaixo)
- insert into #retorno (cd_componente, qt_aplicada, nivel)
- select a.cd_componente, a.qt_aplicada, @@nestlevel
- from processoitem a, item b
- where a.cd_item = @cd_item
- and a.cd_componente = b.cd_item
- and not exists (select 0
- from processoitem c
- where c.cd_item = a.cd_componente)
- order by b.descricao
- -- Aqui é montado um cursor nos itens complexos (que possuem
- -- estrutura). É utilizado um cursor por que para cada item
- -- que possui estrutura, eu vou fazer a chamada recursiva.
- declare c1 cursor local fast_forward read_only for
- select a.cd_componente, a.qt_aplicada
- from processoitem a, item b
- where a.cd_item = @cd_item
- and a.cd_componente = b.cd_item
- and exists (select 0
- from processoitem c
- where c.cd_item = a.cd_componente)
- order by b.descricao
- open c1
- while 1 = 1
- begin
- fetch next from c1 into @vcd_componente, @vqt_aplicada
- if @@fetch_status <> 0 break
- insert into #retorno (cd_componente, qt_aplicada, nivel)
- select @vcd_componente, @vqt_aplicada, @@nestlevel
- -- Chamada recursiva (executando ele mesmo)
- exec spcAdhExpEst @vcd_componente
- end
- close c1
- deallocate c1
- --
Detalhes importantes que devem ser observados:
1. A tabela temporária foi criada no primeiro procedimento. Se ela fosse criada no procedimento recursivo, daria erro, pois ele tentaria criar mais de uma vez a mesma tabela, desta forma, ela sendo criada apenas uma vez pelo procedimento pai, não haverá problemas.
2. O procedimento filho (spcAdhExpEst) consegue enxergar a tabela #retorno.
3. A variável @@nestlevel é uma variável de ambiente, que retorna o nível de aninhamento* da execução do procedimento. Isto é uma forma de saber quantas vezes o procedimento já foi disparado por ele mesmo (na primeira execução, @@nestlevel é igual a 0). Aproveitei esta variável para incluir no relatório a que nível o componente pertence, e também a utilizei como parâmetro para adicionar espaços à esquerda do código e da descrição, para melhorar visualmente a identificação dos níveis na leitura do relatório.
*Definições de aninhar na internet:
- aninhado - Quando se tem um objeto dentro do outro, como uma tabela, por exemplo , você possui um aninhamento , ou seja, um objeto funcionando como ninho para outro objeto.
www.juliobattisti.com.br/tutoriais/keniareis/dicionarioinfo001.asp
Eu estou postando esta dica por que hoje tive uma experiência muito desagradável… um certo programador teve a capacidade de fazer 9 cursores (loops), um dentro do outro, para tratar possíveis 9 níveis do produto. Além de ser um código absurdamente mal pensado, é gigantesco, lento, péssimo de realizar manutenção, entre outros comentários que não vêem ao caso. E como padrão, quando pego código assim, tenho o hábito de selecionar tudo, apagar, refazer… e claro, compartilhar!