Partida... lagarta... fugida!
Senhoras e senhores, meninos e meninas, a corrida está prestes a começar.
Introdução
Apresento-vos as 4 participantes. São 4 tabelas internas, de diferentes raças e credos, que se vão pelejar pelo título atlético do LOOP em velocidade. Aqui estão elas:
Concorrente 1: DATA: LT_ITEM TYPE TABLE Concorrente 2: DATA: LT_ITEM_HASHED TYPE HASHED TABLE Concorrente 3: DATA: LT_ITEM_SORTED TYPE SORTED TABLE Concorrente 4: DATA: LT_ITEM TYPE TABLE + INTO INDEX
A pista de corridas é um programa ABAP cuja primeira parte, comum aos 4, aqui vos deixo:
REPORT YYYTEST_ABAPINHO.
TYPES: BEGIN OF TY_BKPF,
BUKRS TYPE BUKRS,
BELNR TYPE BELNR_D,
GJAHR TYPE GJAHR,
END OF TY_BKPF.
TYPES: BEGIN OF TY_BSEG,
BUKRS TYPE BUKRS,
BELNR TYPE BELNR_D,
GJAHR TYPE GJAHR,
BUZEI TYPE BUZEI,
END OF TY_BSEG.
DATA: LT_HEADER TYPE TABLE OF TY_BKPF.
DATA: LS_HEADER TYPE TY_BKPF.
DATA: LS_ITEM TYPE TY_BSEG.
DATA: LV_COUNTER TYPE I.
START-OF-SELECTION.
SELECT BUKRS BELNR GJAHR
FROM BKPF
INTO TABLE LT_HEADER
UP TO 25000 ROWS.
CHECK LT_HEADER IS NOT INITIAL.
(a partir daqui vem a parte específica de cada um dos concorrentes)
Como vêem, nada de mais. Começa por declarar uns tipos, umas variáveis auxiliares, depois selecciona 25 mil linhas da BKPF para a tabela de cabeçalhos LT_HEADER que é também partilhada pelas 4 concorrentes.
Atenção a todos! Se quiserem fazer apostas, é agora o momento. Porque a prova está prestes a começar.
Aos vossos lugares… Partida… Lagarta… Fugida!
Concorrente número 1
A primeira concorrente é a banal tabela interna de sempre, usada e abusada, como veio de fábrica em meados do século XX. Aqui fica:
DATA: LT_ITEM TYPE TABLE OF TY_BSEG.
SELECT BUKRS BELNR GJAHR BUZEI
FROM BSEG
INTO TABLE LT_ITEM
FOR ALL ENTRIES IN LT_HEADER
WHERE BUKRS = LT_HEADER-BUKRS
AND BELNR = LT_HEADER-BELNR
AND GJAHR = LT_HEADER-GJAHR.
LOOP AT LT_HEADER INTO LS_HEADER.
LOOP AT LT_ITEM INTO LS_ITEM
WHERE BUKRS = LS_HEADER-BUKRS
AND BELNR = LS_HEADER-BELNR
AND GJAHR = LS_HEADER-GJAHR.
ADD 1 TO LV_COUNTER.
ENDLOOP.
ENDLOOP.
Mais convencional é impossível. E os resultados também não são muito animadores. A ver se à luz disto os nossos leitores começam a pensar duas vezes antes de a usar em tudo o que mexe:
Concorrente número 2
É a vez da concorrente número 2, pintura metalizada, jantes de liga leve e equipado com uma HASH de rendimento que lhe dá um roncar de Ferrari. Ei-la:
DATA: LT_ITEM_HASHED TYPE HASHED TABLE OF TY_BSEG
WITH UNIQUE KEY BUKRS BELNR GJAHR BUZEI.
SELECT BUKRS BELNR GJAHR BUZEI
FROM BSEG
INTO TABLE LT_ITEM_HASHED
FOR ALL ENTRIES IN LT_HEADER
WHERE BUKRS = LT_HEADER-BUKRS
AND BELNR = LT_HEADER-BELNR
AND GJAHR = LT_HEADER-GJAHR.
LOOP AT LT_HEADER INTO LS_HEADER.
LOOP AT LT_ITEM_HASHED INTO LS_ITEM
WHERE BUKRS = LS_HEADER-BUKRS
AND BELNR = LS_HEADER-BELNR
AND GJAHR = LS_HEADER-GJAHR.
ADD 1 TO LV_COUNTER.
ENDLOOP.
ENDLOOP.
Esta concorrente tem uma chave única definida com 4 campos. Embora as HASH TABLEs sejam o supra-sumo da barbatana quando invocadas com a chave completa, aqui, por se tratar de um LOOP, tal não é naturalmente possível. Por isso, ela arrasta-se vergonhosamente até à meta. Uma vergonha como podem comprovar pelos resultados seguintes:
Concorrente número 3
Segue-se a concorrente número 3, uma bela tabela que já vem ordenada para maior rendimento aerodinâmico. Rápida e silenciosa como se quer. Aqui está:
DATA: LT_ITEM_SORTED TYPE SORTED TABLE OF TY_BSEG
WITH UNIQUE KEY BUKRS BELNR GJAHR BUZEI.
SELECT BUKRS BELNR GJAHR BUZEI
FROM BSEG
INTO TABLE LT_ITEM_SORTED
FOR ALL ENTRIES IN LT_HEADER
WHERE BUKRS = LT_HEADER-BUKRS
AND BELNR = LT_HEADER-BELNR
AND GJAHR = LT_HEADER-GJAHR.
LOOP AT LT_HEADER INTO LS_HEADER.
LOOP AT LT_ITEM_SORTED INTO LS_ITEM
WHERE BUKRS = LS_HEADER-BUKRS
AND BELNR = LS_HEADER-BELNR
AND GJAHR = LS_HEADER-GJAHR.
ADD 1 TO LV_COUNTER.
ENDLOOP.
ENDLOOP.
Incrível, Senhoras e senhores! A concorrente número 3 foi muito rápida. Cerca de 2000x mais rápida a fazer os LOOPs do que as duas concorrentes anteriores! Eis sua pontuação:
Concorrente número 4
Por último mas não em último, apresento-vos a concorrente número 4. A mais maluca e rebuscada delas todas. É uma espécie de modelo antigo recauchutado, como fazem àqueles Renault 5 Turbo do tempo da avozinha, que parecem uma porcaria pegada e depois voam baixinho:
DATA: LT_ITEM TYPE TABLE OF TY_BSEG.
DATA: LV_INDEX TYPE SYTABIX.
SELECT BUKRS BELNR GJAHR BUZEI
FROM BSEG
INTO TABLE LT_ITEM
FOR ALL ENTRIES IN LT_HEADER
WHERE BUKRS = LT_HEADER-BUKRS
AND BELNR = LT_HEADER-BELNR
AND GJAHR = LT_HEADER-GJAHR.
SORT LT_ITEM BY BUKRS BELNR GJAHR.
LOOP AT LT_HEADER INTO LS_HEADER.
READ TABLE LT_ITEM
TRANSPORTING NO FIELDS
BINARY SEARCH
WITH KEY BUKRS = LS_HEADER-BUKRS
BELNR = LS_HEADER-BELNR
GJAHR = LS_HEADER-GJAHR.
CHECK SY-SUBRC = 0.
LV_INDEX = SY-TABIX.
LOOP AT LT_ITEM INTO LS_ITEM FROM LV_INDEX.
IF LS_ITEM-BUKRS <> LS_HEADER-BUKRS OR
LS_ITEM-BELNR <> LS_HEADER-BELNR OR
LS_ITEM-GJAHR <> LS_HEADER-GJAHR.
EXIT.
ENDIF.
ADD 1 TO LV_COUNTER.
ENDLOOP.
ENDLOOP.
Que resultado impressionante também. Trata-se de uma tabela convencional mas que, graças a umas manobras curiosas, ganha enorme velocidade. No fundo ela faz uns truques para fingir que é mais do que realmente é. Começa por fazer um SORT. Depois, em vez de fazer o segundo LOOP logo de seguida, primeiro faz um READ TABLE BINARY SEARCH e depois faz um LOOP FROM INDEX (um truque de magia já aqui discutido). E os resultados não poderiam ser mais impressionantes pois, embora seja uma tabela da plebe, comporta-se como se sangue azul tivesse:
O Vencedor
E a grande vencedora, senhoras e senhores, é a concorrente número 3, a SORTED TABLE. Logo a seguir, a concorrente número 4, com o seu LOOP FROM INDEX. Como podem comprovar, a diferença entre a SORTED TABLE e esta última das artimanhas é muito pequena. Claro que, podendo escolher, será sempre preferível recorrer a uma SORTED TABLE. Mas caso se esteja a mexer em código existente que tenha já uma tabela das banais e que não possa ser alterada, a concorrente 4 é uma muito boa solução de compromisso. As duas outras tiveram resultados tão maus que mais vale nem falar mais delas.
É interessante comparar os gráficos e observar que o tempo despendido na base de dados é praticamente idêntico nos 4 casos apresentados. As grandes diferenças são ao nível do processamento ABAP.
E assim chega ao fim a corrida de hoje. Felizmente não houve acidentes. Espero que tenha sido tão emocionante para si como foi para nós. Até uma próxima corrida.
(Obrigado Bruno Filipa!)
O Abapinho saúda-vos.