Boas práticas para LOOP AT aninhados
Autor: Mauricio Lauffer | Publicado: 2012-08-01T13:35:00-03:00


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:
&nbsp; lt_bkpf TYPE STANDARD TABLE OF bkpf,
&nbsp; lt_bseg TYPE STANDARD TABLE OF bseg,
&nbsp; ls_bkpf TYPE bkpf,
&nbsp; ls_bseg TYPE bseg.
"Looping cabeçalho
LOOP AT lt_bkpf INTO ls_bkpf.
&nbsp; "Looping item
&nbsp; LOOP AT lt_bseg INTO ls_bseg WHERE bukrs = ls_bkpf-bukrs
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AND belnr = ls_bkpf-belnr
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; AND gjahr = ls_bkpf-gjahr.
&nbsp; &nbsp; "Faz alguma coisa
&nbsp; &nbsp; "Faz outra coisa
&nbsp; 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:
&nbsp; lt_bkpf TYPE STANDARD TABLE OF bkpf,
&nbsp; lt_bseg TYPE STANDARD TABLE OF bseg,
&nbsp; ls_bkpf TYPE bkpf,
&nbsp; ls_bseg TYPE bseg.
"Ordenar para BINARY SEARCH
SORT:
&nbsp; lt_bkpf BY bukrs belnr gjahr,
&nbsp; lt_bseg BY bukrs belnr gjahr buzei.
"Looping cabeçalho
LOOP AT lt_bkpf INTO ls_bkpf.
&nbsp; "Pega índice do registro (sy-tabix)
&nbsp; READ TABLE lt_bseg
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;WITH KEY bukrs = ls_bkpf-bukrs
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; belnr = ls_bkpf-belnr
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; gjahr = ls_bkpf-gjahr
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;TRANSPORTING NO FIELDS
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;BINARY SEARCH.
&nbsp; "Verifica se encontrou registro
&nbsp; CHECK sy-subrc = 0.
&nbsp; "Looping item
&nbsp; LOOP AT lt_bseg INTO ls_bseg FROM sy-tabix.
&nbsp; &nbsp; "Verifica se chave do registro mudou, se sim, sai do looping no item
&nbsp; &nbsp; IF ls_bkpf-bukrs &lt;&gt; ls_bseg-bukrs
&nbsp; &nbsp; &nbsp; &nbsp;OR ls_bkpf-belnr &lt;&gt; ls_bseg-belnr
&nbsp; &nbsp; &nbsp; &nbsp;OR ls_bkpf-gjahr &lt;&gt; ls_bseg-gjahr.
&nbsp; &nbsp; &nbsp; EXIT.
&nbsp; &nbsp; ENDIF.
&nbsp; &nbsp; "Faz alguma coisa
&nbsp; &nbsp; "Faz outra coisa
&nbsp; ENDLOOP.
ENDLOOP.



Basicamente a mudança se dá na utilização de índices para leitura da tabela interna.&nbsp;

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:&nbsp;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.



*&amp;---------------------------------------------------------------------*

*&amp; Report  ZNESTED_LOOPING

*&amp;

*&amp;---------------------------------------------------------------------*

*&amp; ABAP Sample Code

*&amp; Performance in nested looping

*&amp;

*&amp; Maurício Lauffer - Brazil - 08.01.2012

*&amp; http://www.linkedin.com/in/mauriciolauffer

*&amp;

*&amp; This sample explains how to use nested looping with performance

*&amp;

*&amp;---------------------------------------------------------------------*



REPORT znested_looping.





DATA:

&nbsp;&nbsp;gt_bkpf TYPE STANDARD TABLE OF bkpf,

&nbsp;&nbsp;gt_bseg TYPE STANDARD TABLE OF bseg.



FIELD-SYMBOLS:

&nbsp;&nbsp; TYPE bkpf,

&nbsp;&nbsp; TYPE bseg.





"Select header

SELECT * UP TO 500 ROWS

&nbsp;&nbsp;FROM bkpf

&nbsp;&nbsp;INTO TABLE gt_bkpf.

IF sy-subrc &lt;&gt; 0.

&nbsp;&nbsp;RETURN.

ENDIF.



"Select items

SELECT *

&nbsp;&nbsp;FROM bseg

&nbsp;&nbsp;INTO TABLE gt_bseg

&nbsp;&nbsp;FOR ALL ENTRIES IN gt_bkpf

&nbsp;&nbsp;WHERE bukrs = gt_bkpf-bukrs

&nbsp;&nbsp;&nbsp;&nbsp;AND belnr = gt_bkpf-belnr

&nbsp;&nbsp;&nbsp;&nbsp;AND gjahr = gt_bkpf-gjahr.



"Looping into header

LOOP AT gt_bkpf ASSIGNING .

&nbsp;&nbsp;"Get index (sy-tabix) for the first occurrence

&nbsp;&nbsp;"You must guarantee that the table is sorted by the keys used in BINARY SEARCH

&nbsp;&nbsp;READ TABLE gt_bseg TRANSPORTING NO FIELDS

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;WITH KEY bukrs = -bukrs

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;belnr = -belnr

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gjahr = -gjahr

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BINARY SEARCH.

&nbsp;&nbsp;CHECK sy-subrc = 0.



&nbsp;&nbsp;"Looping into items from index which we got before

&nbsp;&nbsp;LOOP AT gt_bseg ASSIGNING  FROM sy-tabix.

&nbsp;&nbsp;&nbsp;&nbsp;"Check the table key, if it has changed, leave this looping

&nbsp;&nbsp;&nbsp;&nbsp;IF -bukrs &lt;&gt; -bukrs

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OR -belnr &lt;&gt; -belnr

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;OR -gjahr &lt;&gt; -gjahr.

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EXIT.

&nbsp;&nbsp;&nbsp;&nbsp;ENDIF.



&nbsp;&nbsp;&nbsp;&nbsp;"Do stuff...

&nbsp;&nbsp;ENDLOOP.

ENDLOOP.





Link do código no GitHub:
https://github.com/mauriciolauffer/ABAP/blob/master/Performance/nested_looping.abap