If changes are made to the code, then there is no guarantee that previous assumptions are satisfied. SAP ABAP checkpoints can be used for making sure that program correctness is maintained. Under checkpoints, we consider Assertions, Breakpoint, and Log points.
Assertions can be used for improving the quality of software. Breakpoint and Log point are used for investigating program behavior in case of problems. They help in understanding and maintaining the code.
The transaction SAAP has been there for quite some time but it is a pity not many ABAPers are aware of it. Therefore we thought our SAPYard family members should know about this useful tool. Better late than never. It is such a handy tool, which might even be useful in the real production environment.
Checkpoint Group
The activation state of all checkpoints which can be activated is controlled by the checkpoint group.
Assertion
Let us consider a scenario where money is transferred from one account to another. Here, the sum of both balances must be equal before and after transfer. Simple Finance. Right? With the help of assertion, we can check this condition. It is a statement which we can put in the program describing a specific condition.
syntax 1 – ASSERT logical expression.
During program execution, if expression fails, execution can be stopped. Program execution can be stopped by raising ASSERTION_FAILED.
syntax 2 – ASSERT ID checkpoint group CONDITION logical expression.
If ID statement is used then assertion activation and its behavior is controlled from outside the program by checkpoint group otherwise it is always active.
How to create Checkpoint group?
Checkpoint group can be created using SAAB transaction.
Enter Name and click on create. Save it.
Please note – By default Assertion is Inactive as shown below in the Assertions frame.
Let us write a very basic program and execute it in SE38.
REPORT ZSAPYardCheckPoint.
DATA : lv1 TYPE c VALUE 'A'.
DATA : lv2 TYPE c VALUE 'B'.
INITIALIZATION.
LOG-POINT ID zcheck_pt FIELDS: lv1,lv2.
START-OF-SELECTION .
lv1 = 'X'.
lv2 = 'Y'.
LOG-POINT ID zcheck_pt FIELDS: lv1,lv2.
WRITE 'Run.. Done'.
ASSERT ID zcheck_pt CONDITION lv1 EQ lv2.
BREAK-POINT ID zcheck_pt.
Execute the program. It would run successfully and write the output “Run.. Done”.
Case 1 – Abort
Now, go to transaction SAAB. Select radio button Abort in Assertions frame and save the Checkpoint Group.
Now, execute the program again. You will get a dump describing the position where Assertion is violated.
You can no doubt see the dump in the regular t-code ST22. But our aim in this article is to see the Log in SAAB T-code where we created the Checkpoint.
Case 2 – Log
In order to start logging, we need let the SAAB T-Code know about our intention. We need to choose the Log radio button (as shown below) in the Assertions (Foreground) section.
You can see the log details with Variable values, which would be very useful in cases where we can’t replicate the scenarios in Quality or Development box – to chase the mysterious missing values.
PS: If you do not see the above log, choose the Log radio button in Assertions frame and re-run your program.
Case 3 – Break
When you select Break option you will get following pop up which is just an information.
What does the above information mean?
Ans: In the normal case, the program is interrupted and the debugger is started. In case of background processing, there are two choices. It will behave like Log mode or Abort mode depending on what is selected in the above pop up. By default it is Log, so in Background processing mode, the program would not go into debug mode, but checkpoint group would log the issues.
Just like Assertions; Breakpoints and Logpoints can be activated by selecting Break and Log respectively.
BREAK-POINT:
Break Point can be activated by writing following code in your program.
BREAK-POINT ID <Checkpoingroup>.
Active breakpoint behaves same as an Always Active breakpoint. In case of background processing, activatable breakpoints are simply ignored. Run the Program in the foreground, you can see the program stop by at line 30.
LOG-POINTS:
Log Point can be made active by writing following code in your program.
LOG-POINT ID <Checkpoingroup>
Logs can be used to identify or analyze the system behavior. Variable values can be logged so that the developers can analyze those values.
You can add in your program statement LOG-POINT ID ZCHEECK_PT. You can also use the following syntax.
Run the program and go to transaction SAAB. Go to tab ‘LOG’. You will see following details which will help you to analyze program’s behavior.
Above statement log the value of field ‘log’. We can log up to 32 fields.
You can use SUBKEY addition to prevent the production of a huge amount of data in the log. All log occurrences will produce one record for the same SUBKEY. Only last occurrence can be seen but the counter will be incremented.
Checkpoint group activation can be done with three levels
Personal Activation – Checkpoint group will be active for current user only.
2. User Level activation – Checkpoint group will be active for all defined users.
3. Server level Activation – The Same way we can define servers for which it will be active.
This was my humble effort to bring your notice to this simple yet very powerful tool which every ABAPer should make use of. I know, this transaction SAAB should have been introduced years ago. But every day you find something new in SAP. Please start using it and let me know if you face any issue. I would try to answer your queries.
If there any other useful tool which you use in your project?
Please share with us if you have some smart way to tackle production issues or identify dumps. If you have some custom tools which can be shared, do send it to us. We would be happy to share it in our portal.
Now, we want to hear from you. Please leave your feedback and comments below.
We have a very active Telegram (App) SAP Technical Group with more than 1440+ SAP Technical Practitioners from 6 Continents of the SAP World. Please join it using below link. Telegram SAP Technical Discuss Group. You need to install the Telegram App first on your mobile device. Once you have it on your mobile, you can join the group and also access it from the Web on your computer and laptop.
SAP HANA is Hot. Fiori/UI5 is Cool. OData is Fascinating. But in all these fun, basic ABAP is still thriving. This article is an example of the basic ABCs of ABAP which we need till ABAP does not get extinct (which will never happen) in the future SAP World. Darwin’s law of Evolution holds good to ABAPers as well. ABAPers need to evolve and get new tricks of the game in their kitty, if they want to stay ahead in the competition. In this article, I have tried to show case some of the New Syntaxes of ABAP, which makes programming easier and reduces the code footprints.
The context behind this article:
In our SAPYard Technical Telegram Group, where we have more than 2470 active consultants from 6 Continents, one member @imkinnu shared the below image/gif and asked the Question.
Question: The result should be printed in Result box instead of the Input text. For example, for Input ‘2+3’ I need the Result ‘5’ to be printed in result box. What i have done is, assigned the Input field ‘IN’ to Result field ‘RES’.
Answer from @Anask (within few seconds. That the beauty of this group):
It treats the variable as string and just copies content of one field to another. You need to split at SIGN and get data into 2 separate fields and then do the operator action. Just use Split and then do the Math operation. Use CASE statement and split based on the sign it contains.
SPLIT lv_input AT lv_operator
INTO lv_operand1
lv_operand2.
I am members of many WhatsApp and Telegram and other groups. But this SAP Technical Group is the most active one where we have tons of discussions every hour. Most Questions get Answered or at least get some ideas for the solution. If you have not joined yet, give it a try. It is safe and secure. And no one can know your mobile number, not even the Admins. You need to have Telegram App installed in your device before you join the group using the below link.
I thought of helping @imkinnu with my own program. I quickly wrote a simple OOPs Program with New ABAP Syntaxes which I try to write in all my new developments.
Well, I forgot to introduce myself. My name is Stephan Köster. I am one of the Admins of the SAPYard SAP Technical Group. I am an SAP ABAP Developer. I am the founder of Köster Consulting. Please check my website for more information.
Enough of me. Now, lets come back to the topic of the day. If you are a novice of ABAP7.4, some of the points which you need to note in the below SAP Calculator Program are:
INSERT |{ lv_result }| INTO gt_string_tab INDEX lv_tabix - 1.
5. Inline Data Declaration in Class Methods
* Call the Class Method to do the calculation
zcl_stkoes_calc=>do_calculation(
EXPORTING
iv_formula = <gv_calc>
RECEIVING
rv_result = DATA(gv_result)
EXCEPTIONS
division_by_zero = 1 " Division By Zero
missing_number_at_beginning = 2 " Missing Number at the beginning
missing_number_at_end = 3 " Missing Number at the end
two_operator_not_allowed = 4 " Two Operator are not allowed
unknown_error = 5 " Unknown Error
OTHERS = 6 ).
Did you check gv_result is declared during the call of the method do_calculation.
Note:We canNOT do similar Inline Data Declaration while calling a Function Module. Only available in Class Method.
6. MESSAGE with SWITCH
MESSAGE |{ SWITCH #( sy-subrc
WHEN 1 THEN |Division by zero|
WHEN 2 THEN |Missing number at the beginning|
WHEN 3 THEN |Missing number at the end|
WHEN 4 THEN |Two operator is not allowed|
WHEN 5 THEN |Unknown Error|
ELSE |Other Error| ) }| TYPE 'S' DISPLAY LIKE 'E'.
Let me show you, what the Calculator can do. It does the basic +, -, X, / mathematic operations. But the logic and concept which I have put in the class can be extrapolated to many real use case actual project requirements.
I have knowingly used the Macros to make you understand how Macros can still be used. If you feel, macros are not needed, you can do the direct Radio Buttons in the Selection Screen without the Macros.
Sample Input String for the Calculator and their corresponding Results
Here is the complete Code which you can try. You can also download the program text file at the bottom of this article.
*&---------------------------------------------------------------------*
*& Report ZSTKOES_CALC
*&---------------------------------------------------------------------*
*& This is a Utilty Program which acts like a Simple Calculator.
*& OOPs Concept along with New ABAP7.4+ Syntaxes are used
*& Feel Free to refer and use it as required
*&---------------------------------------------------------------------*
REPORT zstkoes_calc.
CLASS zcl_stkoes_calc DEFINITION.
PUBLIC SECTION.
CONSTANTS:
BEGIN OF gcs_operators,
div TYPE char1 VALUE '/' ##NO_TEXT,
mod TYPE char3 VALUE 'MOD' ##NO_TEXT,
mul TYPE char1 VALUE '*' ##NO_TEXT,
sub TYPE char1 VALUE '-' ##NO_TEXT,
add TYPE char1 VALUE '+' ##NO_TEXT,
END OF gcs_operators.
CLASS-METHODS:
do_calculation
IMPORTING
VALUE(iv_formula) TYPE string
RETURNING
VALUE(rv_result) TYPE labst
EXCEPTIONS
division_by_zero
missing_number_at_beginning
missing_number_at_end
two_operator_not_allowed
unknown_error.
PRIVATE SECTION.
TYPES:
gtty_string TYPE TABLE OF string.
CLASS-METHODS:
convert_formula_to_string_tab
IMPORTING
VALUE(iv_formula) TYPE string
EXPORTING
VALUE(et_string_tab) TYPE gtty_string
EXCEPTIONS
missing_number_at_beginning
missing_number_at_end
two_operator_not_allowed,
calculate
IMPORTING
VALUE(it_string_tab) TYPE gtty_string
RETURNING
VALUE(rv_result) TYPE labst
EXCEPTIONS
division_by_zero
unknown_error.
ENDCLASS.
CLASS zcl_stkoes_calc IMPLEMENTATION.
METHOD do_calculation.
convert_formula_to_string_tab(
EXPORTING
iv_formula = iv_formula
IMPORTING
et_string_tab = DATA(lt_string_tab)
EXCEPTIONS
missing_number_at_beginning = 1
missing_number_at_end = 2
two_operator_not_allowed = 3 ).
IF sy-subrc EQ 0.
calculate(
EXPORTING
it_string_tab = lt_string_tab
RECEIVING
rv_result = rv_result
EXCEPTIONS
division_by_zero = 4
unknown_error = 5 ).
ENDIF.
IF sy-subrc NE 0.
CASE sy-subrc.
WHEN 1.
RAISE missing_number_at_beginning.
WHEN 2.
RAISE missing_number_at_end.
WHEN 3.
RAISE two_operator_not_allowed.
WHEN 4.
RAISE division_by_zero.
WHEN 5.
RAISE unknown_error.
ENDCASE.
ENDIF.
ENDMETHOD.
METHOD convert_formula_to_string_tab.
FIELD-SYMBOLS:
<lv_value> TYPE string.
CONDENSE iv_formula NO-GAPS.
DATA(lv_off) = 0.
DO.
IF iv_formula+lv_off(1) CN '1234567890'.
DO.
ASSIGN COMPONENT sy-index OF STRUCTURE gcs_operators TO FIELD-SYMBOL(<lv_operator>).
IF sy-subrc NE 0.
EXIT.
ENDIF.
DATA(lv_length) = strlen( <lv_operator> ).
IF iv_formula+lv_off(lv_length) EQ <lv_operator>.
IF lv_off EQ 0.
RAISE missing_number_at_beginning.
ELSEIF <lv_value> IS NOT ASSIGNED.
RAISE two_operator_not_allowed.
ENDIF.
UNASSIGN <lv_value>.
APPEND iv_formula+lv_off(lv_length) TO et_string_tab.
ADD lv_length TO lv_off.
EXIT.
ENDIF.
ENDDO.
ELSE.
IF <lv_value> IS NOT ASSIGNED.
APPEND INITIAL LINE TO et_string_tab ASSIGNING <lv_value>.
<lv_value> = iv_formula+lv_off(1).
ELSE.
<lv_value> = |{ <lv_value> }{ iv_formula+lv_off(1) }|.
ENDIF.
ADD 1 TO lv_off.
ENDIF.
IF lv_off EQ strlen( iv_formula ).
EXIT.
ENDIF.
ENDDO.
IF <lv_value> IS NOT ASSIGNED.
RAISE missing_number_at_end.
ENDIF.
ENDMETHOD.
METHOD calculate.
DO.
ASSIGN COMPONENT sy-index OF STRUCTURE gcs_operators TO FIELD-SYMBOL(<lv_operator>).
IF sy-subrc NE 0.
EXIT.
ENDIF.
DO.
ASSIGN it_string_tab[ table_line = <lv_operator> ] TO FIELD-SYMBOL(<lv_op>).
IF sy-subrc NE 0.
EXIT.
ENDIF.
DATA(lv_from) = sy-tabix - 1.
DATA(lv_to) = sy-tabix + 1.
READ TABLE it_string_tab ASSIGNING FIELD-SYMBOL(<lv_first>) INDEX lv_from.
READ TABLE it_string_tab ASSIGNING FIELD-SYMBOL(<lv_second>) INDEX lv_to.
IF <lv_first> IS ASSIGNED AND <lv_second> IS ASSIGNED.
TRY.
CASE <lv_operator>.
WHEN '/'.
DATA(lv_result) = CONV labst( <lv_first> / <lv_second> ).
WHEN 'MOD'.
lv_result = <lv_first> MOD <lv_second>.
WHEN '*'.
lv_result = <lv_first> * <lv_second>.
WHEN '-'.
lv_result = <lv_first> - <lv_second>.
WHEN '+'.
lv_result = <lv_first> + <lv_second>.
ENDCASE.
CATCH cx_sy_zerodivide INTO DATA(lo_error).
RAISE division_by_zero.
ENDTRY.
DELETE it_string_tab FROM lv_from TO lv_to.
INSERT |{ lv_result }| INTO it_string_tab INDEX lv_from.
ENDIF.
ENDDO.
ENDDO.
IF lines( it_string_tab ) EQ 1.
rv_result = it_string_tab[ 1 ].
ELSE.
RAISE unknown_error.
ENDIF.
ENDMETHOD.
ENDCLASS.
**--------------------------------------------------------------------*
** Start of Program
**--------------------------------------------------------------------*
* Macro to set one radiobutton as default (can be used only once)
DEFINE calc_default.
SELECTION-SCREEN:
BEGIN OF LINE.
PARAMETERS:
rb_calc&1 RADIOBUTTON GROUP cal DEFAULT 'X' USER-COMMAND calc&1.
SELECTION-SCREEN:
COMMENT 3(14) co_calc&1.
PARAMETERS:
pa_calc&1 TYPE string DEFAULT &2.
SELECTION-SCREEN:
END OF LINE.
END-OF-DEFINITION.
* Macro to create radiobutton without deafult
DEFINE calc.
SELECTION-SCREEN:
BEGIN OF LINE.
PARAMETERS:
rb_calc&1 RADIOBUTTON GROUP cal.
SELECTION-SCREEN:
COMMENT 3(14) co_calc&1.
PARAMETERS:
pa_calc&1 TYPE string DEFAULT &2.
SELECTION-SCREEN:
END OF LINE.
END-OF-DEFINITION.
SELECTION-SCREEN BEGIN OF BLOCK b01 WITH FRAME TITLE gt_b01.
* Creating all paramater on SELECTION SCREEN using macro
calc_default 1 '10 + 300 / 100 * 10 - 50'.
calc 2 '+10 + 300 / 100 * 10 - 50'.
calc 3 '10 + 300 / 100 * 10 - 50+'.
calc 4 '10 + 300 / 100 * * 10 - 50'.
calc 5 '10 + 300 / 0 * 10 - 50'.
SELECTION-SCREEN END OF BLOCK b01.
INITIALIZATION.
* Filling all comments and title
co_calc1 = '1. Calculation'.
co_calc2 = '2. Calculation'.
co_calc3 = '3. Calculation'.
co_calc4 = '4. Calculation'.
co_calc5 = '5. Calculation'.
gt_b01 = 'Calculations'.
AT SELECTION-SCREEN OUTPUT.
DATA:
gv_editable TYPE rsscr_name.
* Check which radiobutton is selected
PERFORM get_field_selected_radiobutton CHANGING gv_editable.
* Leave only selected line editable
LOOP AT SCREEN.
IF screen-name(7) EQ 'PA_CALC' AND screen-name NE gv_editable.
screen-input = 0.
MODIFY SCREEN.
ENDIF.
ENDLOOP.
START-OF-SELECTION.
DATA:
gv_editable TYPE rsscr_name.
* Check which radiobutton is selected
PERFORM get_field_selected_radiobutton CHANGING gv_editable.
* Get the Formular of selected radiobutton
ASSIGN (gv_editable) TO FIELD-SYMBOL(<gv_calc>).
IF <gv_calc> IS ASSIGNED.
* Do the calculation
zcl_stkoes_calc=>do_calculation(
EXPORTING
iv_formula = <gv_calc>
RECEIVING
rv_result = DATA(gv_result)
EXCEPTIONS
division_by_zero = 1 " Division By Zero
missing_number_at_beginning = 2 " Missing Number at the beginning
missing_number_at_end = 3 " Missing Number at the end
two_operator_not_allowed = 4 " Two Operator are not allowed
unknown_error = 5 " Unknown Error
OTHERS = 6 ).
* Handle Errors
IF sy-subrc NE 0.
MESSAGE |{ SWITCH #( sy-subrc
WHEN 1 THEN |Division by zero|
WHEN 2 THEN |Missing number at the beginning|
WHEN 3 THEN |Missing number at the end|
WHEN 4 THEN |Two operator is not allowed|
WHEN 5 THEN |Unknown Error|
ELSE |Other Error| ) }| TYPE 'S' DISPLAY LIKE 'E'.
RETURN.
ENDIF.
ENDIF.
END-OF-SELECTION.
WRITE: gv_result.
*&---------------------------------------------------------------------*
*& Form GET_FIELD_SELECTED_RADIOBUTTON
*&---------------------------------------------------------------------*
* <--CV_EDITABLE PARAMETER of selected radiobutton
*----------------------------------------------------------------------*
FORM get_field_selected_radiobutton CHANGING cv_editable TYPE rsscr_name.
DO.
DATA(lv_fieldname) = |RB_CALC{ CONV numc1( sy-index ) }|.
ASSIGN (lv_fieldname) TO FIELD-SYMBOL(<lv_fieldvalue>).
IF sy-subrc NE 0.
EXIT.
ELSEIF <lv_fieldvalue> EQ abap_true.
cv_editable = |PA_CALC{ CONV numc1( sy-index ) }|.
EXIT.
ENDIF.
ENDDO.
ENDFORM.
If you do not want to use Macros for the Radio Buttons, you can download another version of the same program with simpler Selection screen. Calculator Version 2
Output of Version 2 of the SAP ABAP Calculator looks like below.
I hope, this article would motivate you to use the New ABAP Syntax in all your current and future developments. We need to embrace the change and accept it in our daily projects. Change is Progress.
Putting Text in SO10 and saving it to a transport and moving it to Quality and Production is a common requirement. It is a two step process. First save the text and then move the text to transport using program RSTXTRAN. But many times, developer or the person saving the text, forgets to save the text in the transport as it does not prompt for a transport while saving.
If you want to know the regular two step process to move the SO10 text to a transport, check this sap blog.
In our current project, we had a human error and we did not move the text to production. And Security Team did not allow us to save text directly in the production system.
Our manager asked us to find a way so that this issue does not repeat. After some brain storming session, we along with other architects, came up with a solution to prompt users to save the text in transport while saving the text in t-code SO10. This way, we do not forget.
The solution was to put an Implicit Enhancement in the Include program LSTXXFTE (Form TE_SAVE_TEXT). We programmed it to prompt for the transport number, the move the Save button is hit.
Note: There might be many opponents of Implicit Enhancement. If you have a better way to meet the same requirement, please do let us know. We would be happy to apply it.
A short Youtube link which goes through our enhanced functionality and design.
Implicit Enhancement in Include Program LSTXXFTE. At the end of the Sub routine TE_SAVE_TEXT.
Source Code Snippet.
DATA : lo_utlities TYPE REF TO zcl_utilities,
ls_input TYPE zsavetextinput,
lo_subrc TYPE REF TO sy-subrc.
FIELD-SYMBOLS: <lfs_any> TYPE any.
** Get instance
lo_utlities = zcl_utilities=>factory( ).
IF lo_utlities IS BOUND.
** FIll input
ls_input-tdname = tename.
ls_input-tdid = teid.
ls_input-tdspras = tespras.
** Call save method
lo_subrc = lo_utlities->save_standradtext( is_input = ls_input ).
ASSIGN lo_subrc->* TO <lfs_any>.
IF <lfs_any> = 0.
** Success
ENDIF.
ENDIF.
ENDIF.
ZCL_UTILITY class is created and all logic are handled in it.
class ZCL_UTILITIES definition
public
final
create private .
public section.
*"* public components of class ZCL_UTILITIES
*"* do not include other source files here!!!
class-methods FACTORY
returning
value(RT_REF) type ref to ZCL_UTILITIES .
methods SAVE_STANDRADTEXT
importing
!IS_INPUT type ZSAVETEXTINPUT
returning
value(LO_OUTPUT) type ref to SY-SUBRC .
protected section.
*"* protected components of class ZCL_UTILITIES
*"* do not include other source files here!!!
private section.
*"* private components of class ZCL_UTILITIES
*"* do not include other source files here!!!
ENDCLASS.
CLASS ZCL_UTILITIES IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_UTILITIES=>FACTORY
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RT_REF TYPE REF TO ZCL_UTILITIES
* +--------------------------------------------------------------------------------------</SIGNATURE>
method FACTORY.
CREATE OBJECT rt_Ref.
if rt_ref is bound.
endif.
endmethod.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_UTILITIES->SAVE_STANDRADTEXT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IS_INPUT TYPE ZSAVETEXTINPUT
* | [<-()] LO_OUTPUT TYPE REF TO SY-SUBRC
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD save_standradtext.
DATA:
lv_tr_order_zz TYPE e070-trkorr,
lv_tr_task_zz TYPE e070-trkorr,
lv_textkey_zz(110),
lv_len_zz TYPE i.
DATA:
lt_e071_zz TYPE TABLE OF e071,
lt_e071k_zz TYPE TABLE OF e071k,
ls_e071_zz TYPE e071,
ls_e071k_zz TYPE e071k.
FIELD-SYMBOLS:
<lv_zz> TYPE any.
IF sy-subrc = 0.
CALL FUNCTION 'TR_ORDER_CHOICE_CORRECTION'
EXPORTING
iv_category = 'CUST'
iv_cli_dep = 'X'
IMPORTING
ev_order = lv_tr_order_zz
ev_task = lv_tr_task_zz
EXCEPTIONS
no_correction_selected = 1.
IF sy-subrc = 0.
lv_textkey_zz = '%,$,%,%'.
REPLACE '%' WITH 'TEXT' INTO lv_textkey_zz.
REPLACE '%' WITH is_input-tdid INTO lv_textkey_zz.
REPLACE '%' WITH is_input-tdspras INTO lv_textkey_zz.
CONDENSE lv_textkey_zz NO-GAPS.
lv_len_zz = strlen( is_input-tdname ).
ASSIGN is_input-tdname(lv_len_zz) TO <lv_zz>.
REPLACE '$' WITH <lv_zz> INTO lv_textkey_zz.
ls_e071_zz-obj_name = lv_textkey_zz.
ls_e071_zz-trkorr = lv_tr_task_zz.
ls_e071_zz-as4pos = 0.
ls_e071_zz-pgmid = 'R3TR'.
ls_e071_zz-object = 'TEXT'.
* ls_e071_zz-objfunc = objfunc.
APPEND ls_e071_zz TO lt_e071_zz.
CALL FUNCTION 'TR_APPEND_TO_COMM_OBJS_KEYS'
EXPORTING
wi_trkorr = lv_tr_task_zz
TABLES
wt_e071 = lt_e071_zz
wt_e071k = lt_e071k_zz
EXCEPTIONS
error_message = 99.
IF sy-subrc = 0.
GET REFERENCE OF sy-subrc INTO lo_output.
ENDIF.
ENDIF.
ENDIF.
ENDMETHOD.
ENDCLASS.
Hope you liked this trick. If you have any tips or tweaks, please share them with us at mail@sapyard.com and we would be happy to publish them here.
We also have a 2550+ active SAP Consultants from 6 continents in our Telegram Group. Please join and be part of the largest knowledge sharing group in SAP Technical – SAPYard Technical Telegram Group.
SAP Language Translation has been there since the birth of SAP. If you are in a project with Global Roll Outs, you must have faced the issue of Language and Translation. In the mid 2000 era, I was working for a USA client who were rolling out for South America and Asia. The USA release was perfect but we had to put major effort for Language and Currency conversions for other geographies.
Application developers (read ABAPer) often get junk character issue during Form Translations in roll out engagements. Though Language Translation activity is one of the basic requirements in SAP Forms (SmartScript, SmartForm or Adobe), at times if can consume high efforts to troubleshoot junk character issues, and stress the developers and the team to great extend, if not approached diligently.
We have followed the simple below strategy for smooth Translation Activity for localize requirement. You might use it as a guide for your project needs.
Pre-requisites for any Language Translation Activity
If you are in a team who are performing the language translation activity, do not neglect the below two points:
1. Configure and Use Correct Font for Printing
For instance, we need to use JPMINCHO for language Japanese. Similarly for Chinese language, we should use CNSONG or CNHEI.
2. Configure and Use Correct Device Type and Printer
Having said that, the million dollar question is, how do we really identify the correct font and device type that should be used?
How? How? How?
Do not worry. We are here to help you. Adopt the below simple procedures and you would never see those unwanted characters in your output forms.
Hands on Exercise
For our understanding we would pick Chinese Language for Translation. We know that for Simplified Chinese, ‘ZH’ is the SAP language code.
If you want to know the Language Keys or SAP Language Code, then go to table T002.
Coming back to the exercise, we would see how we can Translate a simple Purchase Order Texts from English to Chinese.
Below steps would help us identify the correct Font and Device Type which in turn would help us address the Language Translation requirement.
Step 1: Prepare Language Translation Template
A language translation template should be prepared to collate the localize language (Chinese (ZH) for our example) from the business/end/super users.
Step 2: Identify the Correct Font
Identifying the correct font that should be used for the Purchase Order Form is our next step.
Go to t-code SE71, give the SAPScript name, for PO case it is MEDRUCK and Language as ‘ZH’. Select the Paragraph Formats and hit display.
Click the Font to get the Chinese Font name. Check the font is CNHEI. We need to leverage the same for our business case.
Now we need to analyse and figure out the correct device type which would support the Chinese font CNHEI identified above.
The SAPscript Printer Fonts table TFO03 comes to our rescue. Provide the font identified (CNHEI) in above step and get the correct Device Type. It has one to many relationship. Based on the fonts, character per inch, bold, italics etc choose the most appropriate one for your landscape.
Step 4: Identify the correct Printer for the Device Type
The Spool Printer declaration table TSP03 has the information. Provide the device type CNHPLJ4 and it would provide you the correct Printer.
Above is one of the Printer which supports Chinese Language.
That’s it. Follow the above 4 steps and have a smooth Translation and Printing activity. This is my first article at SAPYard. Please do provide your genuine feedback. It would help me for my next article.
Download Telegram App in your mobile and join the most active SAP Technical Discussion Group where we have dedicated members (2580+) from 6 continents. Telegram SAP Technical Discuss Group.
Documentation is an important aspect of any Project deliverable which cannot be ignored. It not only helps in understanding the product better and developers can reuse the object better but also it proves to be the boon for the AMS team members who support and maintain your product long after you left the project. Every company has it’s own standard way to maintain comments/modification logs and documentations.
No doubt, documentation is necessary but developers feel it is a pain in the neck to write tons of non-executable comments/logs, just for someone else to have better readability.
Can we automate this so called non-productive work?
In this article, I would like to demonstrate a simple use case to use dynamic pattern and make the documentation process interactive and some fun. Also I would show how we can configure code template to enter multiple lines of snippet in a program by simply pressing Tab .
Just for the sake of clarity: Dynamic Pattern is nothing new. It has been there since SAP’s birth. I just did not find this topic at SAPYard, so thought of including it in our SAPYard repository.
I can’t provide the exact template (for obvious reasons) what I am using but you can add as many details as you want.
After calling the pattern in a program, it will first display a pop up containing all transports for that user and from which user can select the required TR as below.
Open your program – > click pattern -> other pattern -> enter your custom pattern
Press enter, pop up will display containing TR details
Normal logs will look like below –
And the dynamic code template will look like below –
Above was a short demo of what this Pattern can do.
Now let’s start the development.
1. Dynamic Patterns
Open any program and go to Utilities -> More Utilities -> Create Pattern
Give name of the pattern
Give name of the pattern and press enter
Write *$&$MUSTER in the first line of the pattern and save.
Now we need a function group and a function module to call our dynamic pattern
Create a function group ( for example zfg_test1) and function module as <Pattern name>_editor_exit ( like ztest_pattern_editor_exit).
Note: <Pattern name>_editor_exit
Open Function module and enter BUFFER as Tables Parameter.
Declare your required variables.
DATA: comment1 TYPE buffer.
Whatever data you want to display after calling the pattern should be passed in BUFFER table .
For example to display transports –
Where ls_e070 contains the TR details.
I have used below code to add a pop up so that user can select the required TR.
LT_E070 – contain TR data, C_CHOICE table index of the selected TR.
After adding all the necessary code in the functional module , save and activate and call the pattern in your program and let the function module do all the magic.
Now lets call a dynamic code template in the program and let’s add code just by pressing the button ‘TAB’.
Open any program and click the below highlighted template button
Select code template -> add -> provide your template name (recommended to have the template name to be short usually 1-3 characters) -> press enter.
For our example we have named it ‘mod’.
Click insert tag -> select interactive -> user variable will display -> add your static comment to your template as below -> save
Open your program – > start typing “mod” -> press Tab. Please note, before you type mod, the helper would show mod and you can press Tab
Enter variable name like your SAP User Id or Name.
Press OK -> It will display like below.
In this article, I just tried to show some basic stuffs which we can automate. You can create your template to write re-usable code for any BAPI call. You can automate to write comments for any lines.
You can also build the automation tool to create your own Technical Specification along with Table Access Diagram and Flow Charts.
Just use your imaginations and enjoy automation. Please provide your feedback and thoughts in the comments section below.
If you have any tool(s) which helps the ABAPers, please do share.
We have a very active Telegram (App) SAP Technical Group with more than 3120+ SAP Technical Practitioners from 6 Continents of the SAP World. Please join it using below link. Telegram SAP Technical Discuss Group. You need to install the Telegram App first on your mobile device. Once you have it on your mobile, you can join the group and also access it from the Web on your computer and laptop.
Wondering what ANST is? Do not feel sad. You are not the only one who is hearing ANST for the first time. SAP is like a vast ocean and no one can claim to know every drop of that ocean. Just to make your heart lighter, even we did not know about ANST until few weeks ago. ANST is the abbreviation for Automated Note Search Tool.
This article is intended to share awareness on the new supportability tools available in SAP to improve productivity while addressing standard issues through OSS notes in SAP applications.
Note: This article will be updated frequently with relevant supportability tools to help application developers troubleshoot SAP application issues.
At times,
searching SAP notes for any bugs or errors in an SAP application can be a
time-consuming activity, and more often developers spend most of their time on
below activities to identify the root cause.
Search for most appropriate and relevant SAP note to fix the issue
Identify if any custom code has impacted the standard business functionality
Identify if any of the configuration table entries are missing
Identify possible objects from where you can start the debugging
With the introduction of new supportability tool like Automated Note Search Tool available from release SAP Basis release 700, efforts to identify OSS notes for any errors in standard or custom applications can be reduced drastically.
The tool not only helps you to identify
the relevant OSS note, but also supports features to identify impact due to
custom code, missing configuration entries and possible objects to start your debugging.
ANST Availability
Released for
all support packages with release 740.
Software
Component
Release Package name
SAP_BASIS 700 SAPKB70028
SAP_BASIS 701 SAPKB70113
SAP_BASIS 702 SAPKB70213
SAP_BASIS 731 SAPKB73106
The transaction to start the tool is named ANST_SEARCH_TOOL
After support package with note 1915529
the transaction has been renamed to ANST
Software Component Release Package name
SAP_BASIS 700 SAPKB70030
SAP_BASIS 700 SAPKB70030
SAP_BASIS 701 SAPKB70115
SAP_BASIS 702 SAPKB70215
SAP_BASIS 731 SAPKB73111
SAP_BASIS 740 SAPKB74005
Pre-requisites before using the
tool.
Authorizations required
Auth object: S_TCODE: Transaction ANST
Auth object: S_TCODE: Transaction SNOTE: For the note search
Auth object: S_DATASET: Authorizations for file access
Auth object: S_ALV_LAYO: for displaying/editing ALV lists
Auth object: S_GUI GUI activities (for example, download / upload)
SAP notes to be implemented
2361155 – ANST: Change user on start & stop functionality
2469123 – ANST: Remove identification of irrelevant SAP Notes
2286869 – ANST: Trace On/Off error “Dynamic Start and Stop cancelled by user”
The Automated Notes Search Tool
(ANST) is an application that searches for SAP Notes in order to solve problems in SAP applications,
specifically those SAP Notes that have correction instructions.
It helps SAP customers to find SAP Notes in order to solve a specific issue and works based on the issue replication at the customer system. When an issue is reproduced on the customer’s system, the list of application components that are involved is displayed and a note search is performed on the application component where the problem exists.
ANST
Key Features
Search for SAP notes
Customer code identification related to the
bug
Identify
missing customizing tables entries
Identify
the possible objects from where debugging can be triggered.
How ANST works?
ANST works in 3 simple steps.
Identify an issue
Replicate the issue through ANST
Execute the trace to identify and implement the note
Step 1 – For any error reported in SAP application, depending on the support pack, execute transaction ANST or ANST_SEARCH_TOOL and then choose the most appropriate option to replicate the issue.
ANST – Replicate the issue
Step 2 – Once, the issue is replicated navigate back to ANST. A trace will be generated and grouped based on application component as shown below.
From here, you can either search for a Note, Custom Code or missing Customizing table entries that may be the potential reason for this issue.
In Our case, we will select the appropriate component and click on note search to identify the relevant SAP note.
ANST – Select Component and Search for the Note
Step 3 – Based on the component selected, list of relevant SAP notes will be displayed. After this, you need to identify the most appropriate SAP Note and implement it. You can display the note directly from here and also download the note if required.
ANST search will be optimize if
there are a smaller number of objects, the search is based on objects presents inside
the generated trace, so replicate the issue with minimal steps involved.
Maintain RFC destination SAPSNOTE
in SM59 pointing to SAP OSS system, this is required by ANST and the search
depends on the RFC connection. You maintain in the settings options available
in ANST.
Ensure you read SAP Note before
implementation if it really matches to your problematic scenario.
When using it for the first time
and you get an error message: “Fatal Error. Customizing table is not filled.”
Refer the SAP Note: 1909768.
When you are using ANST for a web
application and you get an error message: “Web trace cancelled by the user.”
Refer SAP Note: 1885730. After following this if the error still occurs then
raise a ticket.
If you could not find any
relevant SAP Note which can solve the issue, then report the case to SAP
support by raising a ticket to BC-UPG-NA component.
And finally, I would like to end this blog by introducing the new exciting addition by ANST to Support of FIORI Apps:
This feature is available as of note 2605555 – ANST: Enhancement to support Fiori applications
To summarize, certainly, a good addition by SAP to improve productivity and expedite issue resolution in standard or custom applications by identifying and implementing OSS notes, custom program implications and missing configuration entries.
Do you have any other tool which you believe can help others. If yes, please do share it with us. Your feedback would help us create even better future articles. So, please share your thoughts in the comments section below.
Download Telegram App in your mobile and join the most active SAP Technical Discussion Group where we have dedicated members (3570+) from 6 continents. Telegram SAP Technical Discuss Group.
Free SAP ABAP for HANA Training Tutorials Exercises.
Even in this 21st Century, there are many small and medium sized organizations (SME – Small and Medium Enterprises) in developing countries who have relatively poor internet service and connectivity.
Such SMEs do not want to invest on expensive Wireless Scanner devices and Tablets to run Fiori apps (for obvious reasons). So what is the option? They want an economical and innovative provision to get remote Qrcode data into the PC Fiori app to save the cost and also meet the requirement.
How can we send the Qrcode data to the PC Fiori App remotely?
Solution:
Step 1: Install the Barcode to PC Desktop App – BarcodeToPC
Step 4: Place the cursor on the input field of Fiori App and run the Android App you just downloaded and scan the Qr code from far place.
Pre-requisite – Both PC and Android should be connected to the same Wifi modem. You can scan any QR code material far away from PC to get it’s info to the PC. The person sitting in front of the PC will continue the transaction once scan is completed.
Sample Code for Qrcode Input in Fiori App
Also Check – Free End to End Video Courses on HANA, ABAP7.4+, OData, Debugging etc
For screenshot and demo purpose I am using a QR in view. In real time you just have an input and place your cursor on that input. Now ask someone with an Android phone to scan the material qr code.
Wola! You got the value on PC Fiori App.
Check some sample outputs below.
Hit single scan mode and scan the Qr.
The value would be populated in the Fiori App.
This is one of the real time applications we developed for one or our client. Innovation is the key to survive. With the advent of modern devices and technologies, doing business was never this easy.
As an application developer, it gives immense satisfaction to watch your development in live action. Happy clients make you a happy developer.
Do you have any such tips or tricks to share? Do send them to mail@sapyard.com.
Now, SAPYard has a YouTubeChannel. Please Subscribe to our Channel for useful videos shorter than 5 minutes and for complete end to end free video courses.
We have a very active Telegram (App) SAP Technical Group with more than 3600+ SAP Technical Practitioners from 6 Continents of the SAP World. Please join it using this link. Telegram SAP Technical Discuss Group.
You need to install the Telegram App first on your mobile device. Once you have it on your mobile, you can join the group and also access it from the Web on your computer and laptop.
Are you tired of using free online PDF merger tools available on internet with lots of unwanted advertisements and limitations? Ever tried merging PDFs using SAP? No worries this tutorial will help you to merge multiple PDF using SAP.
There can be 3 common possibilities to merge PDFs:
Merging two PDFs from external source
Merging two SAP Adobe forms
Merging an SAP Adobe form and a PDF from an external source. Note: ADS doesn’t support merging PDFs yet, so its tricky one.
A. Merging two PDFs from external source
First case is pretty simple and we have a standard SAP program to help us. Program – RSPO_TEST_MERGE_PDF_FILES.
Just you need to select the PDFs and execute the program and Ta-Da! your merged file is ready.
B. Merging two SAP Adobe Forms or Merging an SAP Adobe Form and a PDF From an external source.
Now comes the tricky part. How to merge two or more SAP Adobe forms or an Adobe form with an external PDF?
For this we have PDFTK(PDF Tool Kit)as our savior. It is an external tool. You need some help from Basis and Security team to install the tool and get the required authorizations. So have good terms with your Basis and Security friends.. SAP is a team game and you cannot win it alone.
Let’s begin merging..
Here are steps that you need to perform before writing your code:
Install PDFTK and the required class – Please go through the attached link and perform the steps as given. Merge PDF files in ABAP
Write the driver program to implement this functionality.
Below are the steps to be performed in the driver program:
Get all the PDF content (ADS, PDF from presentation server, PDF from application server, PDF from DMS server or any other source) in XSTRING format.
Append all the XSTRING PDFs into one internal table IT_PDF. Loop IT_PDF and call ADD_PDF() method of the class ZCL_PDF_MERGE . This will create temporary PDFs in your application server path ZPDFMERGE created by BASIS team.
Finally, call GET_MERGED() method which will create the final merged PDF and return the final PDF XSTRING in the variable v_pdf_merged, also it will delete all the temporary files those were created above.
Now you have the final merged PDF data in XSTRING format and you can use it as per your requirement (to send mails, to save in presentation server or application server , to show on the SAP screen just like an adobe form output).
Below is an example code snippet to merge two adobe form with a PDF from the application server and then save it in the presentation server.
Prerequisites- Authorization object S_LOG_COM should be assigned to you and also you should have ZPDFMERGE path access.
REPORT zsapyard_pdf_merge. PARAMETERS : p_path TYPE sdok_filnm. ” app server path DATA: lo_pdfmerge TYPE REF TO zcl_pdf_merge, v_pdf_merged TYPE xstring, v_pdf TYPE xstring, it_pdf TYPE STANDARD TABLE OF xstring, wa_formoutput1 TYPE fpformoutput, wa_outputparams TYPE sfpoutputparams, wa_docparams TYPE sfpdocparams, wa_formoutput2 TYPE fpformoutput, v_formname1 TYPE fpname, v_formname2 TYPE fpname, v_function_name1 TYPE rs38l_fnam, v_function_name2 TYPE rs38l_fnam, bin_tab TYPE STANDARD TABLE OF tabl1024, lo_gui TYPE REF TO cl_gui_frontend_services, path TYPE string, fullpath TYPE string, length TYPE i, filter TYPE string, uact TYPE i, name TYPE string.
*first adobe form. v_formname1 = ‘ZTEST1’.
*get function module names for Adobe forms TRY. CALL FUNCTION ‘FP_FUNCTION_MODULE_NAME’ EXPORTING i_name = v_formname1 IMPORTING e_funcname = v_function_name1. * e_interface_type = lv_interface_type1.
CATCH cx_fp_api_internal cx_fp_api_repository cx_fp_api_usage. * MOVE sy-subrc TO lv_subrc. ENDTRY.
* second adobe form v_formname2 = ‘ZTEST2’. TRY. CALL FUNCTION ‘FP_FUNCTION_MODULE_NAME’ EXPORTING i_name = v_formname2 IMPORTING e_funcname = v_function_name2. * e_interface_type = lv_interface_type2.
CATCH cx_fp_api_internal cx_fp_api_repository cx_fp_api_usage. * MOVE sy-subrc TO lv_subrc. ENDTRY.
* Set parameters (print/send/output device etc) CALL FUNCTION ‘FP_JOB_OPEN’ CHANGING ie_outputparams = wa_outputparams EXCEPTIONS cancel = 1 usage_error = 2 system_error = 3 internal_error = 4 OTHERS = 5. IF sy-subrc <> 0. * MOVE sy-subrc TO lv_subrc. ENDIF.
wa_docparams-langu = ‘E’. wa_docparams-replangu2 = ‘E’. CALL FUNCTION v_function_name1 EXPORTING /1bcdwb/docparams = wa_docparams IMPORTING /1bcdwb/formoutput = wa_formoutput1 “1st adobe form EXCEPTIONS usage_error = 1 system_error = 2 internal_error = 3 OTHERS = 4. IF sy-subrc <> 0. * MOVE sy-subrc TO lv_subrc. ENDIF. CALL FUNCTION v_function_name2 EXPORTING /1bcdwb/docparams = wa_docparams IMPORTING /1bcdwb/formoutput = wa_formoutput2 “2nd adobe form EXCEPTIONS usage_error = 1 system_error = 2 internal_error = 3 OTHERS = 4. IF sy-subrc <> 0. * MOVE sy-subrc TO lv_subrc. ENDIF.
*get external source PDF data from the application server OPEN DATASET p_path FOR INPUT IN BINARY MODE. IF sy-subrc EQ 0. READ DATASET p_path INTO v_pdf. IF sy-subrc EQ 0. ENDIF. ENDIF. CLOSE DATASET p_path.
* create table with all xstring data APPEND v_pdf TO it_pdf. v_pdf = wa_formoutput1-pdf. APPEND v_pdf TO it_pdf. v_pdf = wa_formoutput2-pdf. APPEND v_pdf TO it_pdf.
* use ZCL_PDF_MERGE class to get the PDF xstring after merging all PDF’s CREATE OBJECT lo_pdfmerge. LOOP AT it_pdf INTO v_pdf. lo_pdfmerge->add_pdf( v_pdf ). ENDLOOP. v_pdf_merged = lo_pdfmerge->get_merged( ). “final merged data *download merged PDF to your desktop CREATE OBJECT lo_gui.
And, your merged file is ready. You can also create a spool request for this or show this on your screen as per your requirement.
We have a very active Telegram (App) SAP Technical Group with more than 3600+ SAP Technical Practitioners from 6 Continents of the SAP World. Please join it using below link. Telegram SAP Technical Discuss Group. You need to install the Telegram App first on your mobile device. Once you have it on your mobile, you can join the group and also access it from the Web on your computer and laptop.
Many times when we try to create Consumer Proxy for the external Web Service (URL), we get error while accessing the WSDL/Schema. The error can be because of firewall, trusted network, URL certification or authorization issues. But once all the network and authorization issues are resolved, if you still get error in WSDL/Schema, then it is highly possible that your Web Service URL has the link to Schema Location which it cannot identify at runtime wizard while creating the Consumer Proxy.
Say you are trying to create consumer proxy in SE80 like
shown below.
You give the name and proceed with the Enterprise Services
Create Wizard.
But you are stuck in the step “Enter the URL for accessing the WSDL/Schema”.
Errors can be like below: “500 Internal Server Error”.
Error can be like “500 SSL Peer Certificate Untrusted”.
Error in URL accessing the WSDL/Schema can also be for .XSD
like below.
Sample xsd errors in WSDL URL:
“Error while uploading the xsd file(schema) in web services definition”
“File ContractService.xsd was not attached because it has an invalid file extension not allowed by the system settings”
“Exception occurred in communication framework: Error in HTTP Framework:500Internal Server Error”
“…<WSDL URL>.xsd “ error
by the way XSD means XML Schema Definition
Most of the time, WSDL URL has the hyper link (URL) to the schema location. Instead of the <xsd:schema> definition </xsd:schema>, it has a URL link to schema location where we can see the actual schema definition and properties. In such Web Services, if we try to consume the WSDL URL, we get error. In order to avoid that error, we need to use the WSDL local file. Do not worry, the local WSDL file would be dynamic and would work exactly like the WSDL URL. But we need to do a small tweak in the local WSDL file.
We need to replace the link to schema location with the exact schema definition from the schema location URL in the WSDL local file. Once we have the complete schema definition replaced in the WSDL, we should be able to create the consumer proxy using the local WSDL file.
Below steps would demonstrate in details, how we can replace the xsd schema location with the exact xsd schema definition. We have taken the public dnb toolkit web service for our example.
Copy the full xsd:schema and replace in the original WSDL.txt file with the xsd:schema from the schemaLocation.
PS: Do not copy the first line of the page source (as marked in image above).
Tips:
<xsd:schema> </xsd:schema> should be between <wsdl:types>
</wsdl:types>.
Step 9 – Save the Web Service file with .WSDL extension
Sample .WSDL image
Step 10 – Choose the WSDL Source as Local File in the Wizard
Now, wherever you were trying to use the WSDL URL, replace it with your local WSDL file. The xsd schema error should be resolved.
Screenshot of sample Consumer Proxy we created.
This should fix the Web Service XSD Schema issue.
Thank you Mastan Raju!!
A special thanks to Mastan Raju, who helped us with a video explaining the root cause. He helped us figuring out the issue and even sent us a sample WSDL file. With his help, we were able to create the consumer proxy from local WSDL file and consume the external Web Service.
If you want to have real time discussions and resolutions, do join our SAP Technical Telegram Group where we have more than 4670+ active SAP consultants from 6 Continents.
Please Note: You need to install Telegram App on your mobile first and then you can join the group using the above link.
SAP users, both business and end users always need to download the output of a report to spreadsheet and do their analytics. The standard excel output from a report is very simple process but it is old fashioned and the spreadsheet looks quite boring. There is no default formatting and the users have to do all the hard work of changing the fonts, coloring the texts, marking the borders etc.
I acknowledge, whatever I mentioned above can be achieved in many ways programmatically. We can do it in the old traditional ABAP way but providing multiple tabs in the spreadsheet and formatting is quite tricky with non OLE method.
OLE = Object Linking and Embedding
The high level agenda of this article is to be the G.O.A.T. (please google it if you do not know the full form) of OLE Excel Guide. We want this one article to be the light house of all ABAP developers who needs to work with OLE Excel in their projects. Too much of chest thumping before the start. Right?
We would use the new ABAP 7.4+ syntaxes to created Excel with OLE. We would format the spreadsheet. Headers in bold, font color would be different at different area, background would be blue or any color of your choice. We would mark the borders better and also create multiple Tabs in the excel spreadsheet.
Let’s start our dive into the OLE approach of creating Excel.
Step 1 – Include OLE2INCL in the program
The OLE automation programs “OLE2INCL” include needs to be specified. It is kind of a library of OLE syntax.
<code>*** Include objects
INCLUDE: ole2incl.</code>
Step 2 – Populate the internal tables with data
For our demo, we are fetching data from some standard SAP tables so that everyone can use our code snippet.
<code>*** Popoluate tables
SELECT ebeln,
bukrs,
bstyp,
bsart
INTO TABLE @DATA(gt_ekko)
FROM ekko
UP TO 5 ROWS
WHERE ebeln LIKE '45%'.
IF gt_ekko[] IS NOT INITIAL.
SELECT ebeln,
ebelp,
matnr,
bukrs
INTO TABLE @DATA(gt_ekpo)
FROM ekpo
FOR ALL ENTRIES IN @gt_ekko
WHERE ebeln = @gt_ekko-ebeln.
IF gt_ekpo[] IS NOT INITIAL.
SELECT ebeln,
ebelp,
gjahr,
belnr
INTO TABLE @DATA(gt_ekbe)
FROM ekbe
FOR ALL ENTRIES IN @gt_ekpo
WHERE ebeln = @gt_ekpo-ebeln
AND ebelp = @gt_ekpo-ebelp.
ENDIF.
ENDIF.</code>
We have 3 internal tables viz GT_EKKO, GT_EKPO and GT_EKBE ready.
Step 3 – Put the internal table in a string table separated by delimiter
Our goal is to put each internal table data in different tabs of the excel. So we would save the internal table data in a long character variable where the data would be separated by delimiter ‘|’. The long variable would be used later to create a file at run-time and save in the tabs of the spreadsheet.
For our explanation, we have created a Table Type with 1500 long characters and used it for storing data separated by ‘|’. I have defined the delimiter variable as ‘deli’ which contains ‘|’ value ( cl_abap_char_utilities=>horizontal_tab ).
<code>TYPES: data1(1500) TYPE c,
ty_data TYPE TABLE OF data1.
*** Variables
DATA: gt_1 TYPE ty_data WITH HEADER LINE,
gt_2 TYPE ty_data WITH HEADER LINE,
gt_3 TYPE ty_data WITH HEADER LINE,
deli(1) TYPE c.
"Delimeter
deli = cl_abap_char_utilities=>horizontal_tab.</code>
Step 4 – Put internal table data to respective data/file types
Loop through the internal tables and concatenate the data to the respective string variables (gt_1, gt_2, gt_3). Please note, I have passed the headers as well, which will act like column names in excel sheets.
<code>**Header for first sheet
gt_1 = |EBELN{ deli }BUKRS{ deli }BSTYP{ deli }BSART|.
APPEND gt_1.
CLEAR gt_1.
**Data for first sheet
LOOP AT gt_ekko INTO DATA(wa_ekko).
gt_1 = | { wa_ekko-ebeln } { deli } { wa_ekko-bukrs } { deli } { wa_ekko-bstyp } { deli } { wa_ekko-bsart } |.
APPEND gt_1.
CLEAR gt_1.
ENDLOOP.
**Header for second sheet
gt_2 = |EBELN{ deli }EBELP{ deli }MATNR{ deli }BUKRS|.
APPEND gt_2.
CLEAR gt_2.
**Data for second sheet
LOOP AT gt_ekpo INTO DATA(wa_ekpo).
gt_2 = | { wa_ekpo-ebeln } { deli } { wa_ekpo-ebelp } { deli } { wa_ekpo-matnr } { deli } { wa_ekpo-bukrs } |.
APPEND gt_2.
CLEAR gt_2.
ENDLOOP.
**Header for third sheet
gt_3 = |EBELN{ deli }EBELP{ deli }GJAHR{ deli }BELNR|.
APPEND gt_3.
CLEAR gt_3.
**Data for third sheet
LOOP AT gt_ekbe INTO DATA(wa_ekbe).
gt_3 = | { wa_ekbe-ebeln } { deli } { wa_ekbe-ebelp } { deli } { wa_ekbe-gjahr } { deli } { wa_ekbe-belnr } |.
APPEND gt_3.
CLEAR gt_3.
ENDLOOP.</code>
If you are struggling (I hope not) with the concepts of workbook and worksheets then this snapshot should help.
We are going to use all these functionalities in our OLE generation report. Excited??
Let us create Workbook and Worksheets.
<code>*--- get list of workbooks, initially empty
CALL METHOD OF h_excel 'Workbooks' = h_sheets.
SET PROPERTY OF h_excel 'Visible' = 1. “If ‘1’ – it opens excel application in frontend and if ‘0’ then excel will be created in backend mode.
CALL METHOD OF h_sheets 'Add' = h_sheet.</code>
All the objects of excel application must be declare with type ‘ole2_object’.
<code>* Ole data Declarations
DATA: h_excel TYPE ole2_object, " Excel object
h_sheets TYPE ole2_object, " list of workbooks
h_sheet TYPE ole2_object, " workbook
h_cell TYPE ole2_object, " cell
worksheet TYPE ole2_object, "Worksheet
e_color TYPE ole2_object, "Color
range TYPE ole2_object, "Range
borders TYPE ole2_object, "Borders
h_sheet1 TYPE ole2_object, "First sheet
h_sheet2 TYPE ole2_object, "Second Sheet
h_sheet3 TYPE ole2_object, "Third Sheet
gs_font TYPE ole2_object. "Font</code>
Step 7 – Activate the current worksheet and name it
<code>GET PROPERTY OF h_excel 'ACTIVESHEET' = worksheet.
SET PROPERTY OF worksheet 'Name' = ’EKKO’. “Sheet name</code>
Step 8 – Pass the data from string internal table to Excel file
There are two ways to pass the data in excel: i) one-by-one ii) copy-paste method
Here, we am going to copy whole data from internal table and paste it in the excel. This approach saves time and increases the performance of code. See, we revealed a way to optimize the code.
<code>**Copy data in clipboard
CALL METHOD cl_gui_frontend_services=>clipboard_export
IMPORTING
data = gt1[]
CHANGING
rc = l_rc
EXCEPTIONS
cntl_error = 1
error_no_gui = 2
not_supported_by_gui = 3
OTHERS = 4.</code>
The above snippet is self explanatory. It copies the data of internal table into clipboard.
Ctrl C is always followed by Ctrl V.
Now paste the copied data from clipboard to spreadsheet.
For pasting the copied data in excel sheet, we need to select the cells and need to make the range, in which the data will be pasted.
<code>**choose first cell.
CALL METHOD OF h_excel 'Cells' = w_cell1
EXPORTING
#1 = 1 "Row
#2 = 1. "Column
**choose second cell.
CALL METHOD OF h_excel 'Cells' = w_cell2
EXPORTING
#1 = 1 "Row
#2 = 1. "Column
**Make range from selected cell
CALL METHOD OF h_excel 'Range' = range
EXPORTING
#1 = w_cell1
#2 = w_cell2.</code>
In our program, we have EBELN as our first field in every table. After copying that data into excel sheet, we see EBELN in below format because of space constraints (less width of cell).
Change the width of particular column with property ‘Columnwidth’.
<code>**Change width of column.
SET PROPERTY OF w_cell1 'Columnwidth' = 12.</code>
Now we need to select the range and paste it in excel worksheet.
<code> CALL METHOD OF range 'Select'.
** Paste data from clipboard to worksheet.
CALL METHOD OF worksheet 'Paste'.</code>
Step 9 – Formatting of the Excel Spreadsheet in SAP ABAP
The above steps ensure, we have the data in our excel. Now we have the interesting job to do i.e. Formatting.
9.1 Create Borders
Whatever data we are going to paste in excel should contains borders. For achieving this, Excel application has a property as ‘borders’.
<code>CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 7. "7 for left side
SET PROPERTY OF borders 'LineStyle'= 1. "type of line.</code>
Above, 7 is indicating border for left side. Same way we have, 8 for right side, 9 for top side, etc.
<code>**Logic to assign borders to fetched data in worksheet.
DATA(i) = 0.
LOOP AT it_sheet INTO DATA(ls_sheet).
i = i + 1.
DATA(first) = |A{ i }|. "Column from where you want to start providing borders.
DATA(second) = |D{ i }|. "Column up to which you want to provide the borders.
**Make range of selected columns.
CALL METHOD OF h_excel 'Range' = range
EXPORTING
#1 = first
#2 = second.
**Logic to assign border on left side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 7. "7 for left side
SET PROPERTY OF borders 'LineStyle'= 1. "type of line.
**Logic to assign border on right side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 8.
SET PROPERTY OF borders 'LineStyle'= 1.
**Logic to assign border on top side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 9.
SET PROPERTY OF borders 'LineStyle'= 1.
**Logic to assign border on bottom side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 10.
SET PROPERTY OF borders 'LineStyle'= 1.
**Logic to assign border on vertical side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 11.
SET PROPERTY OF borders 'LineStyle'= 1.
**Logic to assign border on horizontal side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 12.
SET PROPERTY OF borders 'LineStyle'= 1.
ENDLOOP.</code>
For our case, we have only 4 headers in every worksheet. Therefore we have hard coded for 4 headers. You should make it dynamic.
<code>** Logic to change font and color of header data.
CLEAR i.
DO 4 TIMES.
i = i + 1.
CALL METHOD OF h_excel 'CELLS'= h_cell NO FLUSH
EXPORTING #1 = 1
#2 = i.
GET PROPERTY OF h_cell 'FONT' = gs_font NO FLUSH.
SET PROPERTY OF gs_font 'BOLD' = 1 NO FLUSH.
SET PROPERTY OF gs_font 'SIZE' = 12.
CALL METHOD OF h_cell 'INTERIOR' = range.
SET PROPERTY OF range 'ColorIndex' = 5.
SET PROPERTY OF range 'Pattern' = 1.
ENDDO.</code>
Above code is pretty simple. But you may want to understand the property names. For different colors, we have different numbers in OLE as shown below:
Choose your favourite color and you are done.
Check the output of the three Tabs.
Isn’t this cool? You can play with the excel application and it’s properties to explore more. Next time create your own excel output, format it with the eyes of a designer and amaze your client with the beautiful presentation. After all, SAP is not that boring as others complain.
The complete end to end code snippet is below at the end of the article. You may copy and past it in your ABAP editor. It should work without any hiccups.
This is the first time I am writing any blog in any space. Hopefully with each new articles, I would learn the tricks of writing better tutorials and learning series. SAPYard inspired me to be a technical author and I thoroughly enjoyed the process, right from conceptualizing the topic, working on the actual program to present and actually implementing my writing skills. If you want to join the Awesome SAPYard Author’s team, feel free toregister or you may submit your articles directly to mail@sapyard.com or mailsapyard@gmail.com with all details (text, images and code) and SAPYard team would publish it in your name.
If you want to have real time discussions and resolutions, do join our SAP Technical Telegram Group where we have more than 4675+ active SAP consultants from 6 Continents.
Please Note: You need to install Telegram App on your mobile first and then you can join the group using the above link.
<code>**&---------------------------------------------------------------------*
**& Report ZEXCEL_TAB
**&---------------------------------------------------------------------*
**&
**&---------------------------------------------------------------------*
REPORT zexcel_tab.
*&---------------------------------------------------------------------*
*& Include Z_EXCEL_DOWNLOAD_TOP Report Z_EXCEL_DOWNLOAD
*&---------------------------------------------------------------------*
*** Include objects
INCLUDE: ole2incl.
TYPES: data1(1500) TYPE c,
ty_data TYPE TABLE OF data1.
*** Variable Declarations
DATA: w_cell1 TYPE ole2_object,
w_cell2 TYPE ole2_object.
* Ole data Declarations
DATA: h_excel TYPE ole2_object, " Excel object
h_sheets TYPE ole2_object, " list of workbooks
h_sheet TYPE ole2_object, " workbook
h_cell TYPE ole2_object, " cell
worksheet TYPE ole2_object, "Worksheet
e_color TYPE ole2_object, "Color
range TYPE ole2_object, "Range
borders TYPE ole2_object, "Borders
h_sheet1 TYPE ole2_object, "First sheet
h_sheet2 TYPE ole2_object, "Second Sheet
h_sheet3 TYPE ole2_object, "Third Sheet
gs_font TYPE ole2_object. "Font
*** Variables
DATA: gt_1 TYPE ty_data WITH HEADER LINE,
gt_2 TYPE ty_data WITH HEADER LINE,
gt_3 TYPE ty_data WITH HEADER LINE,
deli(1) TYPE c,
gv_sheet_name(20) TYPE c.
DATA l_rc TYPE i.
"Delimeter
deli = cl_abap_char_utilities=>horizontal_tab.
*** Popoluate tables
SELECT ebeln,
bukrs,
bstyp,
bsart
INTO TABLE @DATA(gt_ekko)
FROM ekko
UP TO 5 ROWS
WHERE ebeln LIKE '45%'.
IF gt_ekko[] IS NOT INITIAL.
SELECT ebeln,
ebelp,
matnr,
bukrs
INTO TABLE @DATA(gt_ekpo)
FROM ekpo
FOR ALL ENTRIES IN @gt_ekko
WHERE ebeln = @gt_ekko-ebeln.
IF gt_ekpo[] IS NOT INITIAL.
SELECT ebeln,
ebelp,
gjahr,
belnr
INTO TABLE @DATA(gt_ekbe)
FROM ekbe
FOR ALL ENTRIES IN @gt_ekpo
WHERE ebeln = @gt_ekpo-ebeln
AND ebelp = @gt_ekpo-ebelp.
ENDIF.
ENDIF.
**Header for first sheet
gt_1 = |EBELN{ deli }BUKRS{ deli }BSTYP{ deli }BSART|.
APPEND gt_1.
CLEAR gt_1.
**Data for first sheet
LOOP AT gt_ekko INTO DATA(wa_ekko).
gt_1 = | { wa_ekko-ebeln } { deli } { wa_ekko-bukrs } { deli } { wa_ekko-bstyp } { deli } { wa_ekko-bsart } |.
APPEND gt_1.
CLEAR gt_1.
ENDLOOP.
**Header for second sheet
gt_2 = |EBELN{ deli }EBELP{ deli }MATNR{ deli }BUKRS|.
APPEND gt_2.
CLEAR gt_2.
**Data for second sheet
LOOP AT gt_ekpo INTO DATA(wa_ekpo).
gt_2 = | { wa_ekpo-ebeln } { deli } { wa_ekpo-ebelp } { deli } { wa_ekpo-matnr } { deli } { wa_ekpo-bukrs } |.
APPEND gt_2.
CLEAR gt_2.
ENDLOOP.
**Header for third sheet
gt_3 = |EBELN{ deli }EBELP{ deli }GJAHR{ deli }BELNR|.
APPEND gt_3.
CLEAR gt_3.
**Data for third sheet
LOOP AT gt_ekbe INTO DATA(wa_ekbe).
gt_3 = | { wa_ekbe-ebeln } { deli } { wa_ekbe-ebelp } { deli } { wa_ekbe-gjahr } { deli } { wa_ekbe-belnr } |.
APPEND gt_3.
CLEAR gt_3.
ENDLOOP.
* start Excel
IF h_excel-header = space OR h_excel-handle = -1.
CREATE OBJECT h_excel 'EXCEL.APPLICATION'.
ENDIF.
*--- get list of workbooks, initially empty
CALL METHOD OF h_excel 'Workbooks' = h_sheets.
SET PROPERTY OF h_excel 'Visible' = 1.
CALL METHOD OF h_sheets 'Add' = h_sheet.
PERFORM create_sheet TABLES gt_1
USING 'EKKO' h_sheet1.
PERFORM create_sheet TABLES gt_2
USING 'EKPO' h_sheet2.
PERFORM create_sheet TABLES gt_3
USING 'EKBE' h_sheet3.
* Free Excel objects
FREE OBJECT: h_cell,
h_sheets,
h_sheet,
h_excel.
*&---------------------------------------------------------------------*
*& Form CREATE_SHEET
*&---------------------------------------------------------------------*
FORM create_sheet TABLES it_sheet TYPE ty_data
USING iv_name
iv_sheet TYPE ole2_object.
DATA l_rc TYPE i.
gv_sheet_name = iv_name.
IF gv_sheet_name NE 'EKKO'.
GET PROPERTY OF h_excel 'Sheets' = iv_sheet .
CALL METHOD OF iv_sheet 'Add' = h_sheet.
SET PROPERTY OF h_sheet 'Name' = gv_sheet_name .
GET PROPERTY OF h_excel 'ACTIVESHEET' = worksheet.
ELSE.
GET PROPERTY OF h_excel 'ACTIVESHEET' = worksheet.
SET PROPERTY OF worksheet 'Name' = gv_sheet_name .
ENDIF.
**Copy data in clipboard
CALL METHOD cl_gui_frontend_services=>clipboard_export
IMPORTING
data = it_sheet[]
CHANGING
rc = l_rc
EXCEPTIONS
cntl_error = 1
error_no_gui = 2
not_supported_by_gui = 3
OTHERS = 4.
**choose first cell.
CALL METHOD OF h_excel 'Cells' = w_cell1
EXPORTING
#1 = 1 "Row
#2 = 1. "Column
**choose second cell.
CALL METHOD OF h_excel 'Cells' = w_cell2
EXPORTING
#1 = 1 "Row
#2 = 1. "Column
**Change width of column.
SET PROPERTY OF w_cell1 'Columnwidth' = 12.
**Make range from selected cell
CALL METHOD OF h_excel 'Range' = range
EXPORTING
#1 = w_cell1
#2 = w_cell2.
CALL METHOD OF range 'Select'.
** Paste data from clipboard to worksheet.
CALL METHOD OF worksheet 'Paste'.
**Logic to assign borders to fetched data in worksheet.
DATA(i) = 0.
LOOP AT it_sheet INTO DATA(ls_sheet).
i = i + 1.
DATA(first) = |A{ i }|. "Column from where you want to start providing borders.
DATA(second) = |D{ i }|. "Column up to which you want to provide the borders.
**Make range of selected columns.
CALL METHOD OF h_excel 'Range' = range
EXPORTING
#1 = first
#2 = second.
**Logic to assign border on left side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 7. "7 for left side
SET PROPERTY OF borders 'LineStyle'= 1. "type of line.
**Logic to assign border on right side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 8.
SET PROPERTY OF borders 'LineStyle'= 1.
**Logic to assign border on top side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 9.
SET PROPERTY OF borders 'LineStyle'= 1.
**Logic to assign border on bottom side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 10.
SET PROPERTY OF borders 'LineStyle'= 1.
**Logic to assign border on vertical side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 11.
SET PROPERTY OF borders 'LineStyle'= 1.
**Logic to assign border on horizontal side.
CALL METHOD OF range 'Borders' = borders NO FLUSH
EXPORTING #1 = 12.
SET PROPERTY OF borders 'LineStyle'= 1.
ENDLOOP.
** Logic to change font and color of header data.
CLEAR i.
DO 4 TIMES.
i = i + 1.
CALL METHOD OF h_excel 'CELLS'= h_cell NO FLUSH
EXPORTING #1 = 1
#2 = i.
GET PROPERTY OF h_cell 'FONT' = gs_font NO FLUSH.
SET PROPERTY OF gs_font 'BOLD' = 1 NO FLUSH.
SET PROPERTY OF gs_font 'SIZE' = 12.
CALL METHOD OF h_cell 'INTERIOR' = range.
SET PROPERTY OF range 'ColorIndex' = 5.
SET PROPERTY OF range 'Pattern' = 1.
ENDDO.
ENDFORM. " CREATE_SHEET</code>
With A to Z of OLE Excel in ABAP 7.4 we explored a lot about excel file in SAP. GUI_DOWNLOAD/UPLOAD is also very straight forward. But whenever I get requirement to read multiple files from application server, do processing and store multiple files on another server… well it stressed me hard in the beginning.
The other day one junior asked
me, when we have GUI_DOWNLOAD and UPLOAD, then why do we need to save file in
AL11. I had to say one line, so that you can shut down your laptop and go
to sleep and when you come in the morning the files would be ready for
you to review in AL11. He did not understand my abstract sentence and I am
sure, many of you might also be wondering what I wrote.
Question: Why cannot we do GUI_DOWNLOAD/UPLOAD in background mode?
Answer: Jobs can be scheduled in background mode not only at the current time, but it can also be scheduled for future time. If you schedule a background job which does GUI_DOWNLOAD/UPLOAD, your program would dump or cancel out. The reason being, SAP did not want to take chance. GUI_DOWNLOAD/UPLOAD needs your laptop/computer to be switched on for it to save the data in the presentation server path. Say you scheduled a job which has GUI_DOWNLOAD/UPLOD and switched off your laptop and went to bed. When the job actually triggers, where will it save the file since your laptop is switched off? Do you get the catch 22 situation?
Therefore SAP came with Application File Concept (although not just for background job).
Now, let us do the same exercise. You have OPEN DATASET (for AL11) syntax in your program. You schedule a job. Switch off your laptop and go to sleep. In the middle of the night, the job triggers and it saves the file in Application Server (AL11). It is not dependent on your local laptop or desktop. It is at the Server level and Servers normally never sleep (except at planned outage or downtime).
Although the File Directory (AL11) concepts are well known to all of us, we always Google for the syntax/tcode/FMs and use them without worrying much about the details and concepts behind the scene (especially lazy programmers like me).
So I thought of doing a favor to myself. Next time I need anything related to T-Code AL11 i.e. SAP Directories, I will not Google. Instead, I will come to this SAPYard page directly where I have listed almost everything we do in AL11. This should be a one stop page for not only all ABAP developers, but also the Basis Team and the Functional/Business team should also find it enriching.
In this article we will try to cover everything about AL11. What AL11 is all about? How to create folder in AL11? Ways to download files from AL11 and upload multiple files into AL11?How to delete files etc. As the title suggests, this is A to Z of all AL11 Operations.
1. What is AL11 and what is it’s use?
Well, AL11 is used for storing files/data on application server so that anyone (SAP) and even third-party systems (Non-SAP) can pick files from it. It is SAP File Directory as it contains directories, multiple folders and files in them. We can upload/download/modify/delete files/folders in t-code AL11.
Below is a quick look
of AL11 t-code.
It has two columns:
Name of Directory Parameter: Contains the directory names. It can contain multiple folders and files.
Directory: Contains the whole path of the folders.
2. How to create our own directory in AL11?
This is not supposed to be done by any ABAP developer and usually we will not get any authorization to perform this activity.
Just for sake of a complete document, I coaxed one of my Basis friends in this crime. See I have secret contacts in the SAP Administration and Security Team. My Basis friend was kind enough to provide the below steps. Now, I am wondering what would be the return price I need to pay for this favor. Just kidding. Tell me what you want. Open offer for you.
He
said it is no rocket science.
Directories
are nothing but folders on Server System paths.
Basis folks and some privileged ones who have the authorization, log into the SAP Server System and create Folders and File. Just like how we create Folders and Files in our Windows/MAC Laptop and PC. As simple as that.
Now suppose we want to create a directory name as ‘ZMM_SOURCE’ on application server, Basis team will login in that Server System and create a folder with the same name which will be our directory.
Below is a screenshot of one such SAP Server System. It looks like normal Windows Folder. Right? But look closely. It is not part of the Windows operating system. It is another Server. If they create a folder in this Server, we can see that same folder in AL11 t-code in SAP.
Now
we need to configure this directory in AL11.
There is an option in AL11 called name ‘Configure User Directories’
We need to click on that button and need to configure our directory.
After execution, you can see your directory in AL11.
If you double click on this, you will see one file in it as I already created it.
That’s all. These directories are nothing but folders and as easy as right click and create new folder action of Windows.
This is also not part
of ABAP work. Only Basis teams have authorization to create it. And this is
nothing but creating new subfolder under our main folder in application server.
Whereas main folder works like directory and subfolder works like folders/subdirectories
in AL11.
Whatever locations on server system, where we are creating folders are called as Physical Paths of directories.
If main folder/directory is already configured then there is no need to configure the sub directory again after addition of subfolders. Those would be displayed in AL11 directory path automatically.
4. How to write/upload files on specific folder of AL11?
There are 2 ways to
upload the file in AL11:
i) Manually via T-Code CG3Z:
After hitting this T-Code CG3Z, you will get below screen.
Source file on front end: Select your file from your laptop or desktop system and put it in this parameter.
Target file on application server: Provide path of file location in the AL11 directory.
Overwrite file: If it is checked, whatever file name you provided in target file parameter, if it exists in AL11, it will be overwriting with provided file else it will create new file there.
I have provided below details:
And got below message after clicking on the upload
button:
Let’s check in AL11 folder now. We can see our file test.xls is created here.
Note: We can ONLY read Doc or Txt files directly by double clicking on the file in AL11 but we can’t read XLS or XLSX files directly. XLS File will give below error.
This error comes because .XLS is a proprietary format of Microsoft. So, unless you have some addons installed on SAP Server, you can’t read XLS files from Application Server.
Do not worry. That is not the end of the world. If you really want to read the XLS file, you can download the file to your local machine (laptop/desktop) and then you can open and read it.
This brings us to question. How to download
AL11 files? Please be patient. It will be covered in the later sections in this
article.
Here we are still covering how to create
Folders/Files in AL11. Let’s look the second way.
ii) By writing ABAP code:
As an ABAP Programmer, usually we do not play with t-codes. We want everything should be done by our code. That gives us more sense of Power.
Sometimes we get requirement to upload multiple files on application server in a single go with some conditions. Let us look into one practical example:
Suppose I have a requirement to collect data
from EKKO and EKPO tables and prepare multiple files for each new PO (EBELN)
and store them on AL11 path. Each file should have PO number as file_name.
Sounds interesting yeah… Let’s do this…
First, we will select data from EKKO and EKPO
tables and stores them in one internal table.
<code>*** Popoluate tables
SELECT ebeln,
bukrs,
bstyp,
bsart
INTO TABLE @DATA(gt_ekko)
FROM ekko
UP TO 15 ROWS
WHERE ebeln LIKE '45%'.
IF gt_ekko[] IS NOT INITIAL.
SELECT ebeln,
ebelp,
matnr,
bukrs
INTO TABLE @DATA(gt_ekpo)
FROM ekpo
FOR ALL ENTRIES IN @gt_ekko
WHERE ebeln = @gt_ekko-ebeln
ORDER BY PRIMARY KEY.
ENDIF.</code>
There is a table called FILEPATH.
All folders of an application server are stored in the database table FILEPATH. We can pass our directory/folder name there and can confirm whether the folder exists or not.
<code>CONSTANTS: lc_filename TYPE filepath-pathintern
VALUE 'ZMM_BATCH_DATA_FOR_ASPECT'.
SELECT SINGLE pathintern
FROM filepath
INTO @DATA(lv_logical_path)
WHERE pathintern = @lc_filename.</code>
As we want to upload multiple files in our folder, we need to call FM FILE_GET_NAME_USING_PATH to get full path of that file and then can open it with Dataset concept:
Now using OPEN
DATASET, we can open the file to write fetched data from EKPO into it.
<code>"Open the file to write.
OPEN DATASET lv_filepath FOR OUTPUT IN TEXT MODE ENCODING DEFAULT. </code>
Mode ‘Output’ is used
for writing data in application server file (all dataset concepts are explained
in the 5th point below).
Now using TRANSFER and
CLOSE DATASET concept, we can pass all relevant data to corresponding files,
save them and take exit from the files.
Whole executable code
is provided below:
<code>REPORT zal11_working.
CONSTANTS: lc_e TYPE c VALUE 'E',
lc_s TYPE c VALUE 'S',
lc_text TYPE string VALUE 'No specific data found for the input',
lc_msg TYPE string VALUE 'The batch data is/are stored at the directory successfully',
lc_filename TYPE filepath-pathintern VALUE 'ZMM_BATCH_DATA_FOR_ASPECT'.
DATA: lv_filepath TYPE string.
*** Popoluate tables
SELECT ebeln,
bukrs,
bstyp,
bsart
INTO TABLE @DATA(gt_ekko)
FROM ekko
UP TO 15 ROWS
WHERE ebeln LIKE '45%'.
IF gt_ekko[] IS NOT INITIAL.
SELECT ebeln,
ebelp,
matnr,
bukrs
INTO TABLE @DATA(gt_ekpo)
FROM ekpo
FOR ALL ENTRIES IN @gt_ekko
WHERE ebeln = @gt_ekko-ebeln
ORDER BY PRIMARY KEY.
ENDIF.
IF gt_ekpo[] IS INITIAL.
MESSAGE lc_text TYPE lc_s DISPLAY LIKE lc_e.
ELSE.
CONSTANTS: lc_filename TYPE filepath-pathintern VALUE 'ZMM_BATCH_DATA_FOR_ASPECT'.
SELECT SINGLE pathintern
FROM filepath
INTO @DATA(lv_logical_path)
WHERE pathintern = @lc_filename.
LOOP AT gt_ekpo ASSIGNING FIELD-SYMBOL(<fs_ekpo>).
AT NEW ebeln.
"Logic to create unique file name with each PO number.
DATA(lv_file_post) = | { <fs_ekpo>-ebeln } .csv|.
"Remove spaces.
CONDENSE lv_file_post NO-GAPS.
CALL FUNCTION 'FILE_GET_NAME_USING_PATH'
EXPORTING
logical_path = lv_logical_path
file_name = lv_file_post
IMPORTING
file_name_with_path = lv_filepath
EXCEPTIONS
path_not_found = 1
missing_parameter = 2
operating_system_not_found = 3
file_system_not_found = 4
OTHERS = 5.
"Open the file to write.
OPEN DATASET lv_filepath FOR OUTPUT IN TEXT MODE ENCODING DEFAULT.
"Set intensity of the background color to default.
FORMAT INTENSIFIED ON.
"Prepare header of file with comma ',' separated.
DATA(lv_file) = |EBELN,EBELP,MATNR,BUKRS|.
"set non-intensified background color.
FORMAT INTENSIFIED OFF.
"Transfer header data line by line to file.
TRANSFER lv_file TO lv_filepath.
CLEAR lv_file.
ENDAT.
"Prepare data of file with comma ',' separated.
lv_file = |{ <fs_ekpo>-ebeln } , { <fs_ekpo>-ebelp } , { <fs_ekpo>-matnr } , { <fs_ekpo>-bukrs }|.
"Remove spaces.
CONDENSE lv_file.
"Transfer data line by line to file.
TRANSFER lv_file TO lv_filepath.
AT END OF ebeln.
"CLose the file and save on Application server.
CLOSE DATASET lv_filepath.
IF sy-subrc IS INITIAL.
MESSAGE lc_msg TYPE lc_s.
ENDIF.
CLEAR lv_filepath.
ENDAT.
CLEAR: lv_file.
ENDLOOP.
ENDIF.</code>
By using COMMAND-BREAK statements (AT END OF), I have achieved to create new file for new PO. The output is like
below:
As shown above, these
are different files with different POs. If you open any file, you can get
header text line as well as values of cells.
Above we learned how to write to AL11 file. Now let’s learn how to download them. Here we will discuss 4
different approaches:
i) Download via T-Code CG3Y:
After hitting this
tcode, you will see below screen:
Source file on application server: Provide the path of file location from AL11 directory
which you need to download.
Target file on front end: Provide the file name from your system (can be new file) and
put it in this parameter.
Overwrite file: If
it is checked, whatever file name you provided in target file parameter, if it
exists in system, it will be overwriting with provided file else it will create
new file there.
Transfer format for data: Usually ASC format works. Do check your requirement. You
might need BIN as well.
As I wanted to save/download test excel file
from application server to my desktop, I have provided below details:
It will create test
file on the desktop and if it is already present then will overwrite it.
ii) Download using list menu:
If the file is not so long, you can directly download the content from AL11. Navigate to the file.
Then Open the file. On System Menu, go to List->Save/Send/Local file:
Question: Why is my AL11 exported file truncated?
Answer: In some case, the exported file is truncated. For List export in AL11, the limit is 512 characters. Apply SAP Note 952766 to increase the limit in AL11.
iii) By using FM ARCHIVFILE_SERVER_TO_CLIENT:
The standard Function module ARCHIVFILE_SERVER_TO_CLIENT can be used for downloading file from AL11. Just provide the PATH:
the full path to the file on SAP server and the TARGETPATH: the destination
path.
iv) By writing ABAP code:
Now if we have
complex requirement like reading/downloading multiple files and do some calculation
and massaging of data from the AL11 file then we need to create an ABAP
program.
For downloading the
file from application server, we must know the below concepts:
a) OPEN DATASET:
<code>OPEN DATASET dset FOR access IN mode [position]
[os_additions]
[error_handling]. </code>
Dset: Contains
the file path of AL11.
Access:
It has below options –
Input: It opens the file for reading. If the file does not exist, the sy-subrc is set to 8.
Output: Opens the file for writing. If the file exists, the content will be deleted and replaced. If the file does not exist, the file will be created.
Appending: It opens the file for appending. The new content will be added to the end of existing file or the file will be created if it does not exist. SY-SUBRC = 4 will be triggered if appending is not successful.
Update: It
opens the file for changes to the existing content.
Mode: You can specify which mode you want to read the file like
binary or text.
ii) TRANSFER DATASET: This statement passes the content of data object dobj to
the file specified in dset.
Syntax:
<code>TRANSFER dobj TO dset [LENGTH len]
[NO END OF LINE]. </code>
The file must be open
for writes, appends, or changes. If a closed or invalid file is accessed, a
handleable exception is raised.
iii) CLOSE DATASET: This statement closes the file specified in dset.
Syntax:
<code>CLOSE DATASET dset.</code>
If the file is
already closed or does not exist, the statement is ignored and the return code
sy-subrc is set to 0.
An opened file that was not explicitly closed using CLOSE
DATASET is automatically closed when the program is exited.
Did I bore you to death with too much of theories?? ufff.. Even I am tired, so I can understand your plight.
Let’s bring some
charm to our learning with the below examples:
In the point 4 above, we created multiple files on our application server now. Let’s read them all. Here I have passed only one file for demonstration. You can make it dynamic as well by passing all file names:
<code>DATA:v_file LIKE v_excel_string VALUE 'E:\Interfaces\ZMM_BATCH_DATA_FOR_ASPECT\4500000001.csv'. " name of the file
*--- read the file from the application server
OPEN DATASET v_file FOR INPUT IN TEXT MODE ENCODING DEFAULT.</code>
Open the file in READ
mode. We can read each line from the file in a loop and split the data based on
delimiter to separate the data and store in internal table:
<code>DO.
READ DATASET v_file INTO wa_tab.
IF sy-subrc IS INITIAL.
CLEAR it_string[].
SPLIT wa_tab AT delimiter INTO TABLE it_string.
LOOP AT it_string ASSIGNING FIELD-SYMBOL(<fs>).
ASSIGN COMPONENT sy-tabix OF STRUCTURE wa_tab2 TO FIELD-SYMBOL(<fs_f>).
IF sy-subrc = 0.
<fs_f> = <fs>.
ENDIF.
AT LAST.
APPEND wa_tab2 TO it_tab2.
ENDAT.
ENDLOOP.
ELSE.
EXIT.
ENDIF.
ENDDO. </code>
After receiving the
data in an internal table, we can CLOSE the file using CLOSE DATSET syntax:
<code>CLOSE DATASET v_file.</code>
Here I have used ALV factory method to display the results in ALV format. Below is the whole executable program:
<code>REPORT zal11_working.
*--- Type structure declaration.
TYPES: BEGIN OF ty_tab1,
ebeln TYPE string,
ebelp TYPE string,
matnr TYPE string,
bukrs TYPE string,
END OF ty_tab1.
*--- work area and internal table declaration.
DATA : it_tab TYPE STANDARD TABLE OF alsmex_tabline,
wa_tab TYPE alsmex_tabline,
it_tab2 TYPE STANDARD TABLE OF ty_tab1,
wa_tab2 TYPE ty_tab1,
it_string TYPE TABLE OF string.
*--- variable declaration
DATA: v_excel_string(2000) TYPE c,
v_file LIKE v_excel_string VALUE 'E:\Interfaces\ZMM_BATCH_DATA_FOR_ASPECT\4500000001.csv', " name of the file
delimiter TYPE c VALUE ',',
t_field(1000) TYPE c OCCURS 0. " delimiter with default value space
*--- Factory method objects
DATA: lr_table TYPE REF TO cl_salv_table,
lr_columns type ref to cl_salv_columns_table,
lr_column type ref to cl_salv_column_table.
*--- read the file from the application server
OPEN DATASET v_file FOR INPUT IN TEXT MODE ENCODING DEFAULT.
IF sy-subrc NE 0.
WRITE:/ 'error opening file'.
ELSE.
DO.
READ DATASET v_file INTO wa_tab.
IF sy-subrc IS INITIAL.
CLEAR it_string[].
SPLIT wa_tab AT delimiter INTO TABLE it_string.
LOOP AT it_string ASSIGNING FIELD-SYMBOL(<fs>).
ASSIGN COMPONENT sy-tabix OF STRUCTURE wa_tab2 TO FIELD-SYMBOL(<fs_f>).
IF sy-subrc = 0.
<fs_f> = <fs>.
ENDIF.
AT LAST.
APPEND wa_tab2 TO it_tab2.
ENDAT.
ENDLOOP.
ELSE.
EXIT.
ENDIF.
ENDDO.
ENDIF.
CLOSE DATASET v_file.
*------display the data from the internal table
IF it_tab2[] IS NOT INITIAL.
DELETE it_tab2 INDEX 1. "Delete header line.
ENDIF.
cl_salv_table=>factory(
IMPORTING r_salv_table = lr_table
CHANGING t_table = it_tab2 ).
lr_columns = lr_table->get_columns( ).
try.
lr_column ?= lr_columns->get_column( 'EBELN' ).
lr_column->set_short_text( 'EBELN' ).
lr_column ?= lr_columns->get_column( 'EBELP' ).
lr_column->set_short_text( 'EBELP' ).
lr_column ?= lr_columns->get_column( 'MATNR' ).
lr_column->set_short_text( 'MATNR' ).
lr_column ?= lr_columns->get_column( 'BUKRS' ).
lr_column->set_short_text( 'BUKRS' ).
catch cx_salv_not_found. "#EC NO_HANDLER
endtry.
lr_table->display( ).</code>
After all you would not want to keep the test files in the application server after testing. So, to delete all unwanted files, you can use below FM: EPS_DELETE_FILE
It has below structures in
it:
Enter the File name in the
FILE_NAME and the directory path (Excluding the file name) in the DIR_NAME and
execute.
After execution, the file will disappear permanently from the AL11 location. As Spiderman’s uncle said, “With more Power comes more Responsibility“. So please use it judiciously.
This FM we can use at program level as well if required to delete files in mass.
We can use DELETE DATASET statement too at program level to delete files from application server.
Syntax:
<code>DELETE DATASET dset.</code>
7. Standard SAP Report to upload and download files?
Yes, you read it right. SAP
has already provided a report which is used for uploading file from
presentation server to application server and vice versa. Awesome yeah
Report name: CACS_FILE_COPY
When you execute this report,
you will get below selection screen.
Now I wanted to store a file
on application server from my desktop:
After successful execution,
we can get below pop-up message:
Now let’s check in AL11:
Ohh yeah!!.. So easy and got
the file too in AL11.
8. Get all files from the directory/folder of application server?
If you want to know what files
are present in any AL11 folder, then you can use below 2 FMs:
EPS_GET_DIRECTORY_LISTING or EPS2_GET_DIRECTORY_LISTING
Let’s see how it works.
Here, we have to provide
directory/folder path to get list of files present in it.
As you see in above snapshot, we got 62 files of our directory. If you check the DIR_LIST table, you will get below details:
This functionality you can
use in the program whenever you want to do mass processing like read
file/download file/delete file etc from single directory. It indeed saves time
and good for performance.
Well, what else you want to do with AL11? I am blank now. Hell tired. Honestly, I don’t think you will need to search any other thread in future for any queries related to AL11. This page should be mother of all pages for AL11 and one source of truth for all AL11 requirements.
If I learn something new
about AL11, I will come back and update this page.
Do you have any trick or tip
to share for AL11? Feel free to email us at mail@sapyard.com
or mailsapyard@gmail.com. Or you may
also leave your findings in the comment section below.
Sharing is Caring. Would you mind sharing the link so that it reaches maximum audience? And our SAP Fraternity can benefit from our work.
If you want to have real time discussions and resolutions, do join our SAP Technical Telegram Group where we have more than 4885+ active SAP consultants from 6 Continents.
Please Note: You need to install Telegram App on your mobile first and then you can join the group using the above link.
OK. The title might look like click bait, but ABAP developers do have the Power to Kill the SAP Session.
This document has provided me the opportunity to flaunt the power of an ABAP team members. You have always undermined the service of an ABAPer. You have used them for SAP house cleaning tasks. But ABAP team members are the backbone of any SAP Project. So never underestimate the powerful ABAPer.
Remember my last article A to Z of AL11? A basis colleague of mine helped me know about the server details/creation of directory etc. Now it was payback time. I was a little tensed, what I might need to offer for the help but my colleague is a simple guy. He just wanted a help with an annoying daily job nothing else. Fewww!!
Now
was the time to thank him with my ABAP skill.
Over a tea break, he casually told me that the Basis team has one job scheduled to update some master data. They schedule the job during the lean window time frame after business hours but somehow very often they find some users locking the transaction the basis team needs to update. And if any user is sitting in that material master, the basis job failed and the basis team had to re-run again.
Our
Basis Team members were frustrated as they had to monitor the job every day and
reschedule it again if it failed.
After
hearing his pain, I could empathize with his plight. After all ABAPers are also
victim of similar agony. Anyway, I had to return the favor and this was the
right time.
Here is the summary of the issue:
There is a batch job which updates the material master (for simplicity assume the data in table MARA) on daily basis. But if some user has logged into that material in MM02 then the job fails to update the material master and logs the lock object error. Basis team has to re-run the job again.
How many times have you opened the SM30 t-code or MM02 or VA02 sessions in the evening and left for home without signing off your system? Now anyone working late night or in another time zone cannot use SM30 to maintain the table you have opened. Next time you are done for the day, log off your SAP system and shut down your machine. It will help global warming and keep the earth green. (from Gyan Bhandar)
My
Basis friend wanted a solution on priority basis.
High level Solution Architecture
Check how many users are locking the Material Master transaction.
Provide Pop Up warning message to those users to save their session and come out of transaction within 5 minutes.
After 5 minutes (time should be configurable and not hard coded), check again for those are still locking the transaction. Provide the 2nd and final warning to save their work within 5 minutes.
After further 5 minutes, the material master transaction which the user is locking would be released/closed. All other transactions and sessions which are not interfering with the job is left untouched.
Once
we know which users are locking our transaction, we need to find out how many
sessions are open in the users’ system.
There
is a standard ABAP class SERVER_INFO which will give us all active session
info.
<code>*** Get all user sessions
CREATE OBJECT server_info.
gt_session_list = server_info->get_session_list( with_application_info = 1 ).</code>
Now
we need to filter out the sessions which belong to our users with client
details.
<code>*** Filter user sessions on the basis of username and client.
gt_session_list_user = VALUE #( FOR ls_lock_details IN gt_lock_details
FOR ls_session_list IN gt_session_list
WHERE ( user_name = ls_lock_details-guname
AND tenant = sy-mandt )
( ls_session_list ) ).</code>
By
now, we have all open sessions of our target users only.
Time to send warnings and notifications to these stubborn users. May be hard working users.
We use FM ‘POPUP_TO_CONFIRM’ for pop up messages. But there is a catch. POPUP_TO_CONFRIM will give message to the person who is running the report or program. The user who has opened the screen in change mode will not get notification if we use this FM. So, what is the alternative?
Chat in SAP. Yes, you read it right. You can send anonymous pop up messages to anyone in your office SAP network. The good thing is they will not be able to find the sender name easily. I hope you are not thinking what I am thinking. Ready for teenage prank?
FM ‘TH_POPUP’ helps us to send pop up messages to any user in the network system. Works
perfectly in our scenario.
<code>DATA(gv_msg) = |You are locking Transaction { gv_tcode }. Please save and leave the transaction within 5 Secs!!!|.
DATA gv_message TYPE sm04dic-popupmsg.
gv_message = gv_msg.
LOOP AT gt_lock_details INTO gs_lock_details.
CALL FUNCTION 'TH_POPUP'
EXPORTING
client = sy-mandt
user = gs_lock_details-guname
message = gv_message
EXCEPTIONS
user_not_found = 1
OTHERS = 2.
ENDLOOP.</code>
Looping
through the users table, we can send notification to all the users who are locking
our transaction.
Let’s
wait from 5 minutes so that users can save the data and close the session.
<code>WAIT UP TO 300 SECONDS.</code>
ABAP Wait statement in SAP Workflow nearly brought my SAP Production system to halt. That is an interesting story which I will tell some other day. I even have the title of the article in mind – “Wait Wait.. do NOT use me”.
After
5 minutes, check again for the list of users who are sitting on our
transaction. In between if any new user locks the transaction; this poor guy
would only get 5 minutes. They would not get the 2nd warning as the
first list of users are getting.
<code>gv_msg = |grrr..You are still locking Transaction { gv_tcode }. Your session will be killed soon!!!|.
gv_message = gv_msg.
LOOP AT gt_lock_details ASSIGNING <fs_lock_details>.
CALL FUNCTION 'TH_POPUP'
EXPORTING
client = sy-mandt
user = <fs_lock_details>-guname
message = gv_message
EXCEPTIONS
user_not_found = 1
OTHERS = 2.
ENDLOOP.</code>
Wait
from final 5 minutes to give the users one more chance to save the data and
close the session.
<code>WAIT UP TO 300 SECONDS.</code>
After the 10th minute, take the list of users who are still locking the transaction. They are the guys who have already left for the day or are talking on phone for more than 10 minutes. By the way, with whom would they talk at these late hours?
Action Time.
We will use system kernel call to get all active session details same as t-code SM04.
<code> "get technical info for the user
CALL 'ThUsrInfo' ID 'OPCODE' FIELD opcode_usr_info
ID 'TID' FIELD <fs_session_list>-logon_hdl
ID 'WITH_APPL_INFO' FIELD with_appl_info
ID 'TABLE' FIELD gt_usr_info[].</code>
Here we will get a huge list of multiple rows for each session number (0-6) that the user has opened. Each login by a user is assigned a unique login ID which is generated based on parameters you log in with viz: the client number, system, and language. This session ID has the form of TXX_UXXXXX, where X can be any number 0-9.
Each
session then has a session ID which is comprised of first the login ID as
described above, with an appended _MX, where X in this case is the session
number, 0-6 (session 1-7). So, we are interested in the .session value of this
technical list:
modelinfo[0] is for session 1. Similarly [1] and [2] would be for session 2 and 3 respectively.
modeinfo[1].session = T51_U11407_M1
Session 3 would have: modeinfo[2].session = T51_U11407_M2
Now we need to use the ‘/UPD’ value in the technical info to get the exact session used by the user. You guessed it right. UPD must be for Update Mode.
<code>CONCATENATE <fs_lock_details>-gusrvb '/UPD' INTO DATA(gv_value).
READ TABLE gt_usr_info ASSIGNING FIELD-SYMBOL(<fs_usr_info>)
WITH KEY value = gv_value.
IF sy-subrc IS INITIAL.
"The key for the value is 'modeinfo[X].enq_info'.... we need just the X
gv_modus_index = <fs_usr_info>-field+9(1).
ELSE.
CONTINUE.
ENDIF.</code>
Now
we will do above process and make the list of the users with required sessions
which we need to close/kill.
After preparing the final user list, I tried to figure out the right way to kill the session of user but failed if initial attempt.
I tried to do the BDC recording of SM12 with delete functionality but that didn’t work. Not sure, may be I did not have the right authorization. I did not dig deep though.
Finally let’s go in for the kill and shut down that session(s). Once we do that, we log which session/user/transaction combo we deleted to the spool so that no one can cry foul play later. We gave enough time people!
Don’t feel sorry for closing the sessions. We warned the users twice, gave them 10 minutes to save the thing but if they are still blocking our transaction. We were forced for the attack.
Sometimes, we need to be difficult. Kicking the users out of the system ensures that the overall system is synced with up to date master data.
This is my favorite line from Spiderman “With great power comes great responsibility”. Always use your super power for the good reason.
Check this video for the output in action:
You can use the below code in your system and execute it. Do not forget to remove the hard code 300 seconds and my sy-uname. If your system is below 7.4, you need to change the new syntax to old and also you might not have some data elements.
Working Code
<code>*&---------------------------------------------------------------------*
*& Report ZUSER_SESSION_KILL
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zuser_session_kill.
*** Types declarations.
DATA:BEGIN OF gt_usr_info OCCURS 10,
field(40),
value(80),
END OF gt_usr_info.
*** Variable declarations.
DATA: gv_time TYPE string,
gv_tcode TYPE eqetcode,
gv_ssi_session_id TYPE ssi_session_id,
gv_modus_num_found TYPE ssi_session_hdl,
gv_modus_index_char TYPE numc1,
gv_message TYPE sm04dic-popupmsg,
gv_modus_string TYPE string,
gv_modus_index TYPE string,
gv_stop TYPE c,
th_opcode(1) TYPE x.
*** Internal table declarations.
DATA: gt_lock_details TYPE TABLE OF seqg3,
gt_session_list TYPE TABLE OF ssi_session_info,
gt_session_list_user TYPE TABLE OF ssi_session_info,
gt_sessions_user TYPE TABLE OF ssi_session_info.
*** Workareas declarations.
DATA: server_info TYPE REF TO cl_server_info,
bdcdata LIKE bdcdata OCCURS 0 WITH HEADER LINE.
*** Constants declarations.
CONSTANTS: opcode_usr_info LIKE th_opcode VALUE 52,
with_appl_info TYPE ssi_bool VALUE 1. " this is the internal SAP number
CLEAR gv_time.
"To stop the loop processing.
WHILE gv_stop NE abap_true.
"send notification to users, those who are locking our table, do it till 10 seconds.
IF gv_time IS INITIAL. " First warning message after 5 minutes
*** fm to GET all lock details of mara table (like sm12)
CALL FUNCTION 'ENQUEUE_READ'
EXPORTING
gname = 'MARA'
guname = '$SBHANDARE'
TABLES
enq = gt_lock_details
EXCEPTIONS
communication_failure = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
EXIT.
ENDIF.
DATA(gv_msg) = |You are locking Transaction { gv_tcode }. Please save and leave the transaction within 5 Secs!!!|.
gv_message = gv_msg.
LOOP AT gt_lock_details ASSIGNING FIELD-SYMBOL(<fs_lock_details>).
CALL FUNCTION 'TH_POPUP'
EXPORTING
client = sy-mandt
user = <fs_lock_details>-guname
message = gv_message
EXCEPTIONS
user_not_found = 1
OTHERS = 2.
ENDLOOP.
WAIT UP TO 300 SECONDS.
gv_time = '01'.
FREE: gt_lock_details[], gv_message, gv_msg.
"send notification to users, those who are locking our table after first notification too, do it from 5 seconds to 45 seconds.
ELSEIF gv_time = '01'. " Second warning message
*** FM to get all lock details of MARA table (like SM12)
CALL FUNCTION 'ENQUEUE_READ'
EXPORTING
gname = 'MARA'
guname = '$SBHANDARE'
TABLES
enq = gt_lock_details
EXCEPTIONS
communication_failure = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
EXIT.
ENDIF.
gv_msg = |Uff...You are still locking Transaction { gv_tcode }. Your session will be killed soon!!!|.
gv_message = gv_msg.
LOOP AT gt_lock_details ASSIGNING <fs_lock_details>.
CALL FUNCTION 'TH_POPUP'
EXPORTING
client = sy-mandt
user = <fs_lock_details>-guname
message = gv_message
EXCEPTIONS
user_not_found = 1
OTHERS = 2.
ENDLOOP.
WAIT UP TO 300 SECONDS.
gv_time = '03'.
FREE: gt_lock_details[], gv_message, gv_msg.
"kill the user sessions after 50 seconds.
ELSEIF gv_time = '03'.
gv_stop = abap_true.
*** FM to get all lock details of MARA table (like SM12)
CALL FUNCTION 'ENQUEUE_READ'
EXPORTING
gname = 'MARA'
guname = '$SBHANDARE'
TABLES
enq = gt_lock_details
EXCEPTIONS
communication_failure = 1
system_failure = 2
OTHERS = 3.
IF sy-subrc <> 0.
EXIT.
ENDIF.
*** Get all user sessions
CREATE OBJECT server_info.
gt_session_list = server_info->get_session_list( with_application_info = 1 ).
*** Filter user sessions on the basis of username and client.
gt_session_list_user = VALUE #( FOR ls_lock_details IN gt_lock_details
FOR ls_session_list IN gt_session_list
WHERE ( user_name = ls_lock_details-guname
AND tenant = sy-mandt )
( ls_session_list ) ).
FREE: gt_session_list[].
LOOP AT gt_lock_details ASSIGNING <fs_lock_details>.
gv_tcode = <fs_lock_details>-gtcode.
CLEAR gt_sessions_user.
LOOP AT gt_session_list_user ASSIGNING FIELD-SYMBOL(<fs_session_list>). " loop at all open sessions to build a list of just the user sessions
CLEAR gv_modus_index.
CLEAR gt_usr_info[].
"get technical info for the user
CALL 'ThUsrInfo' ID 'OPCODE' FIELD opcode_usr_info
ID 'TID' FIELD <fs_session_list>-logon_hdl
ID 'WITH_APPL_INFO' FIELD with_appl_info
ID 'TABLE' FIELD gt_usr_info[].
"Need to use the '/UPD' value in the technical info to get the exact session used by user
CONCATENATE <fs_lock_details>-gusrvb '/UPD' INTO DATA(gv_value).
READ TABLE gt_usr_info ASSIGNING FIELD-SYMBOL(<fs_usr_info>)
WITH KEY value = gv_value.
IF sy-subrc IS INITIAL.
"The key for the value is 'modeinfo[X].enq_info'.... we need just the X
gv_modus_index = <fs_usr_info>-field+9(1).
ELSE.
CONTINUE.
ENDIF.
" If we found a session with that number, we know we had the correct logon... we can add this to the user list
IF gv_modus_index IS NOT INITIAL.
" convert to type ssi_session_handle for later lookup
gv_modus_num_found = gv_modus_index.
" get the session id for this specific session.
CONCATENATE 'modeinfo[' gv_modus_index '].session' INTO DATA(gv_field). " this time we need to find the value from the field
READ TABLE gt_usr_info ASSIGNING <fs_usr_info> WITH KEY field = gv_field.
IF sy-subrc IS INITIAL.
gv_ssi_session_id = <fs_usr_info>-value.
ENDIF.
APPEND <fs_session_list> TO gt_sessions_user.
ENDIF.
ENDLOOP.
IF gt_sessions_user[] IS NOT INITIAL.
SORT gt_sessions_user BY session_hdl ASCENDING.
READ TABLE gt_sessions_user TRANSPORTING NO FIELDS
WITH KEY session_hdl = gv_modus_num_found .
IF sy-subrc IS INITIAL.
" Kill only the session from the table that matches the index of the modus found
gv_modus_index_char = sy-tabix.
CLEAR gv_modus_string.
CONCATENATE 'MODUS-MTCODE(0' gv_modus_index_char ')' INTO gv_modus_string.
ENDIF.
IF gv_modus_index_char EQ '0'.
CONTINUE.
ENDIF.
" First ensure that sm04 is called and set to the sessions view
CLEAR bdcdata[].
PERFORM bdcdaten USING:
'X' 'SAPMSSY0' '0120',
' ' 'BDC_CURSOR' '04/03',
' ' 'BDC_OKCODE' '=SESSIONS'.
" call transaction SM04, sync (S), no screens
CALL TRANSACTION 'SM04'
USING bdcdata
MODE 'N'
UPDATE 'S'.
" Now use bdc to end the session with that session id
CLEAR bdcdata[].
PERFORM bdcdaten USING:
'X' 'SAPMSSY0' '0120',
' ' 'BDC_CURSOR' '04/03',
' ' 'BDC_OKCODE' '=&OL0'.
PERFORM bdcdaten USING:
'X' 'SAPLSKBH' '0800',
' ' 'BDC_CURSOR' 'GT_FIELD_LIST-SELTEXT(01)',
' ' 'BDC_OKCODE' '=WLSE',
' ' 'GT_FIELD_LIST-MARK(01)' 'X',
' ' 'BDC_SUBSCR' 'SAPLSKBH 0810SUB810'.
PERFORM bdcdaten USING:
'X' 'SAPLSKBH' '0800',
' ' 'BDC_CURSOR' 'GT_FIELD_LIST-SELTEXT(01)',
' ' 'BDC_OKCODE' '=CONT',
' ' 'BDC_SUBSCR' 'SAPLSKBH 0810SUB810'.
PERFORM bdcdaten USING:
'X' 'SAPMSSY0' '0120',
' ' 'BDC_CURSOR' '04/03',
' ' 'BDC_OKCODE' '=PS 63'.
PERFORM bdcdaten USING:
'X' 'SAPMSSY0' '0120',
' ' 'BDC_CURSOR' '02/76',
' ' 'BDC_OKCODE' '=&IC1'.
PERFORM bdcdaten USING:
'X' 'SAPMSSY0' '0120',
' ' 'BDC_CURSOR' '02/76',
' ' 'BDC_OKCODE' '=&ILT'.
PERFORM bdcdaten USING:
'X' 'SAPLSSEL' '1104',
' ' 'BDC_OKCODE' '=CRET',
' ' 'BDC_SUBSCR' 'SAPLSSEL 1105%_SUBSCREEN_FREESEL',
' ' 'BDC_CURSOR' '%%DYN001-LOW',
' ' '%%DYN001-LOW' gv_ssi_session_id.
PERFORM bdcdaten USING:
'X' 'SAPMSSY0' '0120',
' ' 'BDC_CURSOR' '04/15',
' ' 'BDC_OKCODE' '=&IC1'.
PERFORM bdcdaten USING:
'X' 'RSM04000_ALV_NEW' '2000',
' ' 'BDC_CURSOR' gv_modus_string,
' ' 'BDC_OKCODE' '=DEL'.
PERFORM bdcdaten USING:
'X' 'SAPMSSY0' '0120',
' ' 'BDC_CURSOR' '04/15',
' ' 'BDC_OKCODE' '=&F15'.
" call transaction SM04, sync (S), no screens
CALL TRANSACTION 'SM04'
USING bdcdata
MODE 'P'
UPDATE 'N'.
" proper logging. times, order number, and transaction added as well as
WRITE: / 'User session from user ', <fs_lock_details>-guname, 'with session number', gv_modus_index_char, 'was deleted.'.
WRITE: / 'Object Lock Time Length:', <fs_lock_details>-gttime.
ENDIF.
ENDLOOP.
ENDIF.
ENDWHILE.
*&---------------------------------------------------------------------*
*& Form BDCDATEN
*&---------------------------------------------------------------------*
FORM bdcdaten USING start fnam fval.
CLEAR bdcdata.
IF start = abap_true.
bdcdata-dynbegin = start.
bdcdata-program = fnam.
bdcdata-dynpro = fval.
ELSE.
bdcdata-fnam = fnam.
bdcdata-fval = fval.
ENDIF.
APPEND bdcdata.
ENDFORM. " BDCDATEN</code>
Below
are the output snapshots of the code:
First Warning message to user (done for 5 secs time as doing testing):
Second
Warning message after 10 minutes to user:
SM12
entry of lock:
After 10 minutes, the sessions which are locking the material master will be closed.
Hope you found this interesting. Try it internally in your team and remember its impact. Also take note of the forbidden system kernel calls.
Your comments make me smile. Please comment!
We have a very active Telegram (App) SAP Technical Group with more than 4930+ SAP Technical Practitioners from 6 Continents of the SAP World. Please join it using below link. Telegram SAP Technical Discuss Group. You need to install the Telegram App first on your mobile device. Once you have it on your mobile, you can join the group and also access it from the Web on your computer and laptop.
In my last article, I tried to show the limitation of Open SQL with respect to SQL Script. Today, I would like to point out the limitation in CDS and a simple tweak to overcome it. ABAP CDS Views have a problem in that you cannot join tables together with mismatched GUIDs, that is to say where one GUID is defined as RAW and the other is defined as CHAR.
BUT000 has PARTNER_GUID defined as RAW(16) and CRMD_PARTNER has PARTNER_NO defined as CHAR(32). When you try to join these two tables together in an ABAP CDS View, it will give an error and the view will not activate due to the join fields being mismatched. This is a restriction imposed by HANA SQL query language used for ABAP CDS Views.
We have taken GUID for our demonstration. The issue can be with any field with data type mismatch. And our tweak would be applicable for all.
The simple solution to bypass this issue is to create another TYPE of View to handle the join. The type of view I am referring to is an ABAP Dictionary View. Yes, normal ABAP Dictionary View can come to the rescue of the CDS View.
Unlike ABAP CDS views, Dictionary Views use Native SQL, which allows joining of mismatched data types for GUIDs.
Dictionary views are created within the SAP GUI editor (ABAP Perspective of HANA Studio or in normal GUI), require no source code entries, so no DDL source file is associated with them.
You cannot alias table names,
so each table can only be used once in the view.
No calculations or grouping is
possible.
As you can tell from the above limitations, these views need to be kept basic, and this is fine for the purpose we have, which is simply to create a linking view between two tables which can then be combined with other more complex ABAP CDS views.
Dictionary views are created in SAP HANA Development Studio under the ABAP perspective or directly in the SAP GUI using transaction code SE11.
If creating in SAP HANA Development Studio, follow the steps below:
Go to ABAP perspective
Open the package to which the view is to be added
Open the Dictionary >> Views folder.
Right click on the “Views” folder name
Select New Dictionary View
Or alternatively
Go to ABAP perspective
Right click on the package to add the view to
Click New and select “Other ABAP repository object”
Open the Dictionary folder
Select Dictionary view and Next
Next
6. The following screen will be displayed.
There are two types of view you can create; Dictionary View and External View. In our case we are only interested in Dictionary Views.
7. Enter the view name desired and select Dictionary View
8. The maintenance screen for transaction code SE11 is displayed, under which you can then select appropriate tabs to add tables, join criteria, the fields to output and the selection criteria to use.
Alternatively, the view can be created
directly within the SAP GUI using the following steps.
Enter transaction code SE11
Enter the view name and click
create
3. Enter a description, then define the tables and join conditions required.
4. Enter the basic fields required, for a link table these should simply be the CLIENT and the two GUIDs used in the join.
You can add further fields
if desired but bear in mind, this view is primarily being created as a linking
table, so keeping it simple is best.
5. Once all entries are made, save, activate and assign to a transport.
6. The activation log will give the following warnings, showing the type mismatch between the GUIDs.
We can ignore the warning. Our database view is ready.
7. Now we have a view that can be used for joining CRMD_PARTNER with BUT000 within and ABAP CDS view as follows:
<code>SELECT FROM CRMD_PARTNER AS PART
INNER JOIN ZVW_PARTNER_LINK AS LINK
ON LINK.CLIENT = PART.CLIENT
AND LINK.PARTNER_NO = PART.PARTNER_NO
INNER JOIN BUT000 AS B000
ON B000.CLIENT = LINK.CLIENT
AND B000.PARTNER_GUID = LINK.PARTNER_GUID
{
<desired fields>
}</code>
Look at the database view closely in the above snippet. The database view has become the connecting link between the two CDS Views and has effectively overcome the problem of joining CRMD_PARTNER direct with BUT000 with GUID mismatch (data element mismatch). And because it is used in a CDS View, this enables use of LEFT OUTER JOIN between the tables if needed.
This is simple trick which we have used in our real project. There can be better way which I am not aware of. If you know of a better solution, please put it in the comments section.Together we can learn better and provide optimized solutions to our clients.
In my next article, I would share the different reporting in SAP viz BI Reporting, Operational Reporting and reporting tools like KPI modeller, Crystal Reports, Lumina etc. So, please stay tuned.
We have a very active Telegram (App) SAP Technical Group with more than 5000+ SAP Technical Practitioners from 6 Continents of the SAP World. Please join it using below link. Telegram SAP Technical Discuss Group. You need to install the Telegram App first on your mobile device. Once you have it on your mobile, you can join the group and also access it from the Web on your computer and laptop.
In our SAP Technical Telegram Group, we discuss everything about SAP. UI5, OData, Fiori issue, New ABAP syntax, CDS approaches, Performance Tuning debates. You think of a topic, we discuss it in our group. No wonder, we are one of the largest SAP Technical group with zero spamming.
Yesterday, one member asked the below question. It was no doubt an interview question from one of the largest IT Company in the world.
Question: How to write the entries of first and second table to third table without SORT statement?
No SORT statement should be there in the solution.
The moment this question was floated, there was a debate among the members asking what is the business requirement? Why can we not use SORT?
Then the member who asked this question revealed it is one of the interview question from one the Big Multi National IT Companies.
Then the next debate started that we should not ask such questions in interview. But one of the member defended saying that SORTing Algorithms are asked by interviewers to eliminate at the very first stage of the selection process.
Ok, leaving behind the debate, whether such interview questions actually test the programming skill or not, everyone agreed to find a solution.
Every developer think differently. And there can be different ways to achieve the same solution. Solution to this question is a perfect example.
Our SAP Technical Group is just an example of how we can collaborate and learn every day. No question is too silly to ask. Ask, Answer, Propose, Depend, Share and Learn is the motto of our SAP Group.
This is the exact code snippet and output shared by Stephan. All of us can keep this for our quick reference for new ABAP syntax and usage.
Stephan provided yet another solution without using INSERT statement. Check the below snippet.
<code>TYPES:
gtyt_interger TYPE TABLE OF i WITH EMPTY KEY,
gtyt_interger_sorted TYPE SORTED TABLE OF i WITH NON-UNIQUE KEY table_line.
DATA(lt_table1) = VALUE gtyt_interger( ( 1 ) ( 9 ) ( 12 ) ( 16 ) ).
DATA(lt_table2) = VALUE gtyt_interger( ( 4 ) ( 10 ) ( 15 ) ( 20 ) ).
DATA(lt_table3) = CORRESPONDING gtyt_interger_sorted( BASE ( lt_table1 ) lt_table2 ).
* Starting with NW7.51 It is possible to prevent duplicate entries using DISCARDING DUPLICATES
* https://help.sap.com/doc/abapdocu_751_index_htm/7.51/en-US/index.htm
cl_demo_output=>write_data( value = lt_table1 name = 'First Table' ).
cl_demo_output=>write_data( value = lt_table2 name = 'Second Table' ).
cl_demo_output=>write_data( value = lt_table3 name = 'Third Table' ).
cl_demo_output=>display( ).</code>
He has used the new BASE key word. BASE is a functional operand position in which a database convertible to the target type can be specified. BASE is use to specify a start value base for the new structure of internal table.
You can read more about keyword BASE in SAP ABAPDOCU
Also, we can use DISCARDING DUPLICATES keywords for handling duplicate rows in component operator CORRESPONDING. Check more about DISCARDING DUPLICATES in ABAP Help.
You may follow Stephan Koester at LinkedIn. You may also check Stephan’s website Koester Consulting for more SAP blogs.
Would you like to share some weird Interview Questions you faced? Anything.. Technical or Non-Technical.
In one of our previous articles, we talked about the SORTing Algorithm logic which is asked in many Tier One MNCs. We showed few ways to achieve it. After going through the different SORTing method, our friend Stephan Koester from Koester-Consultingwas queries to validate the performance of the different SORTs.
So, he wrote one program using the same code snippets and tried to determine which SORTing Algorithm is the most optimized one. The finding is quite interesting.
For the experiment, Stephan created the tables with rows 2 to the power n where n starting from 1 till 13. In short, the table had row counts 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192.
Guinea Pig for the Experiment – i.e The Test Data
To test the performance better, he created two tables. One in ascending order from 2 till 2 to the power of 13. And the second table in descending order from 2 to the power of 13 till 2.
Techniques Used
If you check the code below, he used 3 methods to Sort and record the time.
<code>REPORT zstkoes_sort_performance.
DATA:
lv_time_first TYPE timestampl,
lv_time_second TYPE timestampl,
lv_time_third TYPE timestampl,
lv_time_fourth TYPE timestampl,
lv_time_fifth TYPE timestampl,
lv_time_sixth TYPE timestampl.
DATA:
BEGIN OF gs_time,
count TYPE i,
sorted_unique TYPE timestampl,
sorted_nonunique TYPE timestampl,
manual_sort_logic TYPE timestampl,
END OF gs_time,
gt_times LIKE TABLE OF gs_time.
TYPES:
tt_int TYPE TABLE OF i WITH EMPTY KEY,
BEGIN OF gtys_integer,
integer TYPE i,
END OF gtys_integer,
gtyt_interger TYPE TABLE OF gtys_integer WITH EMPTY KEY,
gtyt_interger_sorted_unique TYPE SORTED TABLE OF gtys_integer WITH UNIQUE KEY integer,
gtyt_interger_sorted_nonunique TYPE SORTED TABLE OF gtys_integer WITH NON-UNIQUE KEY integer.
DATA:it_three TYPE tt_int.
DO 13 TIMES.
DATA(lv_count) = 2 ** sy-index.
DATA(lt_table1) = VALUE gtyt_interger( FOR count2 = 1 THEN count2 + 1 UNTIL count2 > lv_count ( integer = count2 ) ).
DATA(lt_table2) = VALUE gtyt_interger( FOR count = lv_count THEN count - 1 UNTIL count = 0 ( integer = count ) ).
IF sy-index EQ 2.
cl_demo_output=>begin_section( title = |Sample table| ).
cl_demo_output=>write_text( text = |All tables follow the same rule:{
cl_abap_char_utilities=>newline }First table: 1 to count{
cl_abap_char_utilities=>newline }Second table: count to 1| ).
cl_demo_output=>write_data( value = lt_table1 name = |First Table| ).
cl_demo_output=>write_data( value = lt_table2 name = |Second Table| ).
cl_demo_output=>begin_section( title = 'Time needed' ).
ENDIF.
* 1. Sorted Non Unique
GET TIME STAMP FIELD lv_time_first.
DATA(lt_table4) = CORRESPONDING gtyt_interger_sorted_nonunique( BASE ( lt_table1 ) lt_table2 ).
GET TIME STAMP FIELD lv_time_second.
* 2. Sorted Unique
GET TIME STAMP FIELD lv_time_third.
* DISCARDING DUPLICATES can be used with NW7.51 and higher
DATA(lt_table3) = CORRESPONDING gtyt_interger_sorted_unique( lt_table1 DISCARDING DUPLICATES ).
lt_table3 = CORRESPONDING #( BASE ( lt_table3 ) lt_table2 DISCARDING DUPLICATES ).
GET TIME STAMP FIELD lv_time_fourth.
DATA(it_one) = VALUE tt_int( FOR count2 = 1 THEN count2 + 1 UNTIL count2 > lv_count ( count2 ) ).
DATA(it_two) = VALUE tt_int( FOR count = lv_count THEN count - 1 UNTIL count = 0 ( CONV #( count ) ) ).
CLEAR: it_three.
GET TIME STAMP FIELD lv_time_fifth.
APPEND LINES OF it_one TO it_three.
APPEND LINES OF it_two TO it_three.
* 3. Manual Sort Logic
DATA(lv_len) = lines( it_three ).
DATA(i) = 1.
WHILE i < lv_len.
DATA(lv_min) = it_three[ i ].
DATA(j) = i + 1.
WHILE j < lv_len + 1 .
IF it_three[ j ] < lv_min.
DATA(lv_temp) = lv_min.
lv_min = it_three[ j ].
it_three[ j ] = lv_temp.
ENDIF.
j = j + 1.
ENDWHILE.
it_three[ i ] = lv_min.
i = i + 1.
ENDWHILE.
GET TIME STAMP FIELD lv_time_sixth.
* Populating the table with the time for the 3 techniques
gt_times = VALUE #( BASE gt_times ( count = lv_count
sorted_unique = lv_time_fourth - lv_time_third
sorted_nonunique = lv_time_second - lv_time_first
manual_sort_logic = lv_time_sixth - lv_time_fifth ) ).
ENDDO.
cl_demo_output=>write_data( value = gt_times ).
cl_demo_output=>display( ).</code>
Result
Let us run and check the time taken for each Sort Algorithm
As expected, the Manual Sorting algorithm took exponential time compared to the standard syntax. The SORTED_UNIQUE fared better than SORTED_NONUNIQUE. Although the time difference is quite minuscule but SORTED UNIQUE technique was consistently better than SORTED NON UNIQUE and with more volume of data, the gap between UNIQUE and NON UNIQUE would be more visible and impactful.
If you need a reference code snippet to populate table with Non Unique and Unique data, check the below program provided by Stephan. He has created two internal tables and then inserted data of both the tables into one. One of the final table is SORTED UNIQUE and the other one is SORTED NON UNIQUE. This is a good example for everyone to learn modern Syntaxes and be more efficient at their work place.
<code>REPORT zstkoes_sort.
TYPES:
BEGIN OF gtys_integer,
integer TYPE i,
END OF gtys_integer,
gtyt_interger TYPE TABLE OF gtys_integer WITH EMPTY KEY,
gtyt_interger_sorted_unique TYPE SORTED TABLE OF gtys_integer WITH UNIQUE KEY integer,
gtyt_interger_sorted_nonunique TYPE SORTED TABLE OF gtys_integer WITH NON-UNIQUE KEY integer.
DATA(lt_table1) = VALUE gtyt_interger( ( integer = 9 ) ( integer = 1 ) ( integer = 9 ) ).
DATA(lt_table2) = VALUE gtyt_interger( ( integer = 20 ) ( integer = 9 ) ( integer = 4 ) ).
* CORRESPONDING can be used with NW7.40PL2 and higher
DATA(lt_table4) = CORRESPONDING gtyt_interger_sorted_nonunique( BASE ( lt_table1 ) lt_table2 ).
* DISCARDING DUPLICATES can be used with NW7.51 and higher
DATA(lt_table3) = CORRESPONDING gtyt_interger_sorted_unique( lt_table1 DISCARDING DUPLICATES ).
lt_table3 = CORRESPONDING #( BASE ( lt_table3 ) lt_table2 DISCARDING DUPLICATES ).
cl_demo_output=>write_data( value = lt_table1 name = |First Table| ).
cl_demo_output=>write_data( value = lt_table2 name = |Second Table| ).
cl_demo_output=>write_data( value = lt_table3 name = |Sorted Table with unique key| ).
cl_demo_output=>write_data( value = lt_table4 name = |Sorted Table with non-unique key| ).
cl_demo_output=>display( ).</code>
We would like to thank Stephan for all his time and effort in providing us the code snippet and the screenshots. You may connect with Stephan at LinkedIn or visit his company website KCO for any SAP Consulting Work.
Your Feedback Please..
5300+ SAP Technical Practitioners from 6 Continents of the SAP World. Install Telegram app and click this link to join – Telegram SAP Technical Discuss Group.
You know that we have one of the largest SAP Technical Telegram group with more than 5300 Consultants and growing each passing day. Ask-Answer, Propose-Defend, Share & Learn is our motto. Every day numerous questions are asked, few get answered and many are left open for further research and analysis. Every day we learn something new and we always have something to ponder over.
In one of these discussions, one member had a need to read data from Web. This article is a result of the sample code snippet I wrote to help him.
This is the selection screen of my test program to read data from a Web Page.
I have tried to use all the new syntax of ABAP and would like the freshers in new ABAP to look into below snippets.
1. String Concatenation Operation with COND #
<code> lv_url = |http{
COND #(
WHEN p_ssl EQ abap_true
THEN |s| ) }://www.koester-consulting.com/gEtDaTaFrOmWeB.php{
COND #(
WHEN p_id IS NOT INITIAL
THEN |?id={ p_id }| ) }|.</code>
Check, concatenation is done using pipe (|). Also check the COND # which can be included in the string literal operations too. When P_SSL has data then ‘s’ is concatenated after ‘http’.
If you don‘t know how the deep structure needs to look like, you can use the below code snippet to learn more about the received data and prepare your CHANGING structure TYPE accordingly.
<code>TRY.
DATA: lo_data TYPE REF TO data.
DATA(lo_json) = NEW /ui2/cl_json( ).
lo_json->deserialize_int(
EXPORTING
json = lv_data
CHANGING
data = lo_data ).
CATCH cx_sy_move_cast_error .
ENDTRY.</code>
Once you have the lo_data, check it in debug mode and then re-write your code to declare the TYPE you need.
Although serialize method was not needed in our sample report to read Web Data, but since we were talking about JSON to ABAP Internal table, I thought it would be useful if we also showed the other way around.
<code>TYPES:
BEGIN OF ts_deep_entity,
mat_doc TYPE matnr,
doc_year TYPE gjahr,
pstng_date TYPE sy-datum,
ernam TYPE sy-uname,
END OF ts_deep_entity.
TYPES: BEGIN OF ty_head ,
d TYPE ts_deep_entity,
e TYPE ts_deep_entity,
END OF ty_head.
*======================ABAP Data==================================
DATA(ls_abap_data) = VALUE ty_head( d-mat_doc = '4900000814' e-mat_doc = '4900000815'
d-doc_year = '2019' e-doc_year = '2020'
d-pstng_date = '20190701' e-pstng_date = '20200701'
d-ernam = 'kuldeep' e-ernam = 'joshi'
).
*======================ABAP to JSON==================================
DATA(lv_json_body) = /ui2/cl_json=>serialize(
data = ls_abap_data
pretty_name = /ui2/cl_json=>pretty_mode-camel_case ) .</code>
If you look closely, the JSON data has been formatted in CamelCase.
Similarly, we can format the JSON output in different ways:
Camel Case
Extended
Low Case
None
User
User Low Case
We will dedicate a separate article on ABAP to JSON and JSON to ABAP format. Today, we wanted to learn a simple way to read data from Web and display in SAP ABAP. With all the above knowledge, we have written the below program. Let us test it.
<code>REPORT zstkoes_get_data_from_web.
TYPES:
BEGIN OF gty_customer,
id TYPE string,
firstname TYPE string,
name TYPE string,
country TYPE string,
END OF gty_customer .
TYPES:
gtty_customer TYPE TABLE OF gty_customer .
CONSTANTS:
gc_proxy_host TYPE string VALUE 'PROXY_HOST',
gc_proxy_service TYPE string VALUE '8090'.
DATA:
gs_customer TYPE gty_customer,
gt_customer TYPE gtty_customer.
SELECTION-SCREEN:
BEGIN OF LINE,
COMMENT 1(15) c_id.
PARAMETERS:
p_id TYPE string VISIBLE LENGTH 2.
SELECTION-SCREEN END OF LINE.
SELECTION-SCREEN: COMMENT /1(50) c_info,
SKIP,
BEGIN OF LINE.
PARAMETERS: p_ssl TYPE xfeld.
SELECTION-SCREEN: COMMENT 3(50) c_ssl,
END OF LINE.
SELECTION-SCREEN: BEGIN OF LINE.
PARAMETERS: p_proxy TYPE xfeld.
SELECTION-SCREEN: COMMENT 3(50) c_proxy,
END OF LINE.
*----------------------------------------------------------------------*
* INITIALIZATION.
*----------------------------------------------------------------------*
INITIALIZATION.
c_id = 'ID'.
c_info = 'Leave empty to get a list of customer.'.
c_ssl = 'Use SSL (HTTPS)'.
c_proxy = 'Use Proxy'.
*----------------------------------------------------------------------*
* START-OF-SELECTION.
*----------------------------------------------------------------------*
START-OF-SELECTION.
TRY.
DATA: lv_url TYPE string.
lv_url = |http{
COND #(
WHEN p_ssl EQ abap_true
THEN |s| ) }://www.koester-consulting.com/gEtDaTaFrOmWeB.php{
COND #(
WHEN p_id IS NOT INITIAL
THEN |?id={ p_id }| ) }|.
cl_http_client=>create_by_url(
EXPORTING
url = lv_url
proxy_host = COND #(
WHEN p_proxy EQ abap_true
THEN gc_proxy_host )
proxy_service = COND #(
WHEN p_proxy EQ abap_true
THEN gc_proxy_service )
sap_username = sy-uname
IMPORTING
client = DATA(lo_client)
EXCEPTIONS
argument_not_found = 1
plugin_not_active = 2
internal_error = 3
OTHERS = 4 ).
IF lo_client IS BOUND.
lo_client->send(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3
OTHERS = 4 ).
IF sy-subrc NE 0.
lo_client->get_last_error(
IMPORTING
message = DATA(cv_error_msg) ).
MESSAGE cv_error_msg TYPE 'S' DISPLAY LIKE 'E'.
RETURN.
ENDIF.
lo_client->receive(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3
OTHERS = 4 ).
IF sy-subrc NE 0.
lo_client->get_last_error(
IMPORTING
message = cv_error_msg ).
MESSAGE cv_error_msg TYPE 'S' DISPLAY LIKE 'E'.
RETURN.
ENDIF.
lo_client->response->get_status(
IMPORTING
code = DATA(lv_status) ).
IF lv_status = 200.
DATA(lv_data) = lo_client->response->get_cdata( ).
IF lv_data IS INITIAL.
MESSAGE 'No Data found' TYPE 'S' DISPLAY LIKE 'E'.
RETURN.
ENDIF.
* Copy JSON-Data into internal table
IF p_id IS INITIAL.
/ui2/cl_json=>deserialize(
EXPORTING
json = lv_data
CHANGING
data = gt_customer ).
ELSE.
/ui2/cl_json=>deserialize(
EXPORTING
json = lv_data
CHANGING
data = gs_customer ).
ENDIF.
ENDIF.
ENDIF.
CATCH cx_root.
ENDTRY.
*----------------------------------------------------------------------*
* END-OF-SELECTION.
*----------------------------------------------------------------------*
END-OF-SELECTION.
* Output the data
TRY.
IF p_id IS NOT INITIAL.
APPEND gs_customer TO gt_customer.
ENDIF.
CALL METHOD cl_salv_table=>factory
IMPORTING
r_salv_table = DATA(gref_salv)
CHANGING
t_table = gt_customer.
gref_salv->display( ).
CATCH cx_salv_msg .
ENDTRY.</code>
You may connect with me at LinkedIn or visit my company website KCO for any SAP Consulting Work.
Join our Group – Telegram SAP Technical Discuss. Install Telegram App and click this link.Ask – Answer, Propose – Defend, Share and Learn in our Motto.
Do what you love, and you’ll never work another day in your life. I strongly believe in this maxim and always strive to find interesting stuff at my work place. Yes, I work in the blue screen of SAP whole day and I can proudly say it is not boring at all. Though many complain, but I love what I do. Did you know we can secretly chat using SAP ABAP? No, no.. you do not need to build another chatting tool. There is a standard function module ‘ TH_POPUP ‘ which you can use straight out of the box. Check my other article ABAP – Power to Kill to learn more about the SAP ABAP chat.
The point I am trying to make is, there are tons and tons of interesting activities we can perform in SAP. Do not say, you are too busy with your project work and you cannot explore. I would suggest, cut down 30 minutes from your Facebook browsing, 1 hour from going through the fake news and unnecessary messages in Whatsapp and 30 minutes from Instagram. You now have 2 hours of time to explore something new in SAP.
With the same philosophy to try something new and explore more in SAP, we are going to learn something new and different in ABAP programming today.
Scenario:
Today morning, one of the functional consultants came to me and said that our business team is becoming crazy day by day. (well…we ABAPer says the same things for functional consultants too).. shhhhh
After his statement, he revealed the requirement of business team..
Problem statement:
There should be a text box in the selection screen, in which user can put any arithmetic operations, conditional statements, string operations etc and after its execution the result should get printed. In short, they wanted to write some ABAP Commands (open SQL) in the text box and it should give the desired output.
Resolution Discussion:
After hearing this, we both laughed on the business requirement and their craziness. We thought it was a joke. But the business team members were serious. They wanted us to develop something and provide the solution.
He rested his case that day and left me pondering. I like crazy ideas. But not that crazy.
Now the ball was in my court. How to provide a solution which will be dynamic and scalable? They want to write any queries in the text box and expect an output.
After all R&D, I narrowed down the below solution which treats the selection text box as a mini ABAP editor. I felt like a genious inventor after this solution. Yes, I need to pat my back for this innovative solution.
Here user can put any ABAP query with any arithmetic operations, IF conditions, Select queries, Loops, Read statements and what not. And the beauty is, it gets executed and provides the result in output. But the syntax has to be correct when you put the query in the text box.
We need to create a mother program which will take input query from the User and displays its output.
As we will create a temporary program through GENERATE SUBROUTINE option, we need to write the name and declarations for that temporary program:
In above screenshot (red box), I have provided the name as ZSB_SUBPOOL to my temporary program and stored all declaration in SRC with is of table type string. You know what SB stands for in the 2nd and 3rd character of the program name?
Please note: You can give any name without Z or Y. Try giving the name as SUBPOOL and it will still work.
Next, create a subroutine named as ‘DYN1’ whose PERFORM will be called after GENERATE SUBROUTINE statement also passing the input parameter string P_FORM to SRC in that form.
Then we are appending parameter p_form to src.
At the end writing the RESULT on the screen with Write statement.
And then end the subroutine with ENDFORM.
<code>FORM DYN1 USING p_form TYPE string.
APPEND p_form TO src.
WRITE / RESULT.
ENDFORM.</code>
The final step is to call this temporary program via GENERATE SUBROUTINE POOL method.
This is a very powerful command. It does the syntax check and if everything is ok, it creates the temporary program in memory and executes it.
Once the Generate Subroutine Pool step is over and there is no error, it will call the program with logic written in PERFORM ‘DYN1’.
If you input the above text in the selection screen and debug the code, you will get to see the whole code of temporary program as below:
Isn’t it cool?
Code Snippet:
<code>REPORT zdynamic_query_sb.
TYPES:
source_line(256) TYPE c.
DATA:
src TYPE TABLE OF source_line,
prog_name(30) TYPE c,
msg(120) TYPE c,
line(10) TYPE c,
word(10) TYPE c,
off(3) TYPE c.
APPEND 'PROGRAM SUBPOOL.' TO src.
APPEND 'DATA RESULT TYPE string.' TO src.
APPEND 'DATA: a TYPE DECFLOAT34 VALUE ''5.0''.' TO src.
APPEND 'DATA: b TYPE DECFLOAT34 VALUE ''4.5''.' TO src.
PARAMETERS p_form TYPE string.
APPEND 'FORM DYN1 USING p_form TYPE string.' TO src.
APPEND p_form TO src.
APPEND ' WRITE / RESULT.' TO src.
APPEND 'ENDFORM.' TO src.
GENERATE SUBROUTINE POOL src
NAME prog_name MESSAGE msg LINE line WORD word OFFSET off.
IF sy-subrc <> 0.
WRITE: / 'Error during generation in line', line,
/ msg, / 'Word:', word, 'at offset', off.
ELSE.
PERFORM dyn1 IN PROGRAM (prog_name) USING p_form.
ENDIF.</code>
Time to Test
Execute the program and we will get below selection screen:
Insert any sample ABAP query to get its result.
Please Note: RESULT should always be the variable name for our case which you want to print.
After execution, we will get result of above query as below:
APPEND 'DATA: a TYPE DECFLOAT34 VALUE ''5.0''.' TO src.
APPEND 'DATA: b TYPE DECFLOAT34 VALUE ''4.5''.' TO src.
Also, you can access the global variable which you defined in the memory program. Here we defined A and B variables in the the program. We can provide them on selection screen text field and can access them in memory program as well.
The value of variable A is ‘5.0’ whereas the value of B is ‘4.5’ so the RESULT of above query will be ‘0.5’.
Some random Quiz questions:
What would be the output of this text input? RESULT = ‘HELLO SAPYARD’. in the text field?
Do you this this input will work? data(ls_1) = ‘Swapna’. data(ls_2) = ‘Dream’. Concatenate ls_1 ls_2 into RESULT SEPARATED BY SPACE.
What would be the output for this input text? data(ln_1) = 10. data(ln_2) = 5. RESULT = ln_1 * ln_2
Solution
Straight String Operation.Concatenate works fine.errrr!!!! You missed the period at the end. Syntax error.
In this program you can write any ABAP query like from arithmetic operations, conditional operations or even select query too. It Magic. Right?
So what are you waiting for? Copy the program and enjoy this mini ABAP query processor which you just created.
SAP is fun. We just need to find out how to have fun!!
Comments Please.
5300+ SAP Technical Practitioners from 6 Continents of the SAP World. Install Telegram app and click this link to join – Telegram SAP Technical Discuss Group.
You may master the new programming models using Core Data Services. You may build beautiful Fiori and UI5 applications which are sleek, fast and mobile friendly. You may also write SQLScript in your S/4HANA Project. But once an ABAP Programmer, always an ABAP Programmer. Clients will always have some weird requests where you will still need your basic ABAP Programming skills, debugging, analyzing and solutioning acumen.
Background: Recently we received a requirement to replace some hard-coded text written in Text Elements of Smart forms. In the SAP system there were around 350 – 400 custom smart forms to find and replace.
For e.g: I was searching for text “Secret” in which and all smartforms/textmodule its written.
Approaches/Attempts:
Approach 1:
Like many, I thought there should be a table to hold this data and fortunately (from scn community) found table STXFTXT. Field TDLINE holds smart form content and I tried searching.
Results:
voila!! I found what I was looking for. But …..
The field TDLINE is case sensitive. It means, you should know exact word with exact upper case and lower case combination. Otherwise you can’t get correct results in this table (I also read somewhere this table is not at all reliable).
Check, now I am searching for all lower case “secret”. It gives different output.
See here its not returning “ZDEMO” form name in a results
Plan B was to download smart form as XML and search for text. This way you can find the texts.
Solution Implementation:
There is a function module called “FB_CONVERT_FORM_TO_XML” which will convert form to XML by taking Form name and Form type as input. This function module accepts Text Module also as input, which is Bonus.
Now as every ABAPer does, select all forms, loop on each, get XML, convert XML to String and search your Love/Pain stories strings and display all found form names in ALV. Isn’t this the everyday story of all ABAP developers?
Advantage of this approach:
This search is Case Insensitive. It will search for all cases.
Attached program does it. Feel free to Plug and Play . Fancy word for copy paste.
The important functions used in this sample program are: FB_CONVERT_FORM_TO_XML, SSFH_XSTRINGUTF8_TO_STRING, FB_DISPLAY_FORM, DISPLAY_XML_DOCUMENT . Class used is CL_XML_DOCUMENT and method PARSE_STRING.
The entire code is also at the bottom of this article for your quick reference.
In Selection screen, we can give Form name and String for search.
Displays all Form name/Text Module wherever search string exists, with HotSpot clicking on this will open the Smart form/Text Module in XML format. Navigate to your text element.
Please note: Inactive forms will not be converted to XML, but Function module is smart enough to say its inactive by raising Exceptions.
Now the entire Smart Form properties are in your control. Do, whatever you want.
This is my first article at SAPYard. Your genuine feedback is welcome. Please help me improve and provide you better content.
In the next article I will show how to locate (find) exact node or element where search text lies in the Smartforms. Please stay tuned.
5370+ SAP Technical Practitioners from 6 Continents of the SAP World. Install Telegram app and click this link to join – Telegram SAP Technical Discuss Group.
<code>REPORT zsf_find_text.
*--------------------------------------------------------------------*
* Deferred statement CLASS is used to make the class class known,
* regardless of the location of the actual definition of the class
* in the program
CLASS lcl_event_handeler DEFINITION DEFERRED.
*--------------------------------------------------------------------*
DATA:lv_string TYPE tdsfname.
SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME.
SELECT-OPTIONS:s_form FOR lv_string.
PARAMETERS:p_forms TYPE string NO-DISPLAY,
find_dat TYPE string LOWER CASE OBLIGATORY.
SELECTION-SCREEN END OF BLOCK b1.
*--------------------------------------------------------------------*
INITIALIZATION.
s_form-low = 'Z*'.
APPEND s_form.
*--------------------------------------------------------------------*
DATA i_formname TYPE string.
DATA e_xml TYPE xstring.
*--------------------------------------------------------------------*
TYPES:BEGIN OF ty_sf,
formname TYPE tdsfname,
formtype TYPE tdsftype,
status TYPE char8,
e_xml TYPE string,
END OF ty_sf.
DATA:lt_forms TYPE STANDARD TABLE OF ty_sf.
*--------------------------------------------------------------------*
* Class Definition
CLASS lcl_event_handeler DEFINITION.
PUBLIC SECTION.
* Hotspot Handling
METHODS: handle_hotspot FOR EVENT link_click OF cl_salv_events_table
IMPORTING
row
column .
ENDCLASS.
*--------------------------------------------------------------------*
* Class Implementation
CLASS lcl_event_handeler IMPLEMENTATION.
* Handling Hotspot Click
METHOD handle_hotspot.
READ TABLE lt_forms INTO DATA(ls_forms) INDEX row.
IF sy-subrc = 0.
IF column = 'E_XML'.
DATA: gcl_xml TYPE REF TO cl_xml_document.
CREATE OBJECT gcl_xml.
*Parses XML String to DOM
CALL METHOD gcl_xml->parse_string
EXPORTING
stream = ls_forms-e_xml.
*Display XML
CALL METHOD gcl_xml->display.
ELSE.
CALL FUNCTION 'FB_DISPLAY_FORM'
EXPORTING
i_formname = ls_forms-formname
i_formtype = ls_forms-formtype
i_with_dialog = abap_false
EXCEPTIONS
no_name = 1
no_form = 2
no_access_permission = 3
illegal_language = 4
illegal_formtype = 5
OTHERS = 6.
IF sy-subrc <> 0.
* Implement suitable error handling here
ENDIF.
ENDIF.
ENDIF.
ENDMETHOD.
ENDCLASS.
*--------------------------------------------------------------------*
START-OF-SELECTION.
SELECT formname formtype FROM stxfadm INTO TABLE lt_forms
WHERE formname IN s_form .
*--------------------------------------------------------------------*
LOOP AT lt_forms ASSIGNING FIELD-SYMBOL(<ls_forms>).
DATA(lv_tabix) = sy-tabix.
p_forms = <ls_forms>-formname.
* Convert to XML
CALL FUNCTION 'FB_CONVERT_FORM_TO_XML'
EXPORTING
i_formname = p_forms
IMPORTING
e_xml = e_xml
EXCEPTIONS
no_active_source = 3.
IF sy-subrc <> 0.
<ls_forms>-status = 'Inactive'.
CONTINUE.
ENDIF.
DATA ostr_output_data TYPE xstring.
DATA codepage TYPE cpcodepage.
DATA cstr_output_data TYPE string.
* Convert XML to String
CALL FUNCTION 'SSFH_XSTRINGUTF8_TO_STRING'
EXPORTING
ostr_output_data = e_xml
IMPORTING
cstr_output_data = <ls_forms>-e_xml
EXCEPTIONS
conversion_error = 1
internal_error = 2
OTHERS = 3.
IF sy-subrc <> 0.
CONTINUE.
ENDIF.
* SEARCH Command
SEARCH <ls_forms>-e_xml FOR find_dat AND MARK.
IF sy-subrc NE 0.
DELETE lt_forms INDEX lv_tabix.
ENDIF.
ENDLOOP.
*--------------------------------------------------------------------*
DATA: o_alv TYPE REF TO cl_salv_table.
DATA: lx_msg TYPE REF TO cx_salv_msg.
DATA: lr_columns TYPE REF TO cl_salv_columns,
lr_column TYPE REF TO cl_salv_column_table,
lr_colums TYPE REF TO cl_salv_columns_table,
lr_display TYPE REF TO cl_salv_display_settings,
title TYPE lvc_title.
*--------------------------------------------------------------------*
TRY.
cl_salv_table=>factory(
IMPORTING
r_salv_table = o_alv
CHANGING
t_table = lt_forms ).
CATCH cx_salv_msg INTO lx_msg.
ENDTRY.
*--------------------------------------------------------------------*
lr_display = o_alv->get_display_settings( ).
title = 'Search Results for Text:'(001) && | " | && find_dat && | " |.
lr_display->set_list_header( title ).
lr_display->set_striped_pattern( if_salv_c_bool_sap=>true ).
lr_columns = o_alv->get_columns( ).
lr_columns->set_optimize( abap_true ).
DATA(gr_functions) = o_alv->get_functions( ).
gr_functions->set_all( abap_true ).
lr_colums = o_alv->get_columns( ).
lr_column ?= lr_colums->get_column( 'FORMNAME' ).
lr_column->set_cell_type( if_salv_c_cell_type=>hotspot ).
lr_column ?= lr_colums->get_column( 'STATUS' ).
lr_column->set_long_text( 'Status' ).
lr_column->set_short_text( 'Status' ).
lr_column->set_medium_text( 'Status' ).
lr_column ?= lr_colums->get_column( 'FORMTYPE' ).
lr_column->set_visible( if_salv_c_bool_sap=>false ).
lr_column ?= lr_colums->get_column( 'E_XML' ).
lr_column->set_cell_type( if_salv_c_cell_type=>hotspot ).
*--------------------------------------------------------------------*
DATA:co_report TYPE REF TO lcl_event_handeler.
DATA: lo_events TYPE REF TO cl_salv_events_table.
CREATE OBJECT co_report.
* All events
lo_events = o_alv->get_event( ).
*
* Event handler
SET HANDLER co_report->handle_hotspot FOR lo_events.
o_alv->display( ).
*--------------------------------------------------------------------*</code>
SAP ABAP Developers are familiar with LOOP — ENDLOOP syntax. FOR Loop is relatively new to ABAPers, though other programming language use it very commonly. Every developer has seen this syntax in some or other programming language: for (i=1; i<=3; i++).Why would ABAP stay behind?
But old habits die heard. The lazy developers who were comfortable with traditional ABAP programming find it challenging to use the new syntax. In this article today, we would discuss one requirement which we can easily achieve in old ABAP, but needed some help for the new syntax.
This post is again the result of the question we had in our SAP Technical Telegram Group. One of our very active and helpful members, Wesley Massini had a program where he was reading data from a file with fixed length. It was working fine and with no issue. But later on, the business decided to separate the data with underscore “_” and the file data is not fixed any more. They want to separate the data into columns at the delimiter underscore. This delimiter can be anything viz comma(,), tilds (~), pipe (|) etc.
Sounds an easy requirement right? But can you do using the new ABAP Syntax?
It is a file with heterogeneous data. The data in the first row and second row are different. For example, the first two fields (yellow and blue) in both the rows are Plant and Material. But the third field in red is Vendor is first row and while it is an indicator in second row. Similarly fourth field in green is a custom type in first row and some category in second row. The idea is, all the data rows in the file for type first should go to one internal table and all rows like that of second type should go into another internal table.
We used SWITCH statement in new syntax and separated the rows based on the length of the data in the file (which is fixed). Row type one has 26 characters while row type two has 18 characters. Also, we do not need to show the extension .txt.
As author and ABAPer Paul Hardy says, CDS is ABAP in Steriods. We say, not only CDS, the new ABAP itself looks like super active ABAP.
Check the above screenshot. We have In-line data declaration, usage of VALUE, FOR Loop in internal table, SWITCH and #. Do a F4 on each of these keywords and try to understand the concept.
For our case, we have know the TYPE, therefore they come after the VALUE operator. Also after SWITCH there is #. The table types are declared like below.
In order to display the output, we can write the below syntax.
Output
How to Handle Blank Rows?
We are not sure, how to avoid the blank rows in both the tables using the modern syntax. We used a DELETE statement explicitly after the data was populated in the internal tables.
If you know how to remove the blank lines in the FOR LOOP with SWITCH, please provide the solution in the comments section. We will update this article with your solution.
<code>* Delete Blank Lines
DELETE it_vm_data_tab WHERE werks IS INITIAL.
DELETE it_pm_data_tab WHERE werks IS INITIAL.</code>
Earlier our file was fixed length. With the business change, we started receiving file with a delimiter underscore “_”. So, we did not need to worry about the length of each fields. But again, we struggled to figure out the best way to SPLIT at “_” using new ABAP.
Solution 1
After some research and with our new knowledge, we achieved the functionality using STRLEN, SUBSTRING_BEFORE, SUBSTRING_AFTER , sub, occ string functions along with the VALUE and FOR Loop and COND keywords.
Note: The key word occ is for Occurrence. You might see a negative number for occ = -1. If occ is positive value, the occurrences are counted from the left while if occ is a negative number, it is counted from the right side.
Similarly check the keyword sub. It is for Substring. Sub looks for the characters in the string.
<code>DATA result TYPE string.
result = substring( val = 'ABCDEFGH' off = 2 len = 2 ).
result = substring_from( val = 'ABCDEFGH' sub = 'CD' ).
result = substring_after( val = 'ABCDEFGH' sub = 'CD' ).
result = substring_before( val = 'ABCDEFGH' sub = 'CD' ).
result = substring_to( val = 'ABCDEFGH' sub = 'CD' ).</code>
The above snippet is from SAP Help to show the usage of SUB and different SUBSTRING functions. The output for each result is “CD”, “CDEFGH”, “EFGH”, “AB”, and “ABCD” in the same order.
The above code also gave a blank line and we were forced to use the DELETE statement explicitly. Also, we had COND in the syntax which could be avoided. We achieved the business requirement, but we were still wondering if there is a better way to achieve it. Without the DELETE statement and COND. And guess what? Stephan gave a flawless solution yet again!!
Stephan recommended to put an identifier in the beginning of the data row for the file. For our case, it is VM and PM. Also, with his new solution logic there is no need to use COND.
Check the solution above. He has used WHERE clause in the FOR Loop. There is no need for WHEN and THEN. Also, the STRLEN check of each row is prevented along with COND #.
Code Snippet
<code>* Types Declaration
TYPES:
BEGIN OF ty_vm_file,
werks TYPE werks_d,
matnr TYPE matnr,
lifnr TYPE lifnr,
ztype TYPE char1,
END OF ty_vm_file,
BEGIN OF ty_pm_file,
werks TYPE werks_d,
matnr TYPE matnr,
htype TYPE char1,
zcagn TYPE char2,
END OF ty_pm_file,
BEGIN OF ty_filename,
filename TYPE text1024,
END OF ty_filename,
* Table Type declaration
tt_vm_tab TYPE TABLE OF ty_vm_file WITH EMPTY KEY,
tt_pm_tab TYPE TABLE OF ty_pm_file WITH EMPTY KEY,
tt_filename_tab TYPE TABLE OF ty_filename WITH EMPTY KEY.
* My recommendation is to put an identifier at the beginning of the filename like in this sample.
* With this logic there is no need for usage of COND like in the first sample.
DATA(it_fillename_data_tab) = VALUE tt_filename_tab( ( filename = 'VM_CA02_0074203_0000102207_H.txt' )
( filename = 'PM_CA02_0074203_C_HA.txt' ) ).
DATA(it_vm_data_tab) = VALUE tt_vm_tab( FOR ls_filename IN it_fillename_data_tab
WHERE ( filename(3) EQ 'VM_' )
( werks = substring_after( val = substring_before( val = ls_filename-filename sub = '_' occ = 2 ) sub = '_' occ = 1 )
matnr = substring_after( val = substring_before( val = ls_filename-filename sub = '_' occ = 3 ) sub = '_' occ = 2 )
lifnr = substring_after( val = substring_before( val = ls_filename-filename sub = '_' occ = 4 ) sub = '_' occ = 3 )
ztype = substring_after( val = substring_before( val = ls_filename-filename sub = '.' occ = -1 ) sub = '_' occ = -1 )
) ).
DATA(it_pm_data_tab) = VALUE tt_pm_tab( FOR ls_filename IN it_fillename_data_tab
WHERE ( filename(3) EQ 'PM_' )
( werks = substring_after( val = substring_before( val = ls_filename-filename sub = '_' occ = 2 ) sub = '_' occ = 1 )
matnr = substring_after( val = substring_before( val = ls_filename-filename sub = '_' occ = 3 ) sub = '_' occ = 2 )
htype = substring_after( val = substring_before( val = ls_filename-filename sub = '_' occ = 4 ) sub = '_' occ = 3 )
zcagn = substring_after( val = substring_before( val = ls_filename-filename sub = '.' occ = -1 ) sub = '_' occ = -1 )
) ).
* Add both table data for display
cl_demo_output=>write_data( it_vm_data_tab ).
cl_demo_output=>write_data( it_pm_data_tab ).
** Show the output
cl_demo_output=>display( ).</code>
If you have a better solution, please do share. Your feedback and comments are welcome. Please let them coming.
You may connect with with Stephan at LinkedIn or visit his company website KCO for any SAP Consulting Work.
5450+ SAP Technical Practitioners from 6 Continents of the SAP World. Install Telegram app and click this link to join – Telegram SAP Technical Discuss Group.
Normally the ALV output is displayed in a new screen. Not on the same screen where you have the selection parameters and select options. But, sometimes client want to see what they input in the screen and what they get output in the ALV in the same screen. It just helps them perform better, report better and analyse better. This requirement and scenario is not new. We have solution for it from back then in 2008.
If the solution is already there, then why again a new article? SAPYard has to be different. The earlier solution provided uses SALV class “CL_SALV_TABLE“. We will use “CL_GUI_ALV_GRID” class due to its enhanced features.
Expectations
Selection Screen and ALV on the Same Screen.
Add a Button on the ALV Toolbar.
Handle User Command.
For more information on the usage and functioning of the class CL_GUI_ALV_GRID, you can refer to the demo programs provided by SAP with the name like “BCALV_GRID* “.
Sample programs provided by SAP on the usage of CL_GUI_ALV_GRID.
Output
Simple Selection Screen with one Select-Options S_CARRID of type SFLIGHT-CARRID
Below is the Final Output which the Client is Expecting.
Docking Container needs to be created in the INITIALIZATION event.
ALV needs to be created on that Docking Container.
Export the SELECTed Output Table Data to ABAP Memory (this part we do not like. But till we find a better solution, let’s use it).
Import data from ABAP Memory and Generate the Output.
Global Data Declarations
<code>CLASS lcl_report DEFINITION DEFERRED.
DATA: lo_report TYPE REF TO lcl_report. "Reference object of the local class
DATA: alv_container TYPE REF TO cl_gui_docking_container, "ALV Container
alv_grid TYPE REF TO cl_gui_alv_grid, "ALV Grid
layout TYPE lvc_s_layo. "Layout Options
DATA gv_carrid TYPE sflight-carrid. "Variable for Select-options declaration.</code>
Selection Screen Declarations
<code>SELECTION-SCREEN: BEGIN OF BLOCK block_1 WITH FRAME TITLE text-001.
SELECT-OPTIONS: s_carrid FOR gv_carrid.
SELECTION-SCREEN: END OF BLOCK block_1.</code>
Local Class Definition
<code>CLASS lcl_report DEFINITION .
PUBLIC SECTION.
DATA: gt_data TYPE STANDARD TABLE OF sflight.
METHODS :
get_data,
generate_output,
toolbar FOR EVENT toolbar OF cl_gui_alv_grid
IMPORTING e_object, "To add some buttons on the ALV toolbar
user_command FOR EVENT user_command OF cl_gui_alv_grid
IMPORTING e_ucomm.
ENDCLASS.</code>
Local Class Implementation
Method: GET_DATA
<code>CLASS lcl_report IMPLEMENTATION.
"Method Get Data
METHOD get_data.
* data selection
SELECT * FROM sflight
INTO TABLE me->gt_data
WHERE carrid IN s_carrid.
IF sy-dbcnt IS INITIAL.
MESSAGE s398(00) WITH 'No data selected'.
ENDIF.
*
* Export to memory
EXPORT data = me->gt_data TO MEMORY ID sy-cprog.
ENDMETHOD.</code>
Method: GENERATE_OUTPUT
<code>"Method generate_output
METHOD generate_output.
* Local data
DATA: variant TYPE disvariant.
DATA: repid TYPE sy-repid.
* Import output table from the memory and free afterwards
IMPORT data = me->gt_data FROM MEMORY ID sy-cprog.
FREE MEMORY ID sy-cprog.
*
* Only if there is some data
CHECK me->gt_data IS NOT INITIAL.
repid = sy-repid.
variant-report = sy-repid.
variant-username = sy-uname.
layout-zebra = 'X'.
CHECK alv_container IS INITIAL.
CREATE OBJECT alv_container
EXPORTING
repid = repid
dynnr = sy-dynnr
side = alv_container->dock_at_bottom
extension = 200.
CREATE OBJECT alv_grid
EXPORTING
i_parent = alv_container.
* ALV Specific. Data selection.
SET HANDLER : lo_report->toolbar FOR alv_grid.
SET HANDLER : lo_report->user_command FOR alv_grid.
CALL METHOD alv_grid->set_table_for_first_display
EXPORTING
is_layout = layout
is_variant = variant
i_save = 'U'
i_structure_name = 'SFLIGHT'
CHANGING
it_outtab = me->gt_data.
ENDMETHOD.</code>
In this method, we will create one Push-Button(just as an example-no functionality) for example to save a record, in case we had created this ALV as editable.
<code>"Method Tool-bar
METHOD toolbar.
DATA: lv_toolbar TYPE stb_button.
* Push Button "For Example SAVE
CLEAR lv_toolbar.
MOVE 'FC_SAVE' TO lv_toolbar-function. "Function Code
lv_toolbar-icon = icon_system_save. "Save Icon
MOVE 'Save'(100) TO lv_toolbar-text. "Push button Text
MOVE 'Save'(100) TO lv_toolbar-quickinfo. "Push button Quick-Info
MOVE space TO lv_toolbar-disabled. "Push button enabled
APPEND lv_toolbar TO e_object->mt_toolbar.
ENDMETHOD. </code>
Method: USER_COMMAND
This method will be used to handle all the commands given by the user, for example if user press on the Save button we created above etc. I have kept this section as blank, you can add the logic as required.
In case you set the break-point at above method and press the Save button on the ALV, the execution will stop. Please check the below screenshot:
User Command captured when clicked on “Save” button.
Finally, the Events of the program execution
<code>INITIALIZATION.
* object for the report
CREATE OBJECT lo_report.
* generate output
lo_report->generate_output( ).
START-OF-SELECTION.
* Get data
lo_report->get_data( ).</code>
Conclusion
In this blog post we learnt the basics of creating an ALV on the selection screen with the usage of CL_GUI_ALV_GRID class.
If you just copy the above code snippets and paste it to your SE38 editor and activate it. Just like one of our lazy editors at SAPYard did. You will find the output like below. Yes!! It is a working code. And you do not need to design any screen elements.
Why did we need to use ABAP Memory when all data is there in the program and we are displaying them in the same program?
When the program control reaches INITIALIZATION event, all memory is REFRESHED and global data is made FREE. After all everything should be initialized in the INITIALIZATION event. That is the job of INITIALIZATION event. Therefore, we have to have the ALV data somewhere to pull it back when we are in INITIALIZATION event. Now you know why we were forced to take help of the ABAP Memory.
COMMENTS & QUESTIONS PLEASE!!
Do join 6060+ SAP Technical Consultants in this Telegram SAP Technical Discuss Group.Ask, Answer and Learn is our Motto. You need to install Telegram App first in your mobile or desktop and then click the joining link.