2020 Mar 11 8:49 AM
Hi Experts,
I have read in a blog about how to populate internal table using Parallel cursor in For loop iterations. Sharing the sample code below.
*Populate the final table based on the data from EKKO,EKPO,MAKT and T001W using "Parallel Cursor"
DATA(lt_final) = VALUE ty_t_final( FOR ls_ekpo IN lt_ekpo
FOR ls_ekko IN lt_ekko FROM line_index( lt_ekko[ ebeln = ls_ekpo-ebeln ] )
WHERE ( ebeln = ls_ekpo-ebeln )
FOR ls_makt IN lt_makt FROM line_index( lt_makt[ matnr = ls_ekpo-matnr ] )
WHERE ( matnr = ls_ekpo-matnr )
FOR ls_t001w IN lt_t001w FROM line_index( lt_t001w[ werks = ls_ekpo-werks ] )
WHERE ( werks = ls_ekpo-werks )
LET ls_final = VALUE ty_final(
lifnr = ls_ekko-lifnr
maktx = ls_makt-maktx
name1 = ls_t001w-name1 )
IN ( CORRESPONDING #( BASE ( ls_final ) ls_ekpo ) ) ).
When comparing this with traditional way of parallel cursor, On going in to the loop of ITAB2 after reading the tabix of row which matches with key, we will check for the keys and exit the loop if it doesn't match. But in the above shared code, we are scanning all the records in the loop with the matching keys. Can't it be restricted like exiting out of loop if it doesnt match?How will this impact?
2020 Mar 24 1:47 PM
All proposed solutions (see comments under question) show that there's no benefit to do parallel cursor with constructor expressions, without obfuscating the objective a lot.
2020 Mar 11 9:02 AM
@sandra.rossi @matthew.billingham - Experts pls suggest on this!
2020 Mar 11 9:07 AM
2020 Mar 11 9:48 AM
Why do you use "parallel cursor", why don't you simply use tables with keys of type "sorted"?
NB: Please use the CODE button to format your code so that it's shown in a more user-friendly format (colorized).
2020 Mar 11 10:28 AM
Thanks for your responses! @Frederic Girod - We are not in to CDS yet. We are in ABAP 7.4 in oracle DB.
Edited with code option! Thanks Sandra! When we had a discussion on using new ABAP syntaxes, we came across this question of how can we do a parallel cursor method using new syntaxes and that's how we found the above way. But still, using that way we cannot exit the loop if the keys are not matching which again became a point.
Is there any other alternate?
2020 Mar 11 11:46 AM
EDIT (after Michael Piesche comment): my comment is completely incorrect, please forget it.
Looping on sorted tables is the same as parallel cursor, I don't understand why you want to use the old logic. Parallel cursor is only when you have standard tables. You can't leave a FOR iteration.
2020 Mar 12 12:01 PM
sandra.rossi, a parallel cursor is always performed on two 'sorted' tables (manually sorted 'STANDARD TABLE' or automatically 'SORTED TABLE'), where you match the records by only stepping through the rows and only comparing the values. Finding the next match is therefore done in O(1), resulting in total O(n).
This is not the same, especially in terms of performance, as a double loop on TYPE SORTED TABLES, where you match the record each time by actually searching by key. Finding the next match is therefore done in O(log n), resulting in total O(n log n).
There are different ways to setup a parallel cursor, depending on the knowledge about the two tables. Its most simple form can be used, if you have 1:1 record matches in both tables. The most sophisticated implemantation is neccessary, if you have 0..n:0..m matches. A parallel cursor makes 'only' sense, if most of the values in both table have matches and both tables have a rather large amount of rows (otherwise there is not performance gain, or it is not justifiyable enough for the extra development 'effort').
I normally would try to avoid parallel cursors, as their setup/development/testing is more time consuming and therefore use sorted or hashed tables and search with primary or secondary keys instead. But when I have to deal with 100K or even 1M of records in two or more tables that need to be matched, I will have to switch to parallel cursor to improve performance for certain.
Therefore, if it is possible and the performance is the same as the 'traditional' way, I would love to see how parallel cursor is done with the new ABAP syntax 😉
(I didnt have time to look into that yet)
2020 Mar 12 12:47 PM
anabaperlife, I found the blog you are referring to (for-expression-in-abap-7.40-best-case-scenarios).
I would actually consider that coding not a true form of a Parallel Cursor, since there is also binary search involved with statement LINE_INDEX(...) for each row of table1. So I would call this a 'mixture' of Binary Search and Parallel Cursor with the concern of not being able to stop the FOR iteration when there clearly are no further matches (Therefore in this mixed implementation, 'exiting' would be only possible if you would also knew the LINE_INDEX for the next value and set it (minus 1) as TO row: e.g. FOR...FROM...TO..., but for that you need to do a lookahead). And because it is mixed, I see no true value in doing it this way, compared to accessing the matching records by a key-access, assuming table two can be accessed by key.
In a true Paralle Cursor, you have two 'sorted' tables and you only step through the rows of table1 and in parallel through the rows of table2, by notating the current rowindex and depending on a comparison (=, <,>) you either find a match and move on, or you step to the next row of table1 in case of < and to the next row of table2 in case of >. Based on the knowledge of the record matches (1:1 or 0..n:0..m), the comparison can be either neglected (1:1) or simplified (1:m) and making the decision on whether to move to the next record in table1 or table2 can be simplified to always move both cursors (1:1).
2020 Mar 12 1:00 PM
michael.piesche You're right. Thanks a ton for correcting me, I didn't think enough !
2020 Mar 12 8:00 PM
1) You can't exit FOR iterations.
2) The only close workaround is to use FROM and TO: (EDIT: see Quynh comment proposing the alternative UNTIL)
FOR <line1> IN outer_itab
FOR <line2> IN inner_itab FROM start_line TO end_line
3) So, you need to first calculate the start and end indexes of lines by doing an additional iteration on the inner table. The extra time won't be noticed if there are many lines in the outer table.
4) I investigated how it's possible to do it in one constructor expression. I can do it only by cheating by calling a method in the expression + using a kind of global variable. If I do it with the maximum of constructor expression, by using FOR ... GROUP BY, I get a slower result than if I use classic coding. The best performance I can get is approximately the same performance as the classic parallel cursor. But the final code is just overkilling, unclear, ugly, so my conclusion is that it's not worth the pain to use a constructor expression for the parallel cursor.
Here's my test code:
CLASS ltc_main DEFINITION
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PUBLIC SECTION.
CLASS-METHODS class_constructor.
PRIVATE SECTION.
METHODS parallel_cursor_with_for FOR TESTING.
METHODS parallel_cursor_classic FOR TESTING.
TYPES: BEGIN OF ty_group,
carrid TYPE spfli-carrid,
index TYPE i,
size TYPE i,
END OF ty_group,
ty_groups TYPE STANDARD TABLE OF ty_group WITH EMPTY KEY.
CLASS-METHODS get_indexes_in_spfli
IMPORTING groups TYPE ty_groups
carrid TYPE spfli-carrid
RETURNING VALUE(group) TYPE ty_group.
CLASS-DATA groups_spfli_index TYPE i.
CLASS-DATA scarr_s TYPE SORTED TABLE OF scarr WITH UNIQUE KEY carrid.
CLASS-DATA spfli_s TYPE SORTED TABLE OF spfli WITH NON-UNIQUE KEY carrid connid.
TYPES: BEGIN OF ty_s_final,
carrid TYPE scarr-carrid,
carrname TYPE scarr-carrname,
connid TYPE spfli-connid,
cityfrom TYPE spfli-cityfrom,
END OF ty_s_final,
ty_t_final TYPE STANDARD TABLE OF ty_s_final WITH EMPTY KEY.
ENDCLASS.
CLASS ltc_main IMPLEMENTATION.
METHOD class_constructor.
SELECT * FROM scarr INTO TABLE scarr_s.
SELECT * FROM spfli INTO TABLE spfli_s.
DO 10 TIMES.
INSERT LINES OF spfli_s INTO TABLE spfli_s.
ENDDO.
ENDMETHOD.
METHOD parallel_cursor_with_for.
DO 100 TIMES.
" I can't avoid this code outside the constructor expression, because
" it's faster than FOR ... GROUP BY.
CHECK spfli_s IS NOT INITIAL. " mandatory otherwise algorithm is wrong
DATA(group_spfli) = VALUE ty_group( carrid = spfli_s[ 1 ]-carrid index = 1 ).
DATA(groups_spfli) = VALUE ty_groups( ).
LOOP AT spfli_s ASSIGNING FIELD-SYMBOL(<spfli2>).
IF <spfli2>-carrid = group_spfli-carrid.
ADD 1 TO group_spfli-size.
ELSE.
APPEND group_spfli TO groups_spfli.
ADD group_spfli-size TO group_spfli-index.
group_spfli-size = 1.
group_spfli-carrid = <spfli2>-carrid.
ENDIF.
ENDLOOP.
APPEND group_spfli TO groups_spfli.
groups_spfli_index = 0.
DATA(lt_final) = VALUE ty_t_final(
* "FOR ... GROUP BY is slower than classic algorithm.
* LET groups_spfli = VALUE ty_groups(
* FOR GROUPS <group_spfli> OF <spfli> IN spfli_s
* GROUP BY ( carrid = <spfli>-carrid index = GROUP INDEX size = GROUP SIZE )
* WITHOUT MEMBERS
* ( carrid = <group_spfli>-carrid
* index = <group_spfli>-index
* size = <group_spfli>-size ) )
* IN
FOR <scarr> IN scarr_s
LET spfli_index = get_indexes_in_spfli( groups = groups_spfli carrid = <scarr>-carrid )
IN
FOR <spfli> IN spfli_s FROM spfli_index-index TO spfli_index-index + spfli_index-size - 1
( carrid = <scarr>-carrid
carrname = <scarr>-carrname
connid = <spfli>-connid
cityfrom = <spfli>-cityfrom ) ).
ENDDO.
ENDMETHOD.
METHOD get_indexes_in_spfli.
ADD 1 TO groups_spfli_index.
DO.
READ TABLE groups INDEX groups_spfli_index INTO group.
IF sy-subrc <> 0 OR group-carrid = carrid.
EXIT.
ELSEIF group-carrid > carrid.
SUBTRACT 1 FROM groups_spfli_index.
CLEAR group.
EXIT.
ENDIF.
ENDDO.
ENDMETHOD.
METHOD parallel_cursor_classic.
DATA spfli_index TYPE i.
DO 100 TIMES.
DATA(lt_final) = VALUE ty_t_final( ).
spfli_index = 0.
LOOP AT scarr_s ASSIGNING FIELD-SYMBOL(<scarr>).
ADD 1 TO spfli_index.
LOOP AT spfli_s FROM spfli_index ASSIGNING FIELD-SYMBOL(<spfli>).
IF <spfli>-carrid >= <scarr>-carrid.
EXIT.
ENDIF.
ENDLOOP.
LOOP AT spfli_s FROM spfli_index ASSIGNING <spfli>.
ADD 1 TO spfli_index.
IF <spfli>-carrid > <scarr>-carrid.
EXIT.
ENDIF.
lt_final = VALUE #( BASE lt_final
( carrid = <scarr>-carrid
carrname = <scarr>-carrname
connid = <spfli>-connid
cityfrom = <spfli>-cityfrom ) ).
ENDLOOP.
ENDLOOP.
ENDDO.
ENDMETHOD.
ENDCLASS.
2020 Mar 13 6:30 AM
sandra.rossi, thank you so much for the effort. Thats what I was thinking, that it is going to be ugly 😉
Thats why I dont like parallel cursor that much, they are truely messy, compared to other 'clean code'.
But they still prove, that in certain settings, more code can still mean better performance.
So, use with caution.
2020 Mar 16 3:19 AM
Just want to share what I tried:
1. I try to overcome the "exiting out of loop if it doesnt match" by using FILTER, it may not as effective as parallel cursor since I have to create a temporary table in middle:
DATA(lt_final) = VALUE ty_t_final( FOR ls_ekpo IN lt_ekpo
LET ekko_filter = FILTER #( lt_ekko USING KEY _ebeln WHERE ebeln = ls_ekpo-ebeln ) IN
FOR f1 IN ekko_filter
LET makt_filter = FILTER #( lt_makt USING KEY _matnr WHERE matnr = ls_ekpo-matnr ) IN
FOR f2 IN makt_filter
LET t001w_filter = FILTER #( lt_t001w USING KEY _werks WHERE werks = ls_ekpo-werks ) IN
FOR f3 IN t001w_filter
LET ls_final = VALUE ty_final( lifnr = f1-lifnr
maktx = f2-maktx
name1 = f3-name1 ) IN
( CORRESPONDING #( BASE ( ls_final ) ls_ekpo ) ) ).
2. Instead of FOR ..FROM..TO, i tried FOR...UNTIL:
DATA(lt_final) = VALUE ty_t_final( FOR ls_ekpo IN lt_ekpo
FOR j = line_index( lt_ekko[ ebeln = ls_ekpo-ebeln ] )
UNTIL VALUE #( lt_ekko[ j ]-ebeln OPTIONAL ) <> ls_ekpo-ebeln
FOR k = line_index( lt_makt[ matnr = ls_ekpo-matnr ] )
UNTIL VALUE #( lt_makt[ k ]-matnr OPTIONAL ) <> ls_ekpo-matnr
FOR l = line_index( lt_t001w[ werks = ls_ekpo-werks ] )
UNTIL VALUE #( lt_t001w[ l ]-werks OPTIONAL ) <> ls_ekpo-werks
LET ls_final = VALUE ty_final(
lifnr = VALUE #( lt_ekko[ j ]-lifnr OPTIONAL )
maktx = VALUE #( lt_makt[ k ]-maktx OPTIONAL )
name1 = VALUE #( lt_t001w[ l ]-name1 OPTIONAL ) )
IN ( CORRESPONDING #( BASE ( ls_final ) ls_ekpo ) ) ).
The first one have similar result as your original code, the second one have a little difference in case the middle table doesn't match the condition it still go to next table instead of no record return ( look like a inner join vs left/right join
2020 Mar 16 8:38 AM
quynh.doanmanh In fact it's no more "parallel cursor", because you do a binary search at each loop, so the performance is much slower. To verify it, I have converted below your solution so that it fits the complete verifiable example I had published previously, so that it can be compared (I get x4 difference).
NB: your codes does a left outer join via UNTIL, and you can change it to an inner join by using WHILE instead.
METHODS parallel_cursor_with_for_until FOR TESTING.
METHODS parallel_cursor_with_for_while FOR TESTING.
METHOD parallel_cursor_with_for_until.
DO 100 TIMES.
DATA(lt_final) = VALUE ty_t_final(
FOR <scarr> IN scarr_s
FOR i = line_index( spfli_s[ carrid = <scarr>-carrid ] )
UNTIL i = 0 " LEFT OUTER JOIN
OR i > lines( spfli_s )
OR spfli_s[ i ]-carrid <> <scarr>-carrid
( carrid = <scarr>-carrid
carrname = <scarr>-carrname
connid = VALUE #( spfli_s[ i ]-connid OPTIONAL )
cityfrom = VALUE #( spfli_s[ i ]-cityfrom OPTIONAL ) ) ).
ENDDO.
ENDMETHOD.
METHOD parallel_cursor_with_for_while.
DO 100 TIMES.
DATA(lt_final) = VALUE ty_t_final(
FOR <scarr> IN scarr_s
FOR i = line_index( spfli_s[ carrid = <scarr>-carrid ] )
WHILE i <> 0 " INNER JOIN
AND i <= lines( spfli_s )
AND spfli_s[ i ]-carrid = <scarr>-carrid
( carrid = <scarr>-carrid
carrname = <scarr>-carrname
connid = spfli_s[ i ]-connid
cityfrom = spfli_s[ i ]-cityfrom ) ).
ENDDO.
ENDMETHOD.
2020 Mar 24 10:29 AM
Thanks all for your valuable feedbacks and efforts! Since the heading matches with my next question,im posting it here.
I'm trying to debug this FOR iterations. I saw a blog where STEP SIZE in debugger mode is used for that. But i couldn't exactly see the results of every iterations using that.
Could you pls guide on how to debug the iterations? TIA
2020 Mar 24 1:44 PM
Meera K Please ask a new question.
And please don't name it "My current question" and ask all your future questions there 😉
2020 Mar 24 1:47 PM
All proposed solutions (see comments under question) show that there's no benefit to do parallel cursor with constructor expressions, without obfuscating the objective a lot.
2020 Sep 10 10:28 PM
Hi experts,
Can you please provide an example in which you have two or more nested loops and filling a third internal table? What I need to do is to translate the following nested loop to ABAP 7.4 FOR Iteration Expression?
LOOP AT it_tempo INTO <fs_tempo>.
CLEAR wa_result.
MOVE-CORRESPONDING <fs_tempo> TO wa_result.
wa_result-pur_orders = wa-pur_orders.
wa_result-unrestricted_stck = wa-unrestricted_stck.
LOOP AT it_marc ASSIGNING <fs_marc> WHERE matnr = <fs_tempo>-matnr AND
werks = p_werks AND
lvorm = space.
wa_result-werks = <fs_marc>-werks.
READ TABLE it_makt ASSIGNING <fs_makt> WITH KEY matnr = <fs_tempo>-matnr
spras = sy-langu.
IF sy-subrc = 0.
wa_result-maktx = <fs_makt>-maktx.
ENDIF.
READ TABLE it_t023t ASSIGNING <fs_t023t> WITH KEY matkl = <fs_tempo>-matkl
spras = sy-langu.
IF sy-subrc = 0.
wa_result-wgbez = <fs_tempo>-wgbez.
ENDIF.
LOOP AT it_eina ASSIGNING <fs_eina> WHERE matnr = <fs_tempo>-matnr AND
loekz = ' '.
wa_result-lifnr = <fs-eina>-lifnr.
LOOP AT it_eine ASSIGNING <fs_eine> WHERE infnr = <fs_eina>-infnr AND
loekz = ' '.
wa_result-netpr = <fs_eine>-netpr.
IF <fs_eine>-peinh = 0.
wa_result-peinh = 1.
ELSE.
wa_result-peinh = <fs_eine>-peinh.
ENDIF.
APPEND wa_result TO ti_result.
ENDLOOP.
CLEAR: wa_result-lifnr, wa_result-name1.
ENDLOOP.
ENDLOOP.
ENDLOOP.
***********************************************
I'll be thankful if you can help me out with this big issue I have.
Carlos.
2021 Nov 10 9:27 PM
2024 Apr 23 2:03 PM
Hi experts,
I want to create a new itab table where one of the current field will have a fixed value.
data(lt_spfli_sel) = value ltt_spfli_sel( for ls_sp in lt_spfli ( MANDT = ls_sp-mandt CARRID = 'FR'
CONNID = ls_sp-CONNID COUNTRYFR = ls_sp-COUNTRYFR ).
This command works, but I want to avoid writing all the fields of itab lt_spfli via connid = ls_sp-connid contryfr = ls_sp-countryfr etc.
The corresponding old coding would be
loop at lt_spfli assigning field-symbol(<ls_fs_spfli>).
<ls_fs_spfli>-carrid = 'FR'.
append <ls_fs_spfli> to lt_spfli_sel.
endloop.
2024 Aug 29 9:19 AM
Hi @Wanna5 ,
Hope the below code helps
data(lt_spfli_sel) = value ltt_spfli_sel( for <fs_sp> in lt_spfli
( VALUE #( BASE CORRESPONDING #( <fs_sp> )
CARRID = 'FR' ) ) ).
Thanks.