When we design the Function Module, we can specify either Pass by Value or Pass by Reference for a parameter. It is very important to pay close attention while designing the FM where we are using the Table Types as the variables.
Have you ever seen a situation where you can see the data in internal table but it doesn’t LOOP further. It exits out of the LOOP and only processes that LOOP only. Sounds weird?
Let’s see this code. We have an ITAB which has 5 records. We pass this ITAB to the FM ZTEST_NP_PASS_BY_VALUE as parameter CT_VBAP. This parameter is Pass by Value. In the FM, we copy the data from the importing parameter to local table LT_VBAP. We Sort the table LT_VBAP and delete adjacent duplicates which would leave only one record in the LT_VBAP. Ideally, it should not change anything in the CT_VBAP. But the output is surprising.
*&---------------------------------------------------------------------*
*& Purpose: Illustrates dangerous use of Pass by Value
*& Author : Naimesh Patel
*&---------------------------------------------------------------------*
REPORT ztest_np_itab_copy.
DATA: lt_vbap TYPE STANDARD TABLE OF vbap.
DATA: lv_index TYPE i.
*PARAMETERS: p_vbeln TYPE vbak-vbeln DEFAULT '1001234567'.
START-OF-SELECTION.
*----- Pass by Value --------*
PERFORM f_fill_data.
WRITE: / 'Pass by Value'.
WRITE: /(5) space, 'Total lines in LT_VBAP'.
LOOP AT lt_vbap TRANSPORTING NO FIELDS WHERE vbeln IS NOT INITIAL.
WRITE: / sy-tabix.
ENDLOOP.
WRITE: /(5) space, 'FM Call - Parameter Pass by Value'.
LOOP AT lt_vbap TRANSPORTING NO FIELDS WHERE vbeln IS NOT INITIAL.
WRITE : /(10) space, 'Processing', sy-tabix.
CALL FUNCTION 'ZTEST_NP_PASS_BY_VALUE'
CHANGING
ct_vbap = lt_vbap.
ENDLOOP.
* Total Lines
lv_index = LINES( lt_vbap ).
WRITE: /(5) space, 'Total lines in LT_VBAP', lv_index.
*&---------------------------------------------------------------------*
*& Form F_FILL_DATA
*&---------------------------------------------------------------------*
FORM f_fill_data .
DATA: lwa_vbap LIKE LINE OF lt_vbap.
DATA: l_vbeln TYPE vbak-vbeln.
l_vbeln = '1001234567'.
DO 5 TIMES.
lwa_vbap-vbeln = l_vbeln.
lwa_vbap-posnr = sy-index * 100.
APPEND lwa_vbap TO lt_vbap.
ENDDO.
ENDFORM. " F_FILL_DATA
This is the FM ZTEST_NP_PASS_BY_VALUE:
FUNCTION ZTEST_NP_PASS_BY_VALUE.
*"----------------------------------------------------------------------
*"*"Local Interface:
*" CHANGING
*" VALUE(CT_VBAP) TYPE VBAP_T
*"----------------------------------------------------------------------
DATA: li_vbap TYPE STANDARD TABLE OF vbap.
li_vbap = ct_vbap.
SORT li_vbap BY vbeln.
DELETE ADJACENT DUPLICATES FROM li_vbap COMPARING vbeln.
ENDFUNCTION.
Here is the output of the above code:
You can see that there are 5 lines in the LT_VBAP. After FM was called for the first record, it didn’t LOOP further. But the total number of lines in the table LT_VBAP are still 5.
What happened here:
Till the statement which copies the data from the CT_VBAP to LI_VBAP, it is referring to same memory reference which can be checked in the debugger. Once the FM passes control back to the called program, some how it is loosing the track of the T_VBAP and put the data from LI_VBAP back to T_VBAP which has essentially only 1 record. It is weird that the Debugger also shows that still T_VBAP has 5 rows. But the LOOP finishes after processing the 1st record as seen in the output of the report.
Impact
Imagine if this type of FM call with Pass by Value is in the User-exit which could be deep down burried in the call stack and LOOP doesn’t processes all the records in the starndard SAP code, it would be pretty difficult to figure out what happend and why it was happened.
How to save this data from Data Loss?
We need to use the table with reference. This avoid us loosing the data and keeps the data consistency.
Code Snippet to demo pass by Reference
*----- Pass by Reference ---- *
SKIP 2.
CLEAR lt_vbap.
PERFORM f_fill_data.
WRITE: / 'Pass by Reference'.
WRITE: /(5) space, 'Total lines in LT_VBAP'.
LOOP AT lt_vbap TRANSPORTING NO FIELDS WHERE vbeln IS NOT INITIAL.
WRITE: / sy-tabix.
ENDLOOP.
WRITE: /(5) space, 'FM Call - Parameter with Reference'.
LOOP AT lt_vbap TRANSPORTING NO FIELDS WHERE vbeln IS NOT INITIAL.
WRITE : /(10) space, 'Processing', sy-tabix.
CALL FUNCTION 'ZTEST_NP_REFERENCE'
CHANGING
ct_vbap = lt_vbap.
ENDLOOP.
* Total Lines
lv_index = LINES( lt_vbap ).
WRITE: /(5) space, 'Total lines in LT_VBAP', lv_index.
Code Snippet FM ZTEST_NP_REFERENCE
FUNCTION ztest_np_reference.
*"----------------------------------------------------------------------
*"*"Local Interface:
*" CHANGING
*" REFERENCE(CT_VBAP) TYPE VBAP_T
*"----------------------------------------------------------------------
DATA: lt_vbap TYPE STANDARD TABLE OF vbap.
lt_vbap = ct_vbap.
SORT lt_vbap BY vbeln.
DELETE ADJACENT DUPLICATES FROM lt_vbap COMPARING vbeln.
ENDFUNCTION.
I think that there is no data loss in lt_vbap internal table at "Code Snippet to demo Pass by Value". You can check this by putting this piece of code:
loop at lt_vbap transporting no fields where vbeln is not initial.
write:/ '_', sy_tabix.
endloop.
just before the "GET REFERENCE OF lt_vbap INTO lo_data.".
The only loss that happens after "call function …" is that loop administration of internal table (current line index, etc..) has been reset. That's why system goes out from loop after first "call function …".
Have a nice day and thanks for interesting post, Naimesh.
Thanks for your view on this.
You said
The only loss that happens after "call function …" is that loop administration of internal table (current line index, etc..) has been reset. That's why system goes out from loop after first "call function …".
That's point of this post. That after the CALL FUNCTION, it ignores the next iterations. It doesn't loose the data but it doesn't event go to loop iteration.
Regards,
Naimesh Patel
Agree.
What can we learn: It is always faster to pass data by reference. A CHANGING parameter should be used only if we want to CHANGE the data. Everey change to a whole internal table within a loop should be handled with extreme care.
The pass by value using a CHANGING parameter means that the table is copied onto itself destroying any active cursors as here the loop cursor.
In the view of performance, call by value is wrong by default.
Thank you for the statement "GET REFERENCE OF lt_vbap INTO lo_data." This is really good to confuse the reader as lo_data is not used in the program.
Good brain gymnastics!
Hello Clemens,
Good Summary.
Regarding the GET REFERENCE: Actually I was trying to see if it uses the same absolute name to check if it uses different memory. Anyways, I have removed it.
Regards,
Naimesh Patel