Ir para o conteúdo

FOR ALL ENTRIES - Guia Completo

FOR ALL ENTRIES é uma técnica para evitar SELECT dentro de LOOP, melhorando drasticamente a performance.


🔹 Problema: SELECT em LOOP

❌ Código Ineficiente

" Problema: 1000 ordens = 1000 queries à BD! 😱
SELECT * FROM vbak INTO TABLE @DATA(lt_orders) UP TO 1000 ROWS.

DATA lt_items TYPE TABLE OF vbap.

LOOP AT lt_orders INTO DATA(ls_order).
  " Cada iteração faz uma query separada
  SELECT * FROM vbap
    INTO TABLE @DATA(lt_temp)
    WHERE vbeln = @ls_order-vbeln.

  APPEND LINES OF lt_temp TO lt_items.
ENDLOOP.

" Total: 1001 acessos à base de dados! 😱

Problema: Se tiver 1000 encomendas, faz 1000 SELECTs à base de dados. Muito lento!


🔹 Solução: FOR ALL ENTRIES

✅ Código Otimizado

" Solução: Apenas 2 queries à BD! ✅
SELECT * FROM vbak INTO TABLE @DATA(lt_orders) UP TO 1000 ROWS.

DATA lt_items TYPE TABLE OF vbap.

" ✅ Validação obrigatória
IF lt_orders IS NOT INITIAL.
  SELECT * FROM vbap
    INTO TABLE @lt_items
    FOR ALL ENTRIES IN @lt_orders
    WHERE vbeln = @lt_orders-vbeln.
ENDIF.

" Total: 2 acessos à base de dados! ✅

Vantagem: Apenas 2 queries independentemente do número de encomendas!


🔹 Como Funciona

O FOR ALL ENTRIES converte internamente para algo como:

SELECT * FROM vbap
WHERE vbeln IN ('0000001', '0000002', '0000003', ..., '0001000')

O SAP agrupa os valores e faz a query de forma otimizada.


⚠️ REGRA DE OURO

SEMPRE VALIDAR

NUNCA use FOR ALL ENTRIES sem validar se a tabela interna está vazia!

❌ ERRADO (sem validação)

DATA lt_orders TYPE TABLE OF vbak.
" lt_orders está vazia!

" PERIGO: Retorna TODOS os registos da tabela! 😱
SELECT * FROM vbap
  INTO TABLE @DATA(lt_items)
  FOR ALL ENTRIES IN @lt_orders
  WHERE vbeln = @lt_orders-vbeln.

" Resultado: milhões de registos! Sistema pode crashar!

✅ CORRETO (com validação)

DATA lt_orders TYPE TABLE OF vbak.

" ✅ Sempre validar
IF lt_orders IS NOT INITIAL.
  SELECT * FROM vbap
    INTO TABLE @DATA(lt_items)
    FOR ALL ENTRIES IN @lt_orders
    WHERE vbeln = @lt_orders-vbeln.
ENDIF.

🔹 Exemplos Práticos

Exemplo 1: Encomendas e Itens

*&---------------------------------------------------------------------*
*& Report Z_FAE_ORDERS_ITEMS
*&---------------------------------------------------------------------*
REPORT z_fae_orders_items.

DATA: lt_orders TYPE TABLE OF vbak,
      lt_items  TYPE TABLE OF vbap.

START-OF-SELECTION.

  " 1. Buscar encomendas
  SELECT * FROM vbak
    INTO TABLE @lt_orders
    WHERE erdat >= '20240101'
    UP TO 100 ROWS.

  " 2. Validar antes de FOR ALL ENTRIES
  IF lt_orders IS NOT INITIAL.

    " 3. Buscar itens das encomendas
    SELECT * FROM vbap
      INTO TABLE @lt_items
      FOR ALL ENTRIES IN @lt_orders
      WHERE vbeln = @lt_orders-vbeln.

    WRITE: / |Encomendas: { lines( lt_orders ) }|.
    WRITE: / |Itens: { lines( lt_items ) }|.
  ELSE.
    WRITE: / 'Nenhuma encomenda encontrada.'.
  ENDIF.

Exemplo 2: Clientes e Encomendas

" Buscar clientes portugueses
SELECT * FROM kna1
  INTO TABLE @DATA(lt_customers)
  WHERE land1 = 'PT'
  UP TO 50 ROWS.

" Buscar encomendas desses clientes
IF lt_customers IS NOT INITIAL.
  SELECT * FROM vbak
    INTO TABLE @DATA(lt_orders)
    FOR ALL ENTRIES IN @lt_customers
    WHERE kunnr = @lt_customers-kunnr.
ENDIF.

Exemplo 3: Múltiplos Campos

" Pode usar múltiplos campos na condição
DATA: BEGIN OF ls_key,
        carrid TYPE s_carr_id,
        connid TYPE s_conn_id,
      END OF ls_key.

DATA lt_keys LIKE TABLE OF ls_key.

" Popular tabela de chaves
lt_keys = VALUE #(
  ( carrid = 'LH' connid = '0400' )
  ( carrid = 'LH' connid = '0402' )
  ( carrid = 'AA' connid = '0017' )
).

" FOR ALL ENTRIES com múltiplos campos
IF lt_keys IS NOT INITIAL.
  SELECT * FROM sflight
    INTO TABLE @DATA(lt_flights)
    FOR ALL ENTRIES IN @lt_keys
    WHERE carrid = @lt_keys-carrid
      AND connid = @lt_keys-connid.
ENDIF.

🔹 Limitações e Cuidados

1. Duplicados

FOR ALL ENTRIES remove duplicados automaticamente.

DATA lt_ids TYPE TABLE OF vbak-vbeln.

lt_ids = VALUE #( ( '0000001' ) ( '0000001' ) ( '0000002' ) ).

" Mesmo com 2x '0000001', o SELECT faz:
" WHERE vbeln IN ('0000001', '0000002')
IF lt_ids IS NOT INITIAL.
  SELECT * FROM vbap
    INTO TABLE @DATA(lt_items)
    FOR ALL ENTRIES IN @lt_ids
    WHERE vbeln = @lt_ids.
ENDIF.

2. Limite de Registos

Se a tabela interna for muito grande (> 10.000), pode haver problemas de performance.

Solução: Processar em lotes (chunks).

DATA: lv_lines TYPE i,
      lv_start TYPE i VALUE 1,
      lv_end   TYPE i,
      lt_chunk TYPE TABLE OF vbak.

CONSTANTS lc_chunk_size TYPE i VALUE 1000.

lv_lines = lines( lt_all_orders ).

WHILE lv_start <= lv_lines.
  lv_end = lv_start + lc_chunk_size - 1.

  " Processar chunk
  CLEAR lt_chunk.
  lt_chunk = lt_all_orders[ lv_start TO lv_end ].

  IF lt_chunk IS NOT INITIAL.
    SELECT * FROM vbap
      INTO TABLE @DATA(lt_items_temp)
      FOR ALL ENTRIES IN @lt_chunk
      WHERE vbeln = @lt_chunk-vbeln.

    APPEND LINES OF lt_items_temp TO lt_all_items.
  ENDIF.

  lv_start = lv_start + lc_chunk_size.
ENDWHILE.

3. Campos Vazios

Cuidado com campos vazios na tabela interna:

DATA: BEGIN OF ls_data,
        kunnr TYPE kunnr,
        land1 TYPE land1,
      END OF ls_data.

DATA lt_data LIKE TABLE OF ls_data.

" Se land1 estiver vazio em alguns registos...
lt_data = VALUE #(
  ( kunnr = '0001' land1 = 'PT' )
  ( kunnr = '0002' land1 = '' )    " Vazio!
).

" Pode retornar registos indesejados
IF lt_data IS NOT INITIAL.
  SELECT * FROM kna1
    INTO TABLE @DATA(lt_customers)
    FOR ALL ENTRIES IN @lt_data
    WHERE land1 = @lt_data-land1.  " Inclui vazios!
ENDIF.

Solução: Filtrar antes do FOR ALL ENTRIES.

DELETE lt_data WHERE land1 IS INITIAL.

IF lt_data IS NOT INITIAL.
  SELECT * FROM kna1...
ENDIF.

🔹 Alternativa: INNER JOIN

Nem sempre FOR ALL ENTRIES é a melhor opção. Se as tabelas estão relacionadas, use INNER JOIN:

" ❌ FOR ALL ENTRIES desnecessário
SELECT * FROM vbak INTO TABLE @DATA(lt_orders).
IF lt_orders IS NOT INITIAL.
  SELECT * FROM vbap
    INTO TABLE @DATA(lt_items)
    FOR ALL ENTRIES IN @lt_orders
    WHERE vbeln = @lt_orders-vbeln.
ENDIF.

" ✅ MELHOR: INNER JOIN
SELECT h~vbeln,
       h~kunnr,
       i~posnr,
       i~matnr
  FROM vbak AS h
  INNER JOIN vbap AS i ON h~vbeln = i~vbeln
  INTO TABLE @DATA(lt_result).

📊 Quando Usar

Situação Solução Recomendada
Tabelas relacionadas (FK) INNER JOIN
Tabelas sem relação direta FOR ALL ENTRIES
Lógica complexa entre queries FOR ALL ENTRIES
Dados já em memória FOR ALL ENTRIES
SELECT em LOOP SEMPRE EVITAR!

💡 Checklist

Antes de usar FOR ALL ENTRIES:

  • [ ] Verificar se tabela interna IS NOT INITIAL
  • [ ] Considerar INNER JOIN como alternativa
  • [ ] Se tabela grande (>10k), usar processamento em chunks
  • [ ] Filtrar campos vazios se necessário
  • [ ] Testar com diferentes volumes de dados

🚀 Próximo Passo

Aprenda sobre Índices de Base de Dados para otimizar ainda mais as queries.