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:

Figura 1. Fluxo do algoritmo do SEARCH ALL.

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.

  1. É 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.


Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *