Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

ABAP 7.4 Syntax - FOR Loop iteration

ishwarya_doss
Participant
0 Kudos
28,363

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?

1 ACCEPTED SOLUTION

Sandra_Rossi
Active Contributor
7,811

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.

19 REPLIES 19

ishwarya_doss
Participant
0 Kudos
7,811

@sandra.rossi @matthew.billingham - Experts pls suggest on this!

FredericGirod
Active Contributor
0 Kudos
7,811

Is there a reason to not used a CDS view ?

Sandra_Rossi
Active Contributor
7,811

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).

ishwarya_doss
Participant
0 Kudos
7,811

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?

Sandra_Rossi
Active Contributor
7,811

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.

michael_piesche
Active Contributor
7,811

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)

michael_piesche
Active Contributor
7,811

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).

Sandra_Rossi
Active Contributor
0 Kudos
7,811

michael.piesche You're right. Thanks a ton for correcting me, I didn't think enough !

Sandra_Rossi
Active Contributor
7,811

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.

michael_piesche
Active Contributor
7,811

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.

DoanManhQuynh
Active Contributor
7,811

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

Sandra_Rossi
Active Contributor
7,811

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.

ishwarya_doss
Participant
0 Kudos
7,811

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

Sandra_Rossi
Active Contributor
7,811

Meera K Please ask a new question.

And please don't name it "My current question" and ask all your future questions there 😉

Sandra_Rossi
Active Contributor
7,812

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.

carlos_mayka
Explorer
0 Kudos
7,811

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.

0 Kudos
7,811

following....

Wanna5
Newcomer
0 Kudos
6,282

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.

0 Kudos
3,933

Hi @Wanna5 ,
Hope the below code helps

data(lt_spfli_selvalue ltt_spfli_selfor <fs_sp> in lt_spfli 
                                          VALUE #BASE CORRESPONDING #<fs_sp> 
                                                                     CARRID 'FR' )

Thanks.