Pesquisar este blog

quinta-feira, 23 de dezembro de 2010

Um caso de recursividade

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:

 

  1. Impressora XPTO modelo 1
    1. Conjunto eletrônica XPTO 1804
      1. Tomada de embutir 3 pinos
      2. Cinta nylon média
      3. Fusível pequeno 10A
      4. Sensor de temperatura
      5. Ventilador – secagem
        1. Espaguete termo retrátil
        2. Ventilador 120x120x38 c/ rosca
        3. Conector modular miniatura H89F09
      6. Blower – vácuo
        1. Blower ( Siroco ) AD xxxx 24v
        2. Conector modular miniatura H90F23
      7. Aquecimento – cabeças
        1. Resistor Fio 120R x 10W
        2. Cabo paralelo 0,20mm
        3. Apoio de silicone 13mm x 4mm
        4. 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:

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).

Code Snippet
  1.  
  2. CREATE proc spcAdhExplodeProduto
  3.   @cd_item char(13)
  4.   
  5. as
  6.  
  7.   /*
  8.    Autor: Fabiano Cores
  9.    Data : 23/12/2010
  10.    Site : http://sqlburger.blogspot.com
  11.    Desenvolvido para demonstrar a utilização de procedimentos
  12.    recursivos no SQL Server. A reprodução deste artigo só é
  13.    permitida se forem mantidos os créditos do autor.
  14.    */
  15.  
  16.   -- Esta tabela possui papel fundamental na recursividade. Esta
  17.   -- tabela irá existir durante toda a execução deste procedimento
  18.   -- mesmo quando chamamos executamos um segundo procedimento.
  19.   create table #retorno (
  20.     cd_componente char(18) collate database_default null
  21.   , qt_aplicada decimal(10,4) null
  22.   , nivel smallint null
  23.   , ordem int not null identity)
  24.   
  25.   -- Insiro o item pai, para que o usuário saiba de qual produto
  26.   -- está tirando o relatório
  27.   insert into #retorno (cd_componente, qt_aplicada, nivel)
  28.     select @cd_item, 1, 1
  29.   
  30.   -- Este é o procedimento recursivo. Ele fará referencia a ele
  31.   -- mesmo, enquanto preenche a tabela #retorno
  32.   exec spcAdhExpEst @cd_item
  33.  
  34.   -- Aqui, eu pego os registros da tabela #retorno, e retorno
  35.   -- ao software .NET, para exibição ao usuário.
  36.   select convert(char(18), space((nivel-1) * 2) + a.cd_Componente) as 'Código'
  37.        , convert(char(65), space((nivel-1) * 2) + b.descricao) as 'Descrição'
  38.        , a.qt_aplicada 'Quantidade'
  39.        , a.nivel -1 as 'Nível'
  40.     from #retorno a, item b
  41.    where a.cd_componente = b.cd_item
  42.    order by a.ordem
  43.  
  44. --


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:

Code Snippet
  1. --
  2.  
  3. CREATE proc spcAdhExpEst
  4.   @cd_item char(13)
  5.   
  6. as
  7.  
  8.    /*
  9.    Autor: Fabiano Cores
  10.    Data : 23/12/2010
  11.    Site : http://sqlburger.blogspot.com
  12.    Desenvolvido para demonstrar a utilização de procedimentos
  13.    recursivos no SQL Server. A reprodução deste artigo só é
  14.    permitida se forem mantidos os créditos do autor.
  15.    */
  16.  
  17.   declare @vcd_componente char(13)
  18.   declare @vqt_aplicada decimal(19,4)
  19.  
  20.   -- Insere os componentes simples (não possuem estrutura abaixo)
  21.   insert into #retorno (cd_componente, qt_aplicada, nivel)
  22.     select a.cd_componente, a.qt_aplicada, @@nestlevel
  23.       from processoitem a, item b
  24.      where a.cd_item = @cd_item
  25.        and a.cd_componente = b.cd_item
  26.        and not exists (select 0
  27.                          from processoitem c
  28.                         where c.cd_item = a.cd_componente)
  29.      order by b.descricao
  30.  
  31.   -- Aqui é montado um cursor nos itens complexos (que possuem
  32.   -- estrutura). É utilizado um cursor por que para cada item
  33.   -- que possui estrutura, eu vou fazer a chamada recursiva.
  34.   declare c1 cursor local fast_forward read_only for
  35.     select a.cd_componente, a.qt_aplicada
  36.       from processoitem a, item b
  37.      where a.cd_item = @cd_item
  38.        and a.cd_componente = b.cd_item
  39.        and     exists (select 0
  40.                          from processoitem c
  41.                         where c.cd_item = a.cd_componente)
  42.      order by b.descricao
  43.      
  44.   open c1
  45.   
  46.   while 1 = 1
  47.   begin
  48.     fetch next from c1 into @vcd_componente, @vqt_aplicada
  49.     if @@fetch_status <> 0 break
  50.     
  51.     insert into #retorno (cd_componente, qt_aplicada, nivel)
  52.       select @vcd_componente, @vqt_aplicada, @@nestlevel
  53.       
  54.     -- Chamada recursiva (executando ele mesmo)
  55.     exec spcAdhExpEst @vcd_componente
  56.     
  57.   end
  58.   close c1
  59.   deallocate c1
  60.  
  61. --

 

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:

 

image

 

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!

Um comentário: