Loop in a Loop with index less than the stopped one - abap

I have the following problem and I need an idea how to overcome?
I have 2 identical ITABs: ITAB1 and ITAB2 with 60 records.
I am looping in the 1st ITAB and when I am finding a record I am looping in the 2nd ITAB with INDEX = sy-tabix of the 1st one:
LOOP at ITAB1 where COL = '001'.
lv_tabix = sy-tabix.
* Do STH.
LOOP at ITAB2 FROM lv_tabix
* do sth
EXIT.
ENDCASE.
ENDCASE.
Lets suppose that I am looping the 2nd ITAB with lv_tabix = 17 and, I am exiting from the 2nd when its tabix=22.
So I am returning in the 1st ITAB do sth and, I am starting the loop of the 2nd ITAB with lv_tabix=21.
I have noticed that the loop of the 2nd ITAB cannot start from a record (21) which is less than the one it was stopped (22).
Am I right?
How can I overcome this problem?
Thanks
Elias

Cannot reproduce your problem.
DATA(table_1) = VALUE string_table( ( `A` ) ( `B` ) ( `C` ) ( `D` ) ).
DATA(table_2) = VALUE string_table( ( `A` ) ( `B` ) ( `C` ) ( `D` ) ).
LOOP AT table_1 INTO DATA(row_1).
DATA(start_index) = sy-tabix.
LOOP AT table_2 INTO DATA(row_2) FROM start_index.
IF row_2 = `C`.
EXIT.
ENDIF.
ENDLOOP.
ENDLOOP.
works fine, although in the first outer loop iteration it exits the inner loop at sy-tabix = 3 and in the second outer loop iteration restarts the inner loop with the lower start_index = 2.

Related

Enhance performance of ABAP report when using OpenSQL query and function call

I have an issue with my ABAP report for it being very slow and taking a long time to complete.
My original report had a query to MARA to get all materials into an iTab, loop over it and then query additional tables for each material and store the necessary data and a function call to READ_TEXT in an output iTab.
Since this process takes very long to compete I changed the code to rely more on the database to handle data access instead of making queries for additionoal data each at a time. But still the time to complete is very long for the amount of data to be fetched.
Current code looks like this
report z_test_report.
types:
begin of _output,
matnr type mara-matnr,
text01 type c length 40,
text02 type c length 40,
ntgew type mara-ntgew,
matkl type mara-matkl,
ean11 type mara-ean11,
preis type p length 6 decimals 2,
bstd type mbew-lbkum,
laeng type marm-laeng,
breit type marm-breit,
hoehe type marm-hoehe,
volum type marm-volum,
end of _output,
t_output type _output.
data:
gt_output type table of t_output,
gs_output like line of gt_output,
gr_table type ref to cl_salv_table,
gr_funct type ref to cl_salv_functions,
gr_columns type ref to cl_salv_columns_table,
gr_column type ref to cl_salv_column.
selection-screen begin of block b01 with frame.
parameters: pa_date type d obligatory.
parameters: pa_kschl type t685-kschl obligatory.
parameters: pa_pltyp type t189-pltyp obligatory.
selection-screen end of block b01.
start-of-selection.
select
m~matnr,
m~ntgew,
m~matkl,
m~ean11,
t~maktx,
b~lbkum,
l~laeng,
l~breit,
l~hoehe,
l~volum
from
mara as m
left join
makt as t
on
m~matnr = t~matnr
and
t~spras = 'D'
left join
mbew as b
on
b~matnr = m~matnr
and
b~bwkey = '1100'
left join
marm as l
on
l~matnr = m~matnr
and
l~meinh = m~meins
into
#data(wa_daten)
order by
m~matnr.
gs_output = corresponding #( wa_daten ).
select single
*
from
a908
where
a908~kappl = 'V'
and
a908~kschl = #pa_kschl
and
a908~vkorg = '1100'
and
a908~pltyp = #pa_pltyp
and
a908~matnr = #wa_daten-matnr
and
a908~datab < #sy-datum
and
a908~datbi > #sy-datum
into
#data(gs_a908).
if ( sy-subrc = 0 ).
select single
*
from
konp
where
konp~knumh = #gs_a908-knumh
into
#data(gs_knop).
if ( sy-subrc = 0 ).
gs_output-preis = gs_knop-kbetr.
endif.
endif.
data:
material_name like stxh-tdname,
textlines like tline occurs 0,
textline like line of textlines.
material_name = gs_output-matnr.
call function 'READ_TEXT'
exporting
id = 'GRUN'
language = 'D'
name = material_name
object = 'MATERIAL'
tables
lines = textlines
exceptions
id = 1
language = 2
name = 3
not_found = 4
object = 5
reference_check = 6
wrong_access_to_archive = 7
others = 8.
if ( sy-subrc = 0 ).
loop at textlines into textline.
concatenate gs_output-text02 textline-tdline into gs_output-text02.
endloop.
endif.
condense gs_output-text02.
append gs_output to gt_output.
endselect.
try.
cl_salv_table=>factory(
exporting
list_display = if_salv_c_bool_sap=>false
importing
r_salv_table = gr_table
changing
t_table = gt_output
).
catch cx_salv_msg.
endtry.
gr_funct = gr_table->get_functions( ).
gr_funct->set_all( abap_true ).
gr_table->display( ).
I think this code can be improved since reading 17k entries from MARA and joining them with the other tables should not be the bottle neck. Changing the old code to the current one reduced the runtime from about 45s down to 30s, removing the READ_TEXT only made the report take about 5s less.
If anyone has an idea it would be really appreciated. Or if anyone knows a tool to measure report performance to find out about bottle necks.

How do I aggregate negative values with AT END OF and SUM?

I´m working on a ABAP program, in which I have to do a validation for a specific field, like the code below:
SORT t_prcd BY knumh kschl.
LOOP AT t_prcd INTO wa_prcd WHERE knumh = wa_wcocoh-knumh AND kschl = wa_wcocoh-kschl.
IF wa_prcd-vbtyp = 'C'.
wa_prcd-netwr = wa_prcd-netwr * ( -1 ).
wa_prcd-kwmeng = wa_prcd-kwmeng * ( -1 ).
ENDIF.
AT END OF knumh.
SUM.
" SUBTRACT wa_prcd-netwr FROM wa_prcd-netwr.
MOVE EXACT wa_prcd-netwr TO wa_talv-val_vendido.
MOVE wa_prcd-kwmeng TO wa_talv-quant_vendido.
ENDAT.
ENDLOOP.
What I need to do is: if wa_prcd-vbtyp is C the value of wa_prcd-netwr must be negative.
In debugging I see the condition is working fine, but when it comes to SUM, it just does that without sign, which means that instead of sum (-A + -B), it does ( A + B )
Can somebody help me please? Thanks.
When you multiply with -1, you only update the local variable wa_prcd, but not the original table t_prcd. SUM however only takes into account what's in t_prcd.
The fix thus is simply to separate the update from the sum step:
SORT t_prcd BY knumh kschl.
" use a reference or field-symbol to update the table in-place
LOOP AT t_prcd REFERENCE INTO DATA(ref_prcd)
WHERE knumh = wa_wcocoh-knumh AND kschl = wa_wcocoh-kschl
AND vbtyp = 'C'.
ref_prcd->netwr = ref_prcd->netwr * ( -1 ).
ref_prcd->kwmeng = ref_prcd->kwmeng * ( -1 ).
ENDLOOP.
LOOP AT t_prcd INTO DATA(wa_prcd)
WHERE knumh = wa_wcocoh-knumh AND kschl = wa_wcocoh-kschl.
AT END OF knumh.
SUM.
MOVE EXACT wa_prcd-netwr TO wa_talv-val_vendido.
MOVE wa_prcd-kwmeng TO wa_talv-quant_vendido.
ENDAT.
ENDLOOP.

Checkbox multiple selection filter

I want to filter the data into my program depending what checkbox is selected or not.
parameters: p_z1 as checkbox DEFAULT 'X' ,
p_z2 as checkbox.
selection-screen end of block b4.
So if one of these two is selected or if both are selected how can I filter my data?
select single * from mara where matnr = pv_matnr
and "if checkbox one is selected" where matkl = t023-matkl.
"if checkbox two is selected" where matkl = v023-matkl.
You can prepare dynamic where clause -
DATA : lv_query TYPE string.
CONCATENATE 'MATNR = PV_MATNR' lv_query INTO lv_query.
IF p_z1 = 'X'.
CONCATENATE lv_query 'AND MATKL = T023-MATKL' INTO lv_query SEPARATED BY space.
ENDIF.
IF p_z2 = 'X'.
CONCATENATE lv_query 'AND MATKL = V023-MATKL' INTO lv_query SEPARATED BY space.
ENDIF.
SELECT SINGLE * FROM mara WHERE (lv_query).
"Dynamic" queries are to be avoided as far as possible, just to make it easier to check the code against possible SQL injection attacks (with SELECT … WHERE (lv_query), there could be a Denial-of-Service attack with an empty lv_query empty). So I would write the code by divScorp as follows:
parameters: p_z1 as checkbox DEFAULT 'X' ,
p_z2 as checkbox,
pv_matnr TYPE mara-matnr.
DATA: t023 TYPE t023, v023 TYPE v023, mara TYPE mara.
DATA range_matkl TYPE RANGE OF mara-matkl.
CLEAR range_matkl.
IF p_z1 = 'X'.
APPEND VALUE #( sign = 'I' option = 'EQ' low = t023-matkl ) TO range_matkl.
ENDIF.
IF p_z2 = 'X'.
APPEND VALUE #( sign = 'I' option = 'EQ' low = v023-matkl ) TO range_matkl.
ENDIF.
SELECT SINGLE * FROM mara WHERE MATNR = PV_MATNR and matkl IN range_matkl.
PS: my code (and the one of divScorp) is non-sense because pureAbap algorithm is incorrect : in case p_z1 and p_z2 are both 'X', then there's the risk that the SELECT returns nothing if t023-matkl is different from v023-matkl. Maybe p_z1 and p_z2 should be converted into radio buttons? (only one of the two can be selected?)
You can always use:
IF chck1 = 'X' and chck2 = 'X'.
SELECT ... WHERE both.
ELSEIF chck1 = 'X'.
SELECT ... WHERE ...
ELSEIF chck2 = 'X'.
SELECT ... WHERE ...
ENDIF.
But I guess that's not what you wanted to do. Not the most elegant way but hey - it works.

Shortest notation to split ABAP internal table into smaller pieces

In ABAP, I have a pretty large internal table, say 31,000 rows. What's the shortest and most efficient way to split that into multiple smaller tables of fixed size, say 1,000 rows each?
Naive way would be:
DATA lt_next_package TYPE tt_table_type.
LOOP AT it_large_table INTO DATA(ls_row).
INSERT ls_row INTO TABLE lt_next_package.
IF lines( lt_next_package ) >= lc_package_size.
INSERT lt_next_package INTO TABLE rt_result.
CLEAR lt_next_package.
ENDIF.
ENDLOOP.
IF lt_next_package IS NOT INITIAL.
INSERT lt_next_packge INTO TABLE rt_result.
ENDIF.
That works and is rather efficient, but looks cumbersome, esp. the don't-forget-the-last-package section at the very end. I believe there must be a better way to do this with the newer ABAP mesh paths and table expressions, but so far couldn't come up with one.
I am not sure if, there is a right way to do it (sure, there are several ways to do it), but you can try this to overcome the last package problem:
WHILE it_large_table IS NOT INITIAL.
LOOP AT it_large_table ASSIGNING FIELD-SYMBOL(<ls_line>) FROM 1 TO 1000.
INSERT <ls_line> INTO TABLE lt_next_package.
ENDLOOP.
DELETE it_large_table FROM 1 TO 1000.
INSERT lt_next_package INTO TABLE rt_table.
CLEAR: lt_next_package.
ENDWHILE.
Based on JozsefSzikszai's answer, devised another option:
rt_result = VALUE #( FOR i = 1
UNTIL i > round( val = lines( it_large_table) / lc_package_size
dec = 0
mode = cl_abap_math=>round_up )
LET lv_end = i * lc_package_size
lv_start = lv_end - lc_package_size + 1 IN
( VALUE <result-type>(
( LINES OF it_large_table FROM lv_start TO lv_end ) ) ) ).
Somewhat reinvention of both Florian and Jozsef approaches.
Prerequisits:
TYPES:
BEGIN OF line,
rows TYPE string,
slice TYPE bseg_t,
END OF line,
itab TYPE STANDARD TABLE OF line WITH EMPTY KEY,
bseg_t TYPE STANDARD TABLE OF bseg WITH EMPTY KEY.
DATA: result TYPE itab.
Filling large table:
SELECT * UP TO 31000 ROWS
INTO TABLE #DATA(lt_bseg)
FROM bseg.
Here we costruct table of tables which contains slices of the main table by 1000 rows each.
WHILE lt_bseg IS NOT INITIAL.
result = VALUE itab( BASE result
(
rows = | { sy-index * 1000 }-{ sy-index * 1000 + 1000} |
slice = VALUE bseg_t( FOR wa IN lt_bseg INDEX INTO i FROM i + 1 TO i + 1
( LINES OF lt_bseg from i TO i + 999 ) )
)
).
DELETE lt_bseg FROM 1 TO 1000.
ENDWHILE.
Looks somewhat as our requirement, no?
Here are two ways to build a table of subtables AKA pagination:
METHOD prepare_data.
TYPES:
BEGIN OF line,
name TYPE string,
subset TYPE salv_t_row,
END OF line,
itab TYPE STANDARD TABLE OF line WITH EMPTY KEY.
CONSTANTS: lc_package_size TYPE i VALUE 3.
DATA: result TYPE itab,
it_rows TYPE salv_t_row.
DO round( val = lines( it_rows ) / lc_package_size
dec = 0
mode = cl_abap_math=>round_up ) TIMES.
DATA(lv_end) = sy-index * lc_package_size.
DATA(lv_start) = lv_end - lc_package_size + 1.
APPEND INITIAL LINE TO result ASSIGNING FIELD-SYMBOL(<subset>).
<subset>-name = | Subset { sy-index } |.
<subset>-subset = VALUE #( ( LINES OF it_rows FROM lv_start TO lv_end ) ).
ENDDO.
clear result.
result = VALUE itab( FOR i = 1
UNTIL i > round( val = lines( it_rows ) / lc_package_size
dec = 0
mode = cl_abap_math=>round_up )
LET k = 1 IN
(
name = | Subset { i } |
subset = VALUE salv_t_row(
LET
end = i * lc_package_size
start = end - lc_package_size + 1
IN
( LINES OF it_rows from start to end )
)
)
).
Hope this helps. The code here is tested and works. Just copy-paste (and generate some data).

Group invoices by entity, with LOOP AT

My program outputs a list with some invoice and details and I want them to appear by entity.
The list shows the entity number and name repeating in every line of the list, but I want it to appear grouped by.
e.g. there are 4 invoices: two from each entity, how to show the entity and all of the respective invoices and then the other entity, and so on?
The code I have is this one:
FORM select_data3 CHANGING lt_data LIKE gt_map1.
FIELD-SYMBOLS: <fs_main> TYPE zimposto_consumo.
SELECT a~belnr d~spart a~bldat a~waers c~wrbtr a~hwaer c~dmbtr
INTO CORRESPONDING FIELDS OF TABLE lt_data
FROM ( ( bkpf AS a
INNER JOIN bsis AS c ON c~belnr = a~belnr
AND buzei = 1 )
INNER JOIN vbrk AS d ON d~xblnr = c~belnr )
WHERE a~gjahr in gjahr.
LOOP AT lt_data ASSIGNING <fs_main>.
clear <fs_main>-kbetr.
clear <fs_main>-fwste.
clear <fs_main>-hwste.
SELECT SINGLE kbetr fwste hwste FROM bset
INTO (<fs_main>-kbetr, <fs_main>-fwste, <fs_main>-hwste)
WHERE belnr = <fs_main>-belnr.
clear <fs_main>-koart.
SELECT SINGLE koart kunnr FROM bseg
INTO (<fs_main>-koart, wa_bseg-kunnr)
WHERE belnr = <fs_main>-belnr.
IF <fs_main>-koart = 'D'.
SELECT SINGLE name1 FROM kna1
INTO (wa_bseg-name1)
WHERE kunnr = wa_bseg-kunnr.
IF sy-subrc = 0.
FORMAT COLOR COL_TOTAL INTENSIFIED ON.
WRITE:/ sy-uline(137), / sy-vline NO-GAP,
2 'Entidade: ', wa_bseg-kunnr, wa_bseg-name1,
137 sy-vline NO-GAP, / sy-uline(137).
FORMAT COLOR COL_NORMAL INTENSIFIED OFF.
WRITE:/
sy-vline NO-GAP,
(16) <fs_main>-belnr NO-GAP,
sy-vline NO-GAP,
(16) <fs_main>-spart NO-GAP,
sy-vline NO-GAP,
(10) <fs_main>-bldat NO-GAP,
sy-vline NO-GAP.
perc = <fs_main>-kbetr / 10.
...
ENDIF.
ENDIF.
ENDLOOP.
ENDFORM.
I used a block AT NEW ... ENDAT inside the block LOOP AT ... ENDLOOP.