Muitas vezes é necessário escrevermos códigos que leiam tabelas internas em sequência, sendo necessário diversos LOOPs. O comum é utilizarmos uma estrutura encadeada de LOOP AT dentro de LOOP AT. Como no exemplo abaixo:
REPORT zlooping.
DATA:
lt_bkpf TYPE STANDARD TABLE OF bkpf,
lt_bseg TYPE STANDARD TABLE OF bseg,
ls_bkpf TYPE bkpf,
ls_bseg TYPE bseg.
"Looping cabeçalho
LOOP AT lt_bkpf INTO ls_bkpf.
"Looping item
LOOP AT lt_bseg INTO ls_bseg WHERE bukrs = ls_bkpf-bukrs
AND belnr = ls_bkpf-belnr
AND gjahr = ls_bkpf-gjahr.
"Faz alguma coisa
"Faz outra coisa
ENDLOOP.
ENDLOOP.
REPORT zlooping.
DATA:
lt_bkpf TYPE STANDARD TABLE OF bkpf,
lt_bseg TYPE STANDARD TABLE OF bseg,
ls_bkpf TYPE bkpf,
ls_bseg TYPE bseg.
"Looping cabeçalho
LOOP AT lt_bkpf INTO ls_bkpf.
"Looping item
LOOP AT lt_bseg INTO ls_bseg WHERE bukrs = ls_bkpf-bukrs
AND belnr = ls_bkpf-belnr
AND gjahr = ls_bkpf-gjahr.
"Faz alguma coisa
"Faz outra coisa
ENDLOOP.
ENDLOOP.
Há algo errado no código acima? Não absolutamente nada de errado. Ele irá fazer exatamente o que queremos fazer. Looping na BKPF seguido de looping na BSEG.
Agora digamos que na BKPF tem 10 registros e na BSEG 100. Provavelmente nosso looping seria rápido o bastante para não notarmos qualquer degradação do desempenho. Porém, imaginamos agora que nossa BKPF tem 100 registros e nossa BSEG 1000. Muito provavelmente o programa irá demorar um bocado para ser executado, pois serão muitas iterações. O programa lerá a BKPF 100 vezes e para cada iteração na BKPF a BSEG será lida 1000 vezes! Exatamente, a cada laço o programa sempre lerá TODA a BSEG. Neste caso, 100.000 iterações na BSEG.
Mas como podemos resolver esta questão de desempenho? De que forma podemos fazer uma sequência de loopings sem usar o LOOP AT + WHERE?
Abaixo segue uma forma de fazer com que seus loopings não fiquem tão demorados:
REPORT zlooping.
DATA:
lt_bkpf TYPE STANDARD TABLE OF bkpf,
lt_bseg TYPE STANDARD TABLE OF bseg,
ls_bkpf TYPE bkpf,
ls_bseg TYPE bseg.
"Ordenar para BINARY SEARCH
SORT:
lt_bkpf BY bukrs belnr gjahr,
lt_bseg BY bukrs belnr gjahr buzei.
"Looping cabeçalho
LOOP AT lt_bkpf INTO ls_bkpf.
"Pega índice do registro (sy-tabix)
READ TABLE lt_bseg
WITH KEY bukrs = ls_bkpf-bukrs
belnr = ls_bkpf-belnr
gjahr = ls_bkpf-gjahr
TRANSPORTING NO FIELDS
BINARY SEARCH.
"Verifica se encontrou registro
CHECK sy-subrc = 0.
"Looping item
LOOP AT lt_bseg INTO ls_bseg FROM sy-tabix.
"Verifica se chave do registro mudou, se sim, sai do looping no item
IF ls_bkpf-bukrs <> ls_bseg-bukrs
OR ls_bkpf-belnr <> ls_bseg-belnr
OR ls_bkpf-gjahr <> ls_bseg-gjahr.
EXIT.
ENDIF.
"Faz alguma coisa
"Faz outra coisa
ENDLOOP.
ENDLOOP.
REPORT zlooping.
DATA:
lt_bkpf TYPE STANDARD TABLE OF bkpf,
lt_bseg TYPE STANDARD TABLE OF bseg,
ls_bkpf TYPE bkpf,
ls_bseg TYPE bseg.
"Ordenar para BINARY SEARCH
SORT:
lt_bkpf BY bukrs belnr gjahr,
lt_bseg BY bukrs belnr gjahr buzei.
"Looping cabeçalho
LOOP AT lt_bkpf INTO ls_bkpf.
"Pega índice do registro (sy-tabix)
READ TABLE lt_bseg
WITH KEY bukrs = ls_bkpf-bukrs
belnr = ls_bkpf-belnr
gjahr = ls_bkpf-gjahr
TRANSPORTING NO FIELDS
BINARY SEARCH.
"Verifica se encontrou registro
CHECK sy-subrc = 0.
"Looping item
LOOP AT lt_bseg INTO ls_bseg FROM sy-tabix.
"Verifica se chave do registro mudou, se sim, sai do looping no item
IF ls_bkpf-bukrs <> ls_bseg-bukrs
OR ls_bkpf-belnr <> ls_bseg-belnr
OR ls_bkpf-gjahr <> ls_bseg-gjahr.
EXIT.
ENDIF.
"Faz alguma coisa
"Faz outra coisa
ENDLOOP.
ENDLOOP.
Basicamente a mudança se dá na utilização de índices para leitura da tabela interna.
O grande segredo aqui é a utilização do SORT e do READ TABLE com BINARY SEARCH. Utilizar BINARY SEARCH significa que a leitura na tabela interna não será sequêncial, ou seja, não irá ler uma linha de cada vez até terminar de ler todos os registros, a leitura será binária (leia sobre os riscos: http://agilpioneiro.wordpress.com/2012/04/02/binary-search-uma-roleta-russa-com-o-tambor-cheio).
Resumindo, não precisamos e não vamos ler a BSEG inteira, iremos ler a partir do primeiro registro encontrado para a chave informada até a chave do item mudar, quando não for mais a mesma chave do cabeçalho saí do looping no item e vai para o próximo registro do cabeçalho. Simples não?
E, para ficar melhor ainda, ao invés de utilizar o LOOP AT tabela INTO workarea, utilize LOOP AT tabela ASSIGNING fieldsymbol. Aqui teremos um ganho de desempenho, pois ao invés de copiar todo o registro para uma workarea iremos utilizar um ponteiro que indica a posição da linha da tabela na memória. Sempre que possível utilize ponteiros (FIELD-SYMBOL) para ler suas tabelas internas. Sempre haverá ganho de desempenho.
Link do código no GitHub:
https://github.com/mauriciolauffer/ABAP/blob/master/Performance/nested_looping.abap
*&---------------------------------------------------------------------**& Report ZNESTED_LOOPING*&*&---------------------------------------------------------------------**& ABAP Sample Code*& Performance in nested looping*&*& Maurício Lauffer - Brazil - 08.01.2012*& http://www.linkedin.com/in/mauriciolauffer*&*& This sample explains how to use nested looping with performance*&*&---------------------------------------------------------------------*REPORT znested_looping.DATA:gt_bkpf TYPE STANDARD TABLE OF bkpf,gt_bseg TYPE STANDARD TABLE OF bseg.FIELD-SYMBOLS:TYPE bkpf, TYPE bseg. "Select headerSELECT * UP TO 500 ROWSFROM bkpfINTO TABLE gt_bkpf.IF sy-subrc <> 0.RETURN.ENDIF."Select itemsSELECT *FROM bsegINTO TABLE gt_bsegFOR ALL ENTRIES IN gt_bkpfWHERE bukrs = gt_bkpf-bukrsAND belnr = gt_bkpf-belnrAND gjahr = gt_bkpf-gjahr."Looping into headerLOOP AT gt_bkpf ASSIGNING. "Get index (sy-tabix) for the first occurrence"You must guarantee that the table is sorted by the keys used in BINARY SEARCHREAD TABLE gt_bseg TRANSPORTING NO FIELDSWITH KEY bukrs =-bukrs belnr =-belnr gjahr =-gjahr BINARY SEARCH.CHECK sy-subrc = 0."Looping into items from index which we got beforeLOOP AT gt_bseg ASSIGNINGFROM sy-tabix. "Check the table key, if it has changed, leave this loopingIF-bukrs <> -bukrs OR-belnr <> -belnr OR-gjahr <> -gjahr. EXIT.ENDIF."Do stuff...ENDLOOP.ENDLOOP.
Link do código no GitHub:
https://github.com/mauriciolauffer/ABAP/blob/master/Performance/nested_looping.abap