FOR expression in ABAP 7.40 – Best case scenarios
As we all know, the In-line declarations, operators and expressions available in 7.4 SP02 onward are taking the abap world by storm for last few years. For the new or the experienced abap-ers this is almost like fall ‘in-line’ or fall apart. 🙂 The main idea is to write code easily, keep it lean and compact as much as possible.
But there is no end of debate that if the old school abap code is good enough to achieve everything then why complicate life ?Well with all that being said let us discuss about some simple best case scenarios of FOR expression which can seriously reduce no of lines of code and make it more compact. I feel this is one of the best addition among the new features ( Remembering old friend Mr. ‘C’ 😉 ).
Situation 1: To move values from a source internal table to a target internal table which has all the fields of source internal table + some additional fields ( say ).
*Define structure TYPES: BEGIN OF ty_struct1, field1 TYPE i, field2 TYPE string, END OF ty_struct1, BEGIN OF ty_struct2, field1 TYPE i, field2 TYPE string, field3 TYPE i, END OF ty_struct2. *Define table types TYPES: gtt_struct1 TYPE STANDARD TABLE OF ty_struct1 WITH DEFAULT KEY, gtt_struct2 TYPE STANDARD TABLE OF ty_struct2 WITH DEFAULT KEY. * Initialize source table with some random values DATA(lt_source) = VALUE gtt_struct1( ( field1 = 1 field2 = 'A' ) ( field1 = 2 field2 = 'B' ) ). *Use like simple MOVE CORRESPONDING DATA(lt_target1) = VALUE gtt_struct2( FOR lwa_source IN lt_source ( CORRESPONDING #( lwa_source ) ) ). cl_demo_output=>display( lt_target1 ). *Populate sy-tabix in the additional fields within the for loop DATA(lt_target2) = VALUE gtt_struct2( FOR lwa_source IN lt_source INDEX INTO index LET base = VALUE ty_struct2( field3 = index ) IN ( CORRESPONDING #( BASE ( base ) lwa_source ) ) ). cl_demo_output=>display( lt_target2 ). *Populate any value or call custom method in the additional fields within the for loop DATA(lt_target3) = VALUE gtt_struct2( FOR lwa_source IN lt_source LET base = VALUE ty_struct2( field3 = 10 ) "<<< Custom method/any value IN ( CORRESPONDING #( BASE ( base ) lwa_source ) ) ). cl_demo_output=>display( lt_target3 ).
Points to Note:
i) VALUE (Type of the internal table) is used for the in-line declaration of the internal table. Here structure and Type declaration is required before using VALUE operator. If you want to use VALUE # then type of the table must be already available and defined in the program i.e. the internal table need to be declared before.
iii)LT_TARGET3-FIELD3 is populated with a hard coded value 10 but in real life a custom method call can also be done in this place to pass the actual value of field3
We can further enhance this by using line index option to specify from which row the FOR loop will start iterating. The below example illustrates how to populate a final table from the header, item and some other table data.
Sample code is as follows:
TYPES: BEGIN OF ty_final, ebeln TYPE ebeln, ebelp TYPE ebelp, lifnr TYPE lifnr, matnr TYPE matnr, maktx TYPE maktx, werks TYPE werks_d, name1 TYPE name1, END OF ty_final, ty_t_final TYPE STANDARD TABLE OF ty_final WITH DEFAULT KEY. START-OF-SELECTION. SELECT * FROM ekko UP TO 10 ROWS INTO TABLE @DATA(lt_ekko). IF sy-subrc = 0. SELECT * FROM ekpo INTO TABLE @DATA(lt_ekpo) FOR ALL ENTRIES IN @lt_ekko WHERE ebeln = @lt_ekko-ebeln. IF sy-subrc = 0. SELECT * FROM makt INTO TABLE @DATA(lt_makt) FOR ALL ENTRIES IN @lt_ekpo WHERE matnr = @lt_ekpo-matnr AND spras = @sy-langu. SELECT * FROM t001w INTO TABLE @DATA(lt_t001w) FOR ALL ENTRIES IN @lt_ekpo WHERE werks = @lt_ekpo-werks. *Sort the tables with proper key SORT: lt_ekko BY ebeln, lt_ekpo BY ebeln ebelp, lt_makt BY matnr, lt_t001w BY werks. *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 ) ) ). cl_demo_output=>display( lt_final ). ENDIF. ENDIF.
Situation 2: Suppose we have an internal table and we want to create another range table with the values of one particular field from that internal table
*Get details from DB table SELECT * FROM sflight INTO TABLE @DATA(lt_sflight) WHERE connid IN (17,555). IF sy-subrc = 0. *Prepare a range table DATA: lr_carrid TYPE RANGE OF s_carr_id. lr_carrid = VALUE #( FOR ls_value IN lt_sflight ( sign = 'I' option = 'EQ' low = ls_value-carrid ) ). SORT lr_carrid BY low. DELETE ADJACENT DUPLICATES FROM lr_carrid COMPARING low. cl_demo_output=>display( lr_carrid ). ENDIF.
Points to Note:
i) ‘@’ need to be used in the SELECT for in-line declaration of the internal table
Situation 3: There are 2 tables and based on which a third table need to be constructed. Target table has fields common from the first 2 source table.
*Define the structures of header & item table TYPES: BEGIN OF comp, ebeln TYPE ebeln, bukrs TYPE bukrs, END OF comp. *Declare table type TYPES: gtt_comp TYPE STANDARD TABLE OF comp WITH DEFAULT KEY, gtt_header TYPE STANDARD TABLE OF ekko WITH DEFAULT KEY, gtt_item TYPE SORTED TABLE OF ekpo WITH NON-UNIQUE KEY ebeln WITH NON-UNIQUE SORTED KEY key_combi COMPONENTS ebeln. *Populate dummy values DATA(lt_header) = VALUE gtt_header( ( ebeln = '4500000027' ) ( ebeln = '4500000028' ) ( ebeln = '4500000029' ) ). DATA(lt_item) = VALUE gtt_item( ( ebeln = '4500000027' ebelp = '000010' netwr = '100' bukrs = 'IND' ) ( ebeln = '4500000027' ebelp = '000020' netwr = '200' bukrs = 'IND' ) ( ebeln = '4500000027' ebelp = '000030' netwr = '300' bukrs = 'IND' ) ( ebeln = '4500000028' ebelp = '000010' netwr = '999' bukrs = 'USA' ) ( ebeln = '4500000029' ebelp = '000010' netwr = '25' bukrs = 'GB' ) ( ebeln = '4500000029' ebelp = '000020' netwr = '50' bukrs = 'GB' ) ( ebeln = '4500000029' ebelp = '000030' netwr = '100' bukrs = 'GB' ) ( ebeln = '4500000029' ebelp = '000040' netwr = '150' bukrs = 'GB' ) ). *Now populate the values of Compor from item table to a new table DATA(lt_comp) = VALUE gtt_comp( FOR ls_header IN lt_header FOR ls_item IN lt_item WHERE ( ebeln = ls_header-ebeln ) ( ebeln = ls_header-ebeln bukrs = ls_item-bukrs ) ). SORT lt_comp BY ebeln. DELETE ADJACENT DUPLICATES FROM lt_comp COMPARING ebeln. cl_demo_output=>display( lt_comp ).
Points to note
i) VALUE (Type of the internal table) is used for the in-line declaration of the internal table.
Situation 4: Summation of item values and populate the total in header table
- Use the code from situation 3 for the declaration part
*Now sum up all the netwr for a particular purchase order LOOP AT lt_comp ASSIGNING FIELD-SYMBOL(<lfs_comp>). <lfs_comp>-netwr = REDUCE netwr( INIT lv_netwr TYPE netwr FOR ls_item IN FILTER #( lt_item USING KEY key_combi WHERE ebeln = <lfs_comp>-ebeln ) NEXT lv_netwr = lv_netwr + ls_item-netwr ). ENDLOOP. cl_demo_output=>display( lt_comp ).
Points to note:
i) LT_ITEM has to be sorted table for using the FILTER option
ii) REDUCE with FOR is like a nested loop only difference is that you can’t debug all the iterations
Situation 5: Append new values into an internal table which already have some values
- Use the code from situation 3 for the declaration part
*Append new records in item table lt_item = VALUE #( BASE lt_item ( ebeln = '4500000030' ebelp = '000010' netwr = '1099' bukrs = 'CAN' ) ). cl_demo_output=>display( lt_item ).
Points to Note:
i) Here VALUE with # has been used since type of LT_ITEM is already defined
All the above 5 examples are simple and self explanatory. If you try them in your system I am sure it will be easier to grasp. If you have any questions let me know. There are tons of feature added with the new expressions and operators. Please feel free to add any other good use of FOR expression, if I have missed. I am planning to write more in this space like when to use FOR ( New ABAP ) and when to use LOOP ( old abap ) and related topics in future.
https://blogs.sap.com/2014/09/30/abap-news-for-740-sp08-iteration-expressions/ By Horst Keller
Nice blog. Very nice way to solve the 10.000 rows of old-school coding style, even though FIELD-SYMBOLS are already a very powerful instrument like pointers in C, and many others instructions in ABAP too.
I like it but sincerely we've a problem all over that.
Even if we can try to complicate code specifics to protect real coders from newbies and parasites, we will never be able to make CTRL+C & CTRL+V illegal.
So, dear friends, this is totally unuseful against human being capacity to study how to avoid a job .
Someone will have to learn this new style and working hard to make it a standard way to code and someone else will have always a ready soup .. what's the difference? 🙂
In my opinion this is not a real evolution , but a way to create jobs and that's ok too I suppose 🙂
I don't think it will ever replace the old school abap completely. Just that you have to know where to use what to make life easier. Also if some one does copy paste he has to understand the application otherwise troubleshooting will be difficult. Isn't it? 🙂
Yes sure will be difficult but only for who have fixed term contracts and the new generations that will have to rebuild all this garbage . and furthermore they still have to use old programming style, perhaps a little cleaner, because old generations, with more job rights, do not want to know how to learn 🙁
<Sigh> There are always people that will tell me the old way is better. I don't think the "old" way should be thrown away. There are some take forward things... But suppose we didn't change the way we developed. Who would want to be developing with punch cards? Machine language only? We have to evolve. Copy / paste is just too easy.
However, I'm keeping this blog in my back pocket. It is worth it to try to show people the advantages.
It's a hard balance for me to achieve. I love new tools and/or ways of doing things are like a new universe to explore. I can't wait to get my hands on the new toys. Lately there have been huge advantages to watching and adapting to the new tools. I can't use them yet, but soon........
It comes down to a hard question for me. I want to do it because is fun vs. I want to do it because of the functional benefits. This blog will help the discussion along.
Glad that you liked my blog. I just feel that the new features need to be used prudently and not blindly. If there is a small calculation based loop or simple table population then FOR expression is best to be used. But one disadvantage is that its bit dodgy to debug each iteration of FOR which is possible in normal LOOP..ENDLOOP. Anyway good luck with your journey with the new tools..am sure you will enjoy it.
Regarding debugging: Have you tried the "Step Size" button in the debugger?
Yes this works great. We can break the statements within the nested expressions and check the values. Thanks for pointing this out.
First, thanks for your informatiove blog.
Regarding scenario 2:
Lately I like to use this pattern to generate a range table from DB values:
Just wow...Always wanted this feature in ABAP. Great input Daniel. Thanks.
Yeah, very helpfull indeed!
I was wondering if that could also be done with inline-declaration - at first it looks as this might be the case, but no, that's not a propper range table created that way:
Yes in-line declaration is not possible in this case. But it still saves quite a few code lines.
This is really a great content Atanu Mukherjee,
I would just like to ask a question on your parallel cursor I see that the lt_final will only be populated if all the fields have data, Is there a method to accept or display all data even if i field is missing in value.
For example no records was found for makt table and you still want to display all purchasing document item found.
Keep it up 🙂
I would not consider that coding a true form of a Parallel Cursor, since there is also sequential search involved with statement LINE_INDEX( table2[…] ) for each row of table1, because table2 is STANDARD TABLE. But even if table2 would be SORTED or HASHED, there is still a binary or hash search involved. So I would call this a ‘bad mixture’ of (Sequential/Binary/Hash Search and Parallel Cursor with on top 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 table2 can be accessed by key.
In a true Paralle Cursor, you have 2 ‘sorted’ tables but 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).
I would love to see that with the new notation, but unless SAP provides an operator for it with a clean interface, it is going to look ugly and messy if you have to do it yourself, the new way being even ‘worse’ than the old way. One reason SAP might not want to approach this, is because of the possible variations and knowledge about the input data that will lead to a performance gain, but on the other hand, the gain might not be too convincing to persue this (e.g. O(n) vs O(n log n).
Thanks for the content Atanu Mukherjee,
I have the Same Query as Riv Baltazar
"For example no records was found for makt table and you still want to display all purchasing document item found. "
How this can be resolved using For expression ?
Hi Atanu Mukherjee,
Thanks for sharing this wonderful blog.
Is there a way we can read another table inside for loop? Of use If line_Exists inside this for loop?
Thanks & Regards
Hi Atanu Mukherjee,
Really appreciate your unselfish sharing, it always took huge amount of time but it will provide super big benefits to others, especially for the newbies like me.
Wish you could have more sharing.
I am filling internal table from 6 internal tables, my requirement is ( lt_main ) has to fill for all the rows of lt_equi2.
while filling some rows are missing, if all the matching fields were not found in corresponding internal tables .
Ex : if field iloan is not found in table it_iflot for wa_equi2-iloan , then that row fields correspong to lt_equi2 missing in output.
data(lt_main) = VALUE tt_main( for wa_equi2 in it_equi2
for wa_iflot in it_iflot where ( iloan = wa_equi2-iloan )
for wa_eqkt in it_eqkt where ( equnr = wa_equi2-equnr )
for wa_mseg in it_mseg where ( mblnr = wa_equi2-mblnr and mjahr = wa_equi2-mjahr )
for wa_prps in it_prps where ( aufnr = wa_mseg-nplnr or aufnr = wa_mseg-aufnr )
for wa_tcj4t in it_tcj4t where ( profidproj = wa_prps-profl )
( scheme = wa_tcj4t-profi_txt
proj = wa_prps-post1
posid = wa_prps-posid
pspnr = wa_prps-pspnr
iwerk = wa_equi2-iwerk
kostl = wa_iflot-kostl
ltext = wa_iflot-ltext
equnr = wa_equi2-equnr+9(9)
eqktx = wa_eqkt-eqktx
inbdt = wa_equi2-inbdt
herst = wa_equi2-herst
serge = wa_equi2-serge
groes = wa_iflot-groes
tplnr = wa_iflot-tplnr
pltxt = wa_iflot-pltxt
anlnr = wa_iflot-anlnr
drawo = wa_equi2-zzdrawing_off
drawd = wa_equi2-ltext
aufnr = wa_mseg-nplnr
aedat = wa_equi2-datlwb ) ).
thanks in adavance.
Hello, I always wonder is there any solution for this.
Here, we will be getting an error stating <FS_STRUCT> used in the 2nd expression is not compatible since LT_FINAL is of a different structure compared to Lt_table.
Is there any way to unassign <FS_STRUCT> after the 1st statement? So that, it can be used in the next forthcoming FOR statements for different tables.
The normal UNASSIGN statement can't be used to unassign <fs_struct> since it won't have scope outside the FOR loop.