domingo, 29 de janeiro de 2012

Procedure para gerar sequencial numérico e/ou alfabético

Pessoal, sei que já faz bastante tempo que não posto alguma novidade no blog. Mas hoje vou compartilhar uma procedure que pode ser útil para geração de sequenciais que deve-se considerar caracteres numéricos e do alfabeto (0-9, A-Z e a-z).

Um exemplo de utilidade, seria gerar uma faixa assim: A01 - A10. Essa procedure lhe retornará A01, A02, A03, A04, A05, A06, A07, A08, A09 e A10, por exemplo.

Vamos ao código fonte da nossa procedure então... os comentários existentes na procedure explicam a lógica e as validações existentes...

create or replace procedure PR_GERASEQUENCIALNS(p_seqIni varchar2,
                                                      p_seqFin varchar2,
                                                      p_considerarAlpha number) is

   v_seqIni varchar2(50);
   v_seqFin varchar2(50);
   v_considerarAlpha boolean;
   v_somaNumericosIni number;
   v_somaNumericosFin number;
begin
   --atualiza variáveis virtuais...
   v_seqIni := p_seqIni;
   v_seqFin := p_seqFin;
   if p_considerarAlpha = 1 then
      v_considerarAlpha := true;
   else 
      v_considerarAlpha := false;
   end if;
   
   --preenche com zeros a frente os sequenciais para terem o mesmo tamanho...
   if length(v_seqIni) > length(v_seqFin) then
      v_seqFin := lpad(v_seqFin, length(v_seqFin), '0');
   elsif length(v_seqIni) < length(v_seqFin) then
      v_seqIni := lpad(v_seqIni, length(v_seqIni), '0');
   end if;
   
   --avalia as restrições dos parâmetros para prosseguir...
   --primeira restriçäo: o posicionamento dos caracteres deve ser identico, ou seja, um maisculo deve corresponder ao maisculo.
   --segunda restrição: também deve-se levar em consideração que se o parâmetro p_considerarAlpha for 0 e for enviado A98 - B01, por exemplo, nenhum 
   --sequencial será impresso, pois 
   for i in 1 .. length(v_seqFin) loop
      --avalia se a sequencia inicial pertence a faixa de númericos da tabela ASCII e os sequencial final não..
      if ascii(substr(v_seqIni, i, 1)) between 48 and 57 and ascii(substr(v_seqFin, i, 1)) not between 48 and 57 then
         raise_application_error(-20001,'Existe inconsistência entre os caracteres da posição '||i||'. Reavalie os sequenciais.');   
      --avalia se a sequencia inicial pertence a faixa de alfabeticos maiscula da tabela ASCII e os sequencial final não..         
      elsif v_considerarAlpha and ascii(substr(v_seqIni, i, 1)) between 65 and 90 and ascii(substr(v_seqFin, i, 1)) not between 65 and 90 then
         raise_application_error(-20001,'Existe inconsistência entre os caracteres da posição '||i||'. Reavalie os sequenciais.');
      --avalia se a sequencia inicial pertence a faixa de alfabeticos minusculos da tabela ASCII e os sequencial final não..         
      elsif v_considerarAlpha and ascii(substr(v_seqIni, i, 1)) between 97 and 122 and ascii(substr(v_seqFin, i, 1)) not between 97 and 122 then
         raise_application_error(-20001,'Existe inconsistência entre os caracteres da posição '||i||'. Reavalie os sequenciais.');
      elsif not(v_considerarAlpha) and ascii(substr(v_seqIni, i, 1)) between 48 and 57 then
         v_somaNumericosIni := ascii(substr(v_seqIni, i, 1));
         v_somaNumericosFin := ascii(substr(v_seqFin, i, 1));
      end if;
   end loop;
   
   if not(v_considerarAlpha) and v_somaNumericosIni > v_somaNumericosFin then
      raise_application_error(-20001,'A sequencia inicial é maior que a final. Faça uma reavaliação.');
   end if;

   --gera o sequencial de numeros de série...
   for i in 1 .. length(v_seqFin) loop
      if (not(v_considerarAlpha) and ascii(substr(v_seqIni, i, 1)) between 48 and 57) or v_considerarAlpha then
         while ascii(substr(v_seqIni, i, 1)) < ascii(substr(v_seqFin, i, 1)) loop
               dbms_output.put_line(v_seqIni);
      
               v_seqIni := FC_INCSERIAL(v_seqIni);
         end loop;
      end if;
   end loop;
   
   dbms_output.put_line(v_seqFin);
end;

Como todos podem ver, para compilar a procedure com êxito, será necessário ter a função FC_INCSERIAL. Então vou compartilhá-la ela em seguida...

create or replace function FC_INCSERIAL(p_seq varchar2,
                                        p_posicao number default 0) return varchar2 is
  r_seq varchar2(50);
  v_posicao number default p_posicao;
  v_asciiCorrente number;
begin
  --tratamento para a posição...
  if v_posicao = 0 then
     v_posicao := length(p_seq);
  end if;
  
  --tratamento para o ascii
  v_asciiCorrente := ascii(substr(p_seq,v_posicao,1));
  
  --tratamento para caracteres númericos...
  if v_asciiCorrente between 48 and 57 then
     if v_asciiCorrente = 57 then
        r_seq := FC_INCSERIAL(substr(p_seq,0,v_posicao-1),v_posicao-1);
        r_seq := r_seq||chr(48);
     else
        r_seq := substr(p_seq,0,v_posicao-1)||chr(v_asciiCorrente+1);
     end if;
  --tratamento para caracteres alfabeticos maiusculos...     
  elsif v_asciiCorrente between 65 and 90 then
     if v_asciiCorrente = 57 then
        r_seq := FC_INCSERIAL(substr(p_seq,0,v_posicao-1),v_posicao-1);
        r_seq := r_seq||chr(65);
     else
        r_seq := substr(p_seq,0,v_posicao-1)||chr(v_asciiCorrente+1);
     end if;
  --tratamento para caracteres alfabeticos minusculos...     
  elsif v_asciiCorrente between 97 and 122 then
     if v_asciiCorrente = 57 then
        r_seq := FC_INCSERIAL(substr(p_seq,0,v_posicao-1),v_posicao-1);
        r_seq := r_seq||chr(97);
     else
        r_seq := substr(p_seq,0,v_posicao-1)||chr(v_asciiCorrente+1);
     end if;
  end if;
  
  return(r_seq);
end FC_INCSERIAL;

O que torna esse código efetivo é a função recursiva. Outro detalhe que acredito que seja interessante é a lógica de utilizar a tabela ASCII para fazer o incremento de numéricos e alfabéticos.

Um exemplo de chamada para ela seria assim...

begin
  PR_GERASEQUENCIALNS('A90','B05',1);
end;

O resultado da execução desse exemplo, resultaria no seguinte output:

A90
A91
A92
A93
A94
A95
A96
A97
A98
A99
B00
B01
B02
B03
B04
B05

Bom pessoal, por hoje é isso... espero que gostem desse pequeno post...