GitHub Guide Site

Syntax.md

Basic Syntax

Declaring Input Variables

" Prompts FOR an obligatory input VALUE that it allocates IN memory WITH ID wrk
PARAMETERS werks TYPE werks_d MEMORY ID wrk OBLIGATORY,
in_data TYPE SY-datum.

" Prompts FOR input RANGES
SELECT-OPTIONS: so_kunnr FOR zstock_b-kunnr,
  so_matnr FOR zstock_b-matnr.
  
  INITIALIZATION.
  in_data = '20190709'. "Sets 09-07-2019 as the suggested value"Sets 09-07-2019 as the suggested value
  

Search Help
A search help is used to find possible values for a specific input field

************************************************************************
* CUSTOM TYPES
************************************************************************
BEGIN OF ty_variant,
variant TYPE variant,
END OF ty_variant.

************************************************************************
* GLOBAL DATA
************************************************************************
gt_variant TYPE TABLE OF ty_variant.

************************************************************************
* SELECTION SCREEN
************************************************************************
PARAMETERS: p_varnt TYPE variant.

************************************************************************
* INITIALIZATION
************************************************************************
INITIALIZATION.

SELECT variant
  FROM varid
  INTO TABLE gt_variant
  WHERE report EQ SY-repid.
  
  ************************************************************************
  * AT SELECTION SCREEN
  ************************************************************************
  AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_varnt.
  DATA: ls_variant TYPE ty_variant,
  lt_ret TYPE TABLE OF ddshretval,
  ls_ret TYPE ddshretval.
  
  CALL FUNCTION 'F4IF_INT_TABLE_VALUE_REQUEST'
  EXPORTING
  retfield    = 'VARIANT'
  dynpprog    = SY-repid
  dynpnr      = SY-dynnr
  value_org   = 'S'
  TABLES
  value_tab   = gt_variant
  return_tab  = lt_ret.
  
  READ TABLE lt_ret INTO ls_ret INDEX 1.
  p_varnt = ls_ret-fieldval.
  

Declaring Internal Variables

" Declare different TYPES OF variables following the naming convention:
" - l = local,  g = global, i = import, e = export
" - v = variable, s = STRUCTURE, t = table, f = FIELD, r = RANGE, ref = reference, o = object
DATA: lv_werks TYPE werks_d,
ls_ekko  TYPE ekko,
lt_ekpo  TYPE TABLE OF ekpo.

" Declare a STRUCTURE / table type following the naming convention:
" - ty = type, tt = type table
DATA: BEGIN OF ty_example,
field1 type type1,
field2 type type2,
END OF ty_example,
tt_example TYPE TABLE OF ty_example. " tt_example è un tipo tabella
DATA: lt_ex TYPE tt_example. " lt_ex è quindi una tabella

Grouping Code

iL DEFINE is used to reuse the same lines of code multiple times. DEFINEs cannot be debugged and cannot be used outside the program.

DATA: RESULT TYPE I,
N1 TYPE I VALUE 5,
N2 TYPE I VALUE 6.

DEFINE OPERATION.
RESULT = &1 &2 &3.
OUTPUT &1 &2 &3 RESULT.
END-OF-DEFINITION.

DEFINE OUTPUT.
WRITE: / 'The result of &1 &2 &3 is', &4.
END-OF-DEFINITION.

OPERATION 4 + 3.
OPERATION 2 ** 7.
OPERATION N2 - N1.

You can use FORM / PERFORM to use the same code in multiple places. They can also be called from external programs but cannot be created anywhere (e.g., class methods).

PERFORM OPERATION CHANGING n1 n2 tot.

FORM OPERATION CHANGING  lv_n1 TYPE i
  lv_n2 TYPE i
  lv_tot TYPE i.
  
  lv_tot = lv_n1+ lv_n2.
  
ENDFORM.

Strings
When searching for a specific value in a string containing a defined separator, you can quickly get the substring using one of these functions.

DATA(result) = segment( val = 'VAL1-VAL2-VAL3' index = 3 sep = '-' ).
DATA(result) = match( val  = 'VAL1-VAL2-VAL3'  regex = '[^-]*$' ).
DATA(result) = SubString( Val = 'VAL1-VAL2-VAL3' OFF = Find( Val = 'VAL1-VAL2-VAL3' Regex = '-[^-]+$' ) + 1 ).

To concatenate variables into a string, use the following syntax:

CONCATENATE lv_var1 lv_var2 INTO lv_string.

"740"740
lv_string = |{ lv_var1 }{ lv_var2 }|.

Using the 740 syntax, you can also leverage conversion functions.

" Alignment
WRITE / |{ 'Left'     WIDTH = 20 ALIGN = LEFT     PAD = '0' }|.
WRITE / |{ 'Center'   WIDTH = 20 ALIGN = CENTER   PAD = '0' }|.
WRITE / |{ 'Right'    WIDTH = 20 ALIGN = RIGHT    PAD = '0' }|.

" Character style
WRITE / |{ ‘Text’ CASE = (cl_abap_format=>c_raw) }|.
WRITE / |{ ‘Text’ CASE = (cl_abap_format=>c_upper) }|.
WRITE / |{ ‘Text’ CASE = (cl_abap_format=>c_lower) }|.

" Numeric conversion
DATA(lv_vbeln) = ‘0000012345’.
WRITE / |{ lv_vbeln  ALPHA = OUT }|.     “or use ALPHA = IN TO go IN

" Date conversion
WRITE / |{ pa_date DATE = ISO }|.           “Date Format YYYY-MM-DD
WRITE / |{ pa_date DATE = User }|.          “As per user settings
WRITE / |{ pa_date DATE = Environment }|.   “Formatting setting OF

" Quantity conversion
DATA(lv_qty) = 13.000.

WRITE lv_menge TO lv_menge_char LEFT-JUSTIFIED UNIT lv_meinh. " IF lv_meinh == udm WITHOUT decimals (e.g., pieces) -> lv_menge_char = 13

Query

Select single: extracts only one value ```abap SELECT SINGLE field1, field2 FROM table INTO variable WHERE condition.

SELECT SINGLE field1, field2 FROM table INTO @DATA(variable) WHERE condition.


**Info**    
*SELECT SINGLE* extracts the first record from the database that matches the conditions defined in the query, while *SELECT UP TO 1 ROWS* extracts all records that match the WHERE condition, but only the first will be displayed in the final result. However, the second case, before returning the result, performs aggregation and grouping operations to return the result that best suits the search conditions; it is therefore recommended for extractions without a complete primary key.

**Select count:** counts the rows of a select
```abap
SELECT COUNT(*) 
  FROM table
  WHERE condition.
 " sy-dbcnt contains the counter

SELECT COUNT( DISTINT field1 )
  FROM table INTO variable
  WHERE   condition.

Select into table: extracts multiple values

SELECT field1 field2
  FROM table INTO TABLE tab
  WHERE condition.
  
  SELECT field1 field2
    FROM table INTO CORRESPONDING FIELDS OF TABLE tab
    WHERE condition.
    
    SELECT field1 field2
      FROM table INTO TABLE tab
      FOR ALL ENTRIES IN tab2
      WHERE condition.
      
      SELECT field1, field2
        FROM table INTO TABLE @DATA(tab)
        WHERE condition.
        
        *  IN the WHERE condition, '<> @( VALUE #( ) )' can also be used TO indicate an empty VALUE.
        

For All Entries
For all entries used to extract data for each record of an internal table.
DO NOT PUT SELECTS IN LOOPS!
The limitation of SELECT with FOR ALL ENTRIES is that many grouping functions and some keywords of the new syntax are not usable. If you are using an updated system (SAP BASIS 752), it is possible to avoid FOR ALL ENTRIES (for more details, see external links):

SELECT field1 field2
  FROM table INTO CORRESPONDING FIELDS OF TABLE tab
  WHERE condition.
  
  " FOR ALL entries
  SELECT field1 field2
    FROM table INTO TABLE tab2
    FOR ALL ENTRIES IN tab
    WHERE field1 = tab-field1.
    
    " NO FOR ALL entries
    SELECT field1 field2
      FROM table INTO TABLE tab2
      WHERE field1 IN ( SELECT field1 FROM @tab ).
      
      

From SELECT to RANGE: extract multiple values

SELECT sign opt AS option low high FROM tvarvc
  INTO CORRESPONDING FIELDS OF TABLE itab_tipo_range
  WHERE name EQ nomezfunz.
  
  *  IN the WHERE condition, '<> @( VALUE #( ) )' can also be used TO indicate an empty VALUE. @space IS preferable.
  

ATTENTION:

For queries, there are two syntaxes: the old syntax which does not require a comma between the fields to be selected, and the new syntax which does. In the new syntax, an "@" symbol must be placed before report variables (i.e., not dictionary fields or table names), and inline variable or table declaration (@DATA(name)) is allowed.

Difference between INTO TABLE and INTO CORRESPONDING FIELD OF TABLE. The first extracts data and places it in the destination table in the order of extraction (so the table must have the correct structure and names), while in the second case, the query inserts the extracted values from the database into the column that corresponds to the extracted data, if the column is found.

Tabelle interne - Lettura valori

Internal Tables - Reading Values

ATTENTION - sy-index: index contained in DO and WHILE loops - sy-tabix: index in a table loop

" Declare a STRUCTURE type
TYPES: BEGIN OF ty_example,
matnr TYPE matnr,
mtart TYPE mtart,
END OF ty_example.

DATA : it_example TYPE TABLE OF ty_example, " Instantiate a table OF type ty_example
wa_example TYPE ty_example.          " Instantiate a STRUCTURE OF type ty_example

You can use 3 different methods to extract data from tables:

  • Using the structure as a table row
LOOP AT it_example INTO wa_example.
  WRITE:/ wa_example-matnr .
  WRITE:/ wa_example-mtart .
ENDLOOP.
  • Using field-symbols as pointers.
LOOP AT it_example assigning FIELD-symbol(<fs_example>).
  WRITE:/ <fs_example>-matnr .
  WRITE:/ <fs_example>-mtart .
ENDLOOP.
  • Using field-symbols as pointers when a table is dynamic (i.e., there is no fixed structure but it is generated by functions).
FIELD-SYMBOLS: <fs_matnr> TYPE ANY,
<fs_mtart> TYPE ANY. " o DATA

LOOP AT it_example assigning FIELD-symbol(<fs_example>).
  ASSIGN COMPONENT 'MATNR' OF STRUCTURE <fs_example> TO <fs_matnr>.
  ASSIGN COMPONENT 'MTART' OF STRUCTURE <fs_example> TO <fs_mtart>.
  WRITE:/ <fs_matnr> .
  WRITE:/ <fs_mtart> .
ENDLOOP.

There is an alternative: references (preferable for read table).

LOOP AT it_example reference INTO DATA(lr_example).
  WRITE:/ lr_example->matnr .
  WRITE:/ lr_example->mtart .
ENDLOOP.

READ table it_example reference INTO DATA(lr_example) WITH key matnr = '22000000'.
IF lr_example IS BOUND.
  ...
ENDIF.

If you want to read a row or the value of a row without modifying its value, it's recommended to use inline controls:

DATA(ls_example) = VALUE #( it_example[ matnr = '22000000' ] OPTIONAL ).
" IF the row does NOT exist, an empty STRUCTURE IS created.

DATA(lv_mtart) = VALUE #( it_example[ matnr = '22000000' ]-mtart OPTIONAL ).
DATA(lv_mtart) = VALUE #( it_example[ matnr = '22000000' ]-mtart DEFAULT def ).
" IF the row does NOT exist, the variable IS created empty. IN the second CASE, a DEFAULT VALUE IS assigned IF the sought VALUE IS empty.

Internal Tables - Writing Values

Move-corresponding

MOVE-CORRESPONDING is useful for moving rows from one table to a destination table, with row type conversion based on the data being moved. When adding rows to a table that already contains records, MOVE-CORRESPONDING would overwrite the existing records. You can bypass this problem with the syntax:

gt_outtab = CORRESPONDING #( BASE ( gt_outtab ) lt_tmp_out ).

Inserting New Values
There are various ways to insert a row of values into an internal table, and they vary depending on the requirement.

DATA: lt_mara TYPE TABLE OF mara.

" INSERT a row AT the END
APPEND VALUE #( matnr = '123' ) TO lt_mara.

" INSERT a row AT a specific position
INSERT VALUE #( matnr = '123') INTO TABLE lt_mara INDEX n.

" INSERT the first row
lt_mara = ( ( matnr = '123' ) ).

Internal Tables - Sorting and Value Management

Table Grouping
If I need to perform a GROUP BY on a table based on various columns, I can use the FOR loop to generate a table containing the grouping.

TYPES: BEGIN OF ty_imp,
sel4     TYPE p_pernr,
codconto TYPE saknr,
mese     TYPE string,
gjahr    TYPE gjahr,
END OF ty_imp,
tty_imp TYPE TABLE OF ty_imp WITH EMPTY KEY.

DATA(lt_count_imp) = VALUE tty_imp(
FOR GROUPS ls_group OF ls_file IN gt_int_file GROUP BY ( sel4 = ls_file-sel4 codconto = ls_file-codconto mese = ls_file-mese gjahr = ls_file-gjahr )
(
ls_group
)
).
```

**LOOP WITH GROUP BY**
Questo ciclo permette di raggruppare IN LOOP un determinato gruppo di valori secondo una chiave

This LOOP allows you TO group a specific SET OF values IN a LOOP according TO a key.

- GROUP SIZE - Number OF rows IN the internal table FOR the particular group key.

- GROUP INDEX - Index OF the group FROM Main LOOP iteration.

- REFERENCE INTO DATA - Assigning the group DATA INTO a separate internal table TO access inside the LOOP GROUP.

- ASCENDING / DESCENDING - SORT the groups BY the group key IN ASCENDING OR DESCENDING order before the group LOOP IS executed.

- WITHOUT MEMBERS - Constructs groups but there IS NO access TO the rows OF the groups IN the group LOOP. IF the addition WITHOUT MEMBERS IS specified.
The addition WITHOUT MEMBERS IS used TO improve performance IN ALL cases WHERE the content OF the groups IS NOT required.

- LOOP AT GROUP. Group LOOP TO access each row OF the group.
Here IS my sample code TO understand LOOP AT WITH GROUP BY AND some group features.

```abap
TYPES ty_t_mard TYPE SORTED TABLE OF mard WITH UNIQUE KEY matnr werks lgort.
DATA lt_mard TYPE ty_t_mard.


SELECT * FROM mard INTO TABLE lt_mard.
  DATA : lv_stock TYPE mard-labst.
  
  LOOP AT lt_mard INTO DATA(wa_mard) GROUP BY ( matnr = wa_mard-matnr
    size = GROUP SIZE )
    REFERENCE INTO DATA(group1).
    CLEAR : lv_stock.
    WRITE : / group1->matnr , group1->size.
    
    LOOP AT GROUP group1 ASSIGNING FIELD-SYMBOL(<mard>).
      ADD <mard>-labst TO lv_stock.
    ENDLOOP.
    WRITE :/ 'Total:' ,  lv_stock.
  ENDLOOP.
  

Counting rows according to defined conditions ```abap DATA(lv_lines) = REDUCE i( INIT x = 0 FOR wa IN lt_table WHERE ( matnr IS NOT INITIAL ) NEXT x = x + 1 ).


 **Getting row number according to defined conditions**
  ```abap
  DATA(lv_index) = line_index( lt_ihpavb[ parvw = 'AG' ] ).
  ```

  **Getting number of table rows**
  ```abap
  DATA(lv_index) = lines( lt_ihpavb ).
  ```
To obtain the structure of an internal table (column name and type), you can use classes that help extract this information.

```abap

 FORM get_dynamic_field USING istructure TYPE data.
  "mapping partner
  DATA : lo_ref_descr TYPE REF TO cl_abap_structdescr,
         lt_detail    TYPE abap_compdescr_tab,
         ls_detail    LIKE LINE OF lt_detail,
         ls_c_compcd  TYPE idwtcompcd.

  lo_ref_descr ?= cl_abap_typedescr=>describe_by_data( istructure ). "Call static method on a structure
  lt_detail[] = lo_ref_descr->components.

  LOOP AT lt_detail INTO ls_detail.
    ASSIGN COMPONENT ls_detail-name OF STRUCTURE istructure TO FIELD-SYMBOL(<ls_comp>).
    "Here I have the columns of the table
  ENDLOOP.

ENDFORM.

Events

It happens that an event needs to be raised statically from code (from a BADI or another method). One must therefore resort to a standard class to RAISE it.

DATA : lv_objtype          TYPE sibftypeid VALUE 'ZCL_UD_UPDATE_IDOC',
lv_event            TYPE sibfevent VALUE 'CREATE_IDOC',
lv_param_name       TYPE swfdname,
wf_objkey           TYPE sweinstcou-objkey,
lr_event_parameters TYPE REF TO if_swf_ifs_parameter_container.

" CALL the CLASS METHOD TO GET an event container
CALL METHOD cl_swf_evt_event=>get_event_container
EXPORTING
im_objcateg  = cl_swf_evt_event=>mc_objcateg_cl
im_objtype   = lv_objtype
im_event     = lv_event
RECEIVING
re_reference = lr_event_parameters. " PARAMETERS OF the event TO be called

" SET the PARAMETERS OF the event I want TO CALL
TRY.
  lr_event_parameters->SET(
  EXPORTING
  name                          = 'I_INSPL'
  VALUE                         = new_insplot
  ).
  
  CATCH cx_swf_cnt_cont_access_denied.    "
    CATCH cx_swf_cnt_elem_access_denied.    "
      CATCH cx_swf_cnt_elem_not_found.    "
        CATCH cx_swf_cnt_elem_type_conflict.    "
          CATCH cx_swf_cnt_unit_type_conflict.    "
            CATCH cx_swf_cnt_elem_def_invalid.    "
              CATCH cx_swf_cnt_container.    "
              ENDTRY.
              
              " RAISE the event
              TRY.
                CALL METHOD cl_swf_evt_event=>raise_in_update_task
                EXPORTING
                im_objcateg        = cl_swf_evt_event=>mc_objcateg_cl
                im_objtype         = lv_objtype
                im_event           = lv_event
                im_objkey          = wf_objkey
                im_event_container = lr_event_parameters.
                CATCH cx_swf_evt_invalid_objtype .
                  CATCH cx_swf_evt_invalid_event .
                  ENDTRY.
                  



Attention:
The RAISE is not strictly required to be triggered with the raise_in_update_task method, but it depends on the synchronization factor of the raise.

JSON

It's possible to use the /UI2/CL_JSON class to transform ABAP objects into JSON and vice versa.

DATA: lt_flight TYPE STANDARD TABLE OF sflight,
lrf_descr TYPE REF TO cl_abap_typedescr,
lv_json   TYPE string.


SELECT * FROM sflight INTO TABLE lt_flight.
  
  " serialize table lt_flight INTO JSON, skipping initial FIELDS AND converting ABAP FIELD names INTO camelCase<br><br>
  lv_json = /ui2/cl_json=>serialize( DATA = lt_flight compress = ABAP_TRUE pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).
  WRITE / lv_json.
  
  CLEAR lt_flight.
  
  " deserialize JSON string json INTO internal table lt_flight doing camelCase TO ABAP LIKE FIELD name mapping<br><br>
  /ui2/cl_json=>deserialize( EXPORTING json = lv_json pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING DATA = lt_flight ).
  
  " serialize ABAP object INTO JSON string <br><br>
  lrf_descr = cl_abap_typedescr=>describe_by_data( lt_flight ).
  lv_json = /ui2/cl_json=>serialize( lrf_descr ).
  WRITE / lv_json.