SEARCH e SEARCH ALL: Pesquisando em tabelas internas
Como comentei em outro artigo que publiquei por aqui, tabela interna é o nome dado pelo COBOL a um conjunto homogêneo de dados que se repetem. São semelhantes aos arrays que existem em outras linguagens, podem ser criadas com tamanhos variáveis, mas não podem ser expandidas dinamicamente em tempo de execução.
Neste artigo aqui vamos mostrar os comandos disponíveis no COBOL para localizar ocorrências dentro de uma tabela interna e fazer algumas considerações sobre performance.
Para isso, vamos trabalhar com uma tabela composta por código da unidade da federação e nome do Estado:
01 conteudo.
03 filler pic x(22) value "ACACRE ".
03 filler pic x(22) value "ALALAGOAS ".
03 filler pic x(22) value "AMAMAZONAS ".
03 filler pic x(22) value "APAMAPA ".
03 filler pic x(22) value "BABAHIA ".
03 filler pic x(22) value "CECEARA ".
03 filler pic x(22) value "DFDISTRITO FEDERAL ".
03 filler pic x(22) value "ESESPIRITO SANTO ".
03 filler pic x(22) value "FNFERNANDO DE NORONHA ".
03 filler pic x(22) value "GOGOIAS ".
03 filler pic x(22) value "MAMARANHAO ".
03 filler pic x(22) value "MGMINAS GERAIS ".
03 filler pic x(22) value "MSMATO GROSSO DO SUL ".
03 filler pic x(22) value "MTMATO GROSSO ".
03 filler pic x(22) value "PAPARA ".
03 filler pic x(22) value "PBPARAIBA ".
03 filler pic x(22) value "PEPERNAMBUCO ".
03 filler pic x(22) value "PIPIAUI ".
03 filler pic x(22) value "PRPARANA ".
03 filler pic x(22) value "RJRIO DE JANEIRO ".
03 filler pic x(22) value "RNRIO GRANDE DO NORTE ".
03 filler pic x(22) value "RORONDONIA ".
03 filler pic x(22) value "RRRORAIMA ".
03 filler pic x(22) value "RSRIO GRANDE DO SUL ".
03 filler pic x(22) value "SCSANTA CATARINA ".
03 filler pic x(22) value "SESERGIPE ".
03 filler pic x(22) value "SPSAO PAULO ".
03 filler pic x(22) value "TOTOCANTINS ".
01 filler redefines conteudo.
03 tabela occurs 28 indexed by indice.
05 cd-uf pic x(02).
05 nm-uf pic x(20).
Nos exemplos a seguir vamos procurar o nome do Estado associado a um determinado código de unidade federativa.
Se não houvesse o comando SEARCH
A pesquisa em tabelas internas pode ser realizada com comandos básicos do COBOL. Um PERFORM para o loop e um IF para localizar o dado que se procura são suficientes.
O exemplo abaixo mostra esse tipo de abordagem:
identification division.
program-id. gtc011.
data division.
working-storage section.
*> Variaveis de trabalho
77 cd-uf-a-procurar pic x(02) value spaces.
77 nm-uf-encontrado pic x(20) value spaces.
77 subscrito pic 9(02) value zeros.
*> Tabela interna
01 conteudo.
03 filler pic x(22) value "ACACRE ".
03 filler pic x(22) value "ALALAGOAS ".
03 filler pic x(22) value "AMAMAZONAS ".
03 filler pic x(22) value "APAMAPA ".
03 filler pic x(22) value "BABAHIA ".
03 filler pic x(22) value "CECEARA ".
03 filler pic x(22) value "DFDISTRITO FEDERAL ".
03 filler pic x(22) value "ESESPIRITO SANTO ".
03 filler pic x(22) value "FNFERNANDO DE NORONHA ".
03 filler pic x(22) value "GOGOIAS ".
03 filler pic x(22) value "MAMARANHAO ".
03 filler pic x(22) value "MGMINAS GERAIS ".
03 filler pic x(22) value "MSMATO GROSSO DO SUL ".
03 filler pic x(22) value "MTMATO GROSSO ".
03 filler pic x(22) value "PAPARA ".
03 filler pic x(22) value "PBPARAIBA ".
03 filler pic x(22) value "PEPERNAMBUCO ".
03 filler pic x(22) value "PIPIAUI ".
03 filler pic x(22) value "PRPARANA ".
03 filler pic x(22) value "RJRIO DE JANEIRO ".
03 filler pic x(22) value "RNRIO GRANDE DO NORTE ".
03 filler pic x(22) value "RORONDONIA ".
03 filler pic x(22) value "RRRORAIMA ".
03 filler pic x(22) value "RSRIO GRANDE DO SUL ".
03 filler pic x(22) value "SCSANTA CATARINA ".
03 filler pic x(22) value "SESERGIPE ".
03 filler pic x(22) value "SPSAO PAULO ".
03 filler pic x(22) value "TOTOCANTINS ".
01 filler redefines conteudo.
03 tabela occurs 28.
05 cd-uf pic x(02).
05 nm-uf pic x(20).
procedure division.
inicio.
display "Informe um código da federacao: "
accept cd-uf-a-procurar from console
perform varying subscrito from 1 by 1 until subscrito > 28
if cd-uf(subscrito) = cd-uf-a-procurar
move nm-uf(subscrito) to nm-uf-encontrado
exit perform
end-if
end-perform
if nm-uf-encontrado = spaces
display "UF nao encontrada"
else
display cd-uf-a-procurar "=" nm-uf-encontrado
end-if
stop run.
Este programa permite que o usuário digite um código de unidade federativa, busca esse código na tabela interna e exibe o nome do Estado correspondente. Se o código não for encontrado, exibe a mensagem “UF não encontrada”.
As variáveis de trabalho definidas no programa servem para armazenar o código da UF que o usuário deseja procurar (cd-uf-a-procurar), o nome do Estado encontrado (nm-uf-encontrado) e um subscrito que será usado para varrer todas as ocorrências da tabela.
As linhas abaixo mostram o programa em execução:
[aeisxpad ~/cbl]$ gtc011 Informe um código da federacao: RJ RJ=RIO DE JANEIRO [aeisxpad ~/cbl]$ gtc011 Informe um código da federacao: xx UF nao encontrada
Search sequencial
O comando SEARCH faz basicamente o que o PERFORM VARYING e o IF fizeram no programa anterior. A única diferença é que um índice precisa ser criado na tabela interna, e esse índice precisa ser inicializado antes da execução do SEARCH.
A tabela ficaria assim:
01 filler redefines conteudo.
03 tabela occurs 28 indexed by indice.
05 cd-uf pic x(02).
05 nm-uf pic x(20).
E na PROCEDURE DIVISION poderíamos eliminar o PERFORM e substituir pelo SEARCH:
procedure division.
inicio.
display "Informe um código da federacao: "
accept cd-uf-a-procurar from console
set indice to 1
search tabela varying indice
at end
display "UF nao encontrada"
when cd-uf(indice) = cd-uf-a-procurar
display cd-uf-a-procurar "=" nm-uf(indice)
end-search
stop run.
Alguns comentários sobre o comando SEARCH:
- A pesquisa começa na ocorrência que corresponde ao valor que está no índice. Por isso temos que inicializá-lo com 1.
- O único comando que se pode usar para atribuição de valor a índices é SET. Por isso escrevemos SET 1 TO INDICE, e não MOVE 1 TO INDICE.
- O objeto do comando deve ser aquele que foi declarado com a cláusula OCCURS. No exemplo acima não seria possível, por exemplo, fazer um SEARCH em “CD-UF”, porque esse item elementar não possui a cláusula OCCURS.
- A frase WHEN estabelece a condição para a que pesquisa seja considerada bem sucedida. Nesse exemplo, ela será bem sucedida quando um determinado código de UF da tabela for igual ao código informado pelo usuário. Por isso, WHEN CD-UF(INDICE) = CD-UF-A-PROCURAR.
- O comando termina quando a condição do WHEN é considerada verdadeira.
- É possível definir mais de uma cláusula WHEN, com diferentes condições. O SEARCH irá parar quando qualquer uma das condições for considerada verdadeira.
- A frase AT END só é executada se a pesquisa chegar ao fim da tabela e nenhuma condição WHEN foi considerada verdadeira.
Search binário
O search binário produz uma pesquisa mais rápida do que o search sequencial. Sintaticamente a mudança é pequena: inserir a palavra ALL após o comando e remover a cláusula VARYING, como no exemplo abaixo:
search all tabela
at end
display "UF nao encontrada"
when cd-uf(indice) = cd-uf-a-procurar
display cd-uf-a-procurar "=" nm-uf(indice)
end-search
Apesar dessa pequena diferença, o algoritmo implementado pelo compilador é muito mais eficiente. O fluxo abaixo mostra como esse processo funciona na tabela que estamos usando em nossos exemplos:
Repare que no SEARCH ALL nem todas as ocorrências da tabela serão percorridas. O algoritmo avalia se, na posição corrente do índice, o valor da chave é igual, maior ou menor do que o valor procurado. E vai ajustando os valores dos ponteiros em função do resultado dessas comparações. Os ponteiros são internos e não precisam ser declarados pelo programa.
O efeito disso é que, enquanto no pior cenário do search sequencial toda a tabela é percorrida, no search binário apenas uma fração das ocorrências da tabela precisa ser testada.
Mas para que o SEARCH ALL funcione existem duas condições.
- É preciso declarar uma chave para a tabela através das opções ASCENDING ou DESCENDING da cláusula OCCURS. A tabela do nosso exemplo ficaria assim:
*> Tabela interna
01 conteudo.
03 filler pic x(22) value "ACACRE ".
03 filler pic x(22) value "ALALAGOAS ".
03 filler pic x(22) value "AMAMAZONAS ".
03 filler pic x(22) value "APAMAPA ".
03 filler pic x(22) value "BABAHIA ".
03 filler pic x(22) value "CECEARA ".
03 filler pic x(22) value "DFDISTRITO FEDERAL ".
03 filler pic x(22) value "ESESPIRITO SANTO ".
03 filler pic x(22) value "FNFERNANDO DE NORONHA ".
03 filler pic x(22) value "GOGOIAS ".
03 filler pic x(22) value "MAMARANHAO ".
03 filler pic x(22) value "MGMINAS GERAIS ".
03 filler pic x(22) value "MSMATO GROSSO DO SUL ".
03 filler pic x(22) value "MTMATO GROSSO ".
03 filler pic x(22) value "PAPARA ".
03 filler pic x(22) value "PBPARAIBA ".
03 filler pic x(22) value "PEPERNAMBUCO ".
03 filler pic x(22) value "PIPIAUI ".
03 filler pic x(22) value "PRPARANA ".
03 filler pic x(22) value "RJRIO DE JANEIRO ".
03 filler pic x(22) value "RNRIO GRANDE DO NORTE ".
03 filler pic x(22) value "RORONDONIA ".
03 filler pic x(22) value "RRRORAIMA ".
03 filler pic x(22) value "RSRIO GRANDE DO SUL ".
03 filler pic x(22) value "SCSANTA CATARINA ".
03 filler pic x(22) value "SESERGIPE ".
03 filler pic x(22) value "SPSAO PAULO ".
03 filler pic x(22) value "TOTOCANTINS ".
01 filler redefines conteudo.
03 tabela occurs 28 ascending key cd-uf indexed by indice.
05 cd-uf pic x(02).
05 nm-uf pic x(20).
2. O conteúdo da tabela precisa estar ordenado de acordo com o que foi informado. Nesse exemplo, o conteúdo precisa estar classificado em ordem ascendente pelo campo cd-uf.
Duas situações podem acontecer se uma das duas regras acima não forem respeitadas: (1) Se não houver opção ASCENDING/DESCENDING na cláusula OCCURS e o programa tentar executar um SEARCH ALL, o compilador exibirá uma mensagem de erro durante a compilação; (2) Se houver opção ASCENDING/DESCENDING, mas o conteúdo não estiver ordenado de acordo com a opção, o programa vai compilar mas os resultados da pesquisa provavelmente serão incorretos.
Comparações de performance
Para mostrar a diferença de performance das três abordagens eu compilei o programa abaixo no GnuCOBOL e o executei no Linux. Ele é apenas uma versão adaptada dos três exemplos que vimos antes.
identification division.
program-id. gtc014.
data division.
working-storage section.
*> Variaveis de trabalho
77 cd-uf-a-procurar pic x(02) value spaces.
77 nm-uf-encontrado pic x(20) value spaces.
77 hora-inicio pic 9(08) value zeros.
77 hora-termino pic 9(08) value zeros.
77 subscrito pic 9(02) value zeros.
*> Constantes
78 LOOP value 1000000.
*> Tabela interna
01 conteudo.
03 filler pic x(22) value "ACACRE ".
03 filler pic x(22) value "ALALAGOAS ".
03 filler pic x(22) value "AMAMAZONAS ".
03 filler pic x(22) value "APAMAPA ".
03 filler pic x(22) value "BABAHIA ".
03 filler pic x(22) value "CECEARA ".
03 filler pic x(22) value "DFDISTRITO FEDERAL ".
03 filler pic x(22) value "ESESPIRITO SANTO ".
03 filler pic x(22) value "FNFERNANDO DE NORONHA ".
03 filler pic x(22) value "GOGOIAS ".
03 filler pic x(22) value "MAMARANHAO ".
03 filler pic x(22) value "MGMINAS GERAIS ".
03 filler pic x(22) value "MSMATO GROSSO DO SUL ".
03 filler pic x(22) value "MTMATO GROSSO ".
03 filler pic x(22) value "PAPARA ".
03 filler pic x(22) value "PBPARAIBA ".
03 filler pic x(22) value "PEPERNAMBUCO ".
03 filler pic x(22) value "PIPIAUI ".
03 filler pic x(22) value "PRPARANA ".
03 filler pic x(22) value "RJRIO DE JANEIRO ".
03 filler pic x(22) value "RNRIO GRANDE DO NORTE ".
03 filler pic x(22) value "RORONDONIA ".
03 filler pic x(22) value "RRRORAIMA ".
03 filler pic x(22) value "RSRIO GRANDE DO SUL ".
03 filler pic x(22) value "SCSANTA CATARINA ".
03 filler pic x(22) value "SESERGIPE ".
03 filler pic x(22) value "SPSAO PAULO ".
03 filler pic x(22) value "TOTOCANTINS ".
01 filler redefines conteudo.
03 tabela occurs 28 ascending key cd-uf indexed by indice.
05 cd-uf pic x(02).
05 nm-uf pic x(20).
procedure division.
0-inicio.
accept hora-inicio from time
perform LOOP times
move "FN" to cd-uf-a-procurar
perform 1-pesquisa-com-perform varying subscrito from 1 by 1 until subscrito > 28
move "RJ" to cd-uf-a-procurar
perform 1-pesquisa-com-perform varying subscrito from 1 by 1 until subscrito > 28
end-perform
accept hora-termino from time
display "Pesquisa com PERFORM VARYING..: de " hora-inicio " a " hora-termino
accept hora-inicio from time
perform LOOP times
move "FN" to cd-uf-a-procurar
perform 2-pesquisa-search-sequencial
move "RJ" to cd-uf-a-procurar
perform 2-pesquisa-search-sequencial
end-perform
accept hora-termino from time
display "Pesquisa com SEARCH SEQUENCIAL: de " hora-inicio " a " hora-termino
accept hora-inicio from time
perform LOOP times
move "FN" to cd-uf-a-procurar
perform 3-pesquisa-search-binario
move "RJ" to cd-uf-a-procurar
perform 3-pesquisa-search-binario
end-perform
accept hora-termino from time
display "Pesquisa com SEARCH BINARIO...: de " hora-inicio " a " hora-termino
stop run.
1-pesquisa-com-perform.
if cd-uf(subscrito) = cd-uf-a-procurar
exit paragraph
end-if.
2-pesquisa-search-sequencial.
set indice to 1
search tabela varying indice
at end
display "UF nao encontrada"
when cd-uf(indice) = cd-uf-a-procurar
exit paragraph
end-search.
3-pesquisa-search-binario.
set indice to 1
search all tabela
at end
display "UF nao encontrada"
when cd-uf(indice) = cd-uf-a-procurar
exit paragraph
end-search.
Ele é dividido em três blocos. Cada bloco executa uma das abordagens ou soluções que vimos até aqui: pesquisa com PERFORM VARYING, pesquisa com SEARCH SEQUENCIAL e pesquisa com SEARCH BINARIO.
Todos os blocos procuram duas unidades da federação fixas: FN (Fernando de Noronha) e RJ (Rio de Janeiro), que dividem a tabela mais ou menos em três partes iguais.
Cada bloco é executado 1.000.000 de vezes. A quantidade de iterações é definida por uma constante chamada LOOP que foi definida na WORKING-STORAGE.
Guardamos a hora do sistema, com centésimos de segundo, antes e depois da execução de cada bloco. As horas de início e término são exibidas ao final do bloco.
O resultado foi o seguinte…
[aeisxpad ~/cbl]$ gtc014 Pesquisa com PERFORM VARYING..: de 09225618 a 09230497 Pesquisa com SEARCH SEQUENCIAL: de 09230497 a 09230518 Pesquisa com SEARCH BINARIO...: de 09230518 a 09230525
E os tempos obtidos foram:
- PERFORM VARYING: 8 segundos e 79 centésimos
- SEARCH SEQUENCIAL: 21 centésimos
- SEARCH BINARIO: 7 centésimos
Se você quiser ler mais sobre tabelas internas, métodos de carga e pesquisa, dá uma olhada no capítulo 9, Tabelas Internas, Matrizes e Vetores, do tutorial COBOL: Do Básico ao Avançado, que está disponível aqui neste portal.
Se tiver qualquer dúvida ou comentário, deixa aí embaixo que eu responderei com o maior prazer.