ABAP Cookbook

Content
  • Working with variables
  • Working with references
  • Working with internal tables
  • Working with references and field-symbols
  • Working with objects
  • Logical expressions/ Control Flow
  • String Processing
  • Error handling 
  • SQL

Checklist

  • An interface models the behaviors of an abstract type. 
  • Only declare types and methods in interfaces. 
  • Use classes as blueprints to create objects. 
  • Think interfaces first; abstract classes second.
  • Use utility classes for basic functions.
  • Inheritance models an is-a relationship. 
  • Avoid deep inheritance hierarchies. 
  • Design for inheritance or disallow it.
  • Prefer composition to inheritance. 
  • Don’t mix stateful and stateless paradigms in the same class. 
  • Use global classes by default. Persönliches Exemplar für Abat IT3 Classes and Interfaces 94
  • Class members visibility should be minimized. 
  • Consider making immutable attributes read-only.
  • Only use read-only attributes in certain situations. 
  • Declare global classes constructors in the PUBLIC SECTION. 
  • Declare local classes constructors in the correct section. 
  • Use dependency injection to decouple from underlying resources. 
  • Consider static creation methods. Only use singleton when multiple instances don’t make sense. 
  • Prefer NEW to CREATE OBJECT.
  • Aim for as few input parameters as possible.
  • Split the behavior instead of adding optional parameters.
  • Use a preferred parameter sparingly.
  • Create separate methods instead of using Boolean parameters.
  • Minimize the number of EXPORTING parameters.
  • Prefer RETURNING to EXPORTING.
  • Consider using the result as the name of RETURNING parameters.
  • Use CHANGING parameters sparingly.
  • Avoid using more than one kind of output parameter.
  • Do not reassign IMPORTING parameters passed by value.
  • Mind the semantics of EXPORTING parameters, prefer pass-by-value.
  • Pass-by-value CHANGING parameters don’t make sense.

Working with variables 

Constants

IF component->type = cl_component_type=>element. ASSIGN COMPONENT component->name OF structure STRUCTURE to FIELDSYMBOL(). ELSEIF component->type = cl_component_type=>structure. “Do something else. ENDIF.

Constants grouping

INTERFACE if_message_constants PUBLIC.
CONSTANTS:
BEGIN OF severity,
warning TYPE msgty VALUE ‘W’.
error TYPE msgty VALUE ‘E’.
system TYPE msgty VALUE ‘S’.
END OF severity.
CONSTANTS:
BEGIN OF msgid_v1,
save_successfull TYPE msgno VALUE ‘001’.
save_failed TYPE msgno VALUE ‘006’.
END OF msgid_v1.
ENDINTERFACE.

String Building

DATA(message) = `Received an unexpected HTTP ` && status_code && ` with message ` && text.
DATA(message) = |Received HTTP code { status_code } with message { text }|.

Boolean functions 

boolc, boolx and xsdbool IF boolc( lv_val1 < lv_val2 ) = |X|. … ENDIF.

XSDBOOL for Inline Decisions
DATA(has_entries) = XSDBOOL( messages IS NOT INITIAL ) is the same as

IF messages IS INITIAL.
has_entries = abap_false.
ELSE.
has_entries = abap_true.
ENDIF.

String Expression

 s1 = 'ab' && 'cd'.

Substring

The return codes of the following function calls are: "CD", "CDEFGH", "EFGH", "AB", and "ABCD".
  • 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' ).

Conditional operators

DATA(lv_status) = COND char5(  WHEN sy-datum LT lv_begin THEN ‘EARLY’
                                                        WHEN sy-datum GT lv_end THEN ‘LATE’  ELSE ‘OK’ ).

DATA(text) = NEW class( )->meth( SWITCH #( sy-langu WHEN ‘D’ THEN `DE`
                                                                                              WHEN ‘E’ THEN `EN`).

Value operator

Variables: VALUE dtype|#( )
Structures: VALUE dtype|#( comp1 = a1 comp2 = a2 … )
Tables: VALUE dtype|#( ( … ) ( … ) … ) …

Working with internal tables 

Standard tables are typically used when individual entries can be accessed via their index. The index of a table entry provides the fastest possible way to access an entry. 
READ TABLE employees REFERENCE INTO employee INDEX 4

Another way to access the rows of a standard table would be by their primary keys.
READ TABLE employees REFERENCE INTO employee WITH TABLE KEY id = 'E101'.

If sorted access to standard tables is required, then entries can be sorted either in ascending or descending order by one or more columns using a SORT statement.
READ TABLE employees REFERENCE INTO employee WITH KEY id = 'E101' BINARY SEARCH.

Sorted tables should be used for larger tables, whose entries always need to be sorted, right from the time of its creation.
DATA employees TYPE SORTED TABLE OF employee WITH UNIQUE KEY id.

Hashed tables should be used only for large tables. The entries in these tables are filled in a single step, never modified, and are often read using their key.

Hashed tables should be used only for large tables. The entries in these tables are filled in a single step, never modified, and are often read using their key.
READ TABLE employees REFERENCE INTO employee WITH TABLE KEY id = 'E107'.


Therefore, the usage of DEFAULT KEY must be avoided. A better approach would be one of the following options: 
  • Explicitly specify the components of the key, for example, with the following statement: DATA employees TYPE STANDARD TABLE OF employee WITH NON-UNIQUE KEY id cost_ center.
  • Use EMPTY KEY if no key is required, for example, with the following statement: DATA employees TYPE STANDARD TABLE OF employee WITH EMPTY KEY.

Append or Insert

The APPEND statement appends one or more rows to an internal index table, which inserts new rows at the end of the internal table, with respect to the primary table index. You can use the APPEND statement in the following ways (does not work for Hashed )
  • APPEND ROWS OF new_employees TO employees.
  • APPEND new_employee TO employees.
The INSERT statement adds one or more rows to an internal table at any specified position
  • INSERT new_employee INTO employees [INDEX 18].
  • INSERT new_employee INTO employees [INDEX 18].
The insertion of new rows is executed successfully only after validating all existing unique table keys, including the primary table key and multiple unique secondary table keys, if present. The system handles any duplicate entries by setting system fields, such as sy-subrc, or by raising appropriate exceptions.

Verifying the Existence of a Row

READ TABLE employees TRANSPORTING NO FIELDS WITH KEY first_name = 'Jason'.
IF sy-subrc = 0.
" Do Something
ENDIF.

LOOP AT employees REFERENCE INTO employee WHERE first_name = 'Jason'.
ENDLOOP.
IF sy-subrc = 0.
" Do Something
ENDIF.

To check the existence of a row by its one (and only one) key:
IF LINE_EXISTS( employees[ key = 'E101' ] ).
To check the existence of a row by any of its column names:
IF LINE_EXISTS( employees[ first_name = 'Jason' ] ).

Retrieving Table Contents

Reading All Rows Matching a Condition using LOOP AT and NESTED IF Iteratively
LOOP AT employees REFERENCE INTO employee.
IF employee->location = 'London'.
" Do Something
ENDIF.
ENDLOOP.

Reading a Single Row Matching a Condition Using READ TABLE
READ TABLE employees REFERENCE INTO employee WITH KEY location = 'London'.
IF sy-subrc = 0.
" Do something
ENDIF.

Reading All Rows Matching a Condition Using LOOP AT and Nested IF
LOOP AT employees REFERENCE INTO employee.
IF employee->location = 'Sydney'.
" Do Something
ENDIF.
ENDLOOP.

Reading All Rows Matching a Condition Using LOOP AT and WHERE
LOOP AT employees REFERENCE INTO employee WHERE location = 'Sydney'.
" Do Something
ENDLOOP.

Reading a Row and Then Reacting to the Exception If It Fails
TRY.
DATA(row) = employees[ id = 'E106' ].
CATCH cx_sy_itab_line_not_found.
RAISE EXCEPTION NEW my_data_not_found( ).
ENDTRY.

Block Processing of Table Rows and Single Row Operations

INSERT LINES OF old_employees FROM 10 TO 25 INTO employees.

Reading Multiple Consecutive Rows of a Table Using LOOP AT
LOOP AT employees ASSIGNING <employee> FROM 9 TO 16 WHERE manager = ‘Helen’.
<employee>-manager = ‘Michael’.
ENDLOOP.

DESCRIBE TABLE and Table Function LINES

 DESCRIBE TABLE employees LINES no_of_employees.
IF no_of_employees = 0.
" Do Something
ENDIF.

IF LINES( employees ) = 0.
" Do Something
ENDIF.

Declaration

  • In standard tables, only the addition NON-UNIQUE KEY can be specified. If uniqueness is not specified, this is added implicitly. The addition UNIQUE KEY cannot be specified.
  • In sorted tables, one of the two additions UNIQUE KEY or NON-UNIQUE KEY must be specified.
  • In hashed tables, the addition UNIQUE KEY must be specified.
Defines a sorted table with a primary key without an explicitly specified name.
DATA sbook_tab
     TYPE SORTED TABLE
     OF sbook
     WITH UNIQUE KEY carrid connid fldate bookid.

Defines a sorted table with a primary key with an explicitly specified name.
DATA sbook_tab
TYPE SORTED TABLE
OF sbook
WITH UNIQUE KEY primary_key
COMPONENTS carrid connid fldate bookid.

Define a sorted table with a primary key, which an alias name is defined for.
DATA sbook_tab
     TYPE SORTED TABLE
     OF sbook
     WITH UNIQUE KEY primary_key ALIAS full_table_key
                 COMPONENTS carrid connid fldate bookid.

Declares an internal hashed table. The row type corresponds to the structure of the database table SPFLI. Two key fields are defined for the primary table key. The other statements demonstrate how the table is filled with rows from database table SPFLI and how a row is read.

DATA: spfli_tab TYPE HASHED TABLE OF spfli
                WITH UNIQUE KEY carrid connid,
      spfli_wa  LIKE LINE OF spfli_tab.

SELECT *
       FROM spfli
       WHERE  carrid = 'LH'
       INTO TABLE @spfli_tab.

spfli_wa = spfli_tab[ KEY primary_key   carrid =  'LH' connid =  '0400' ].

Ranges table is declared, filled, and evaluated in the WHERE condition of a SELECT statement.

DATA carrid_range TYPE RANGE OF spfli-carrid.

carrid_range = VALUE #(
  ( sign = 'I' option = 'BT' low = 'AA' high = 'LH') ).

SELECT *
       FROM spfli
       WHERE carrid IN @carrid_range
       INTO TABLE @DATA(spfli_tab).

Looping Variables

DATA(messages_from_order_processing) = order_processor->get_messages( ).
LOOP AT messages_from_order_processing ASSIGNING FIELD-SYMBOL(<message_from_
order_processing>).
IF highest_severity > <message_from_order_processing>-severity.
highest_severity = <message_from_order_processing>-severity.
ENDIF.
ENDLOOP

DATA(messages_from_order_processing) = order_processor->get_messages( ).
LOOP AT messages_from_order_processing
REFERENCE INTO DATA(message_from_order_processing).
IF highest_severity > message_from_order_processing->severity.
highest_severity = message_from_order_processing->severity.
ENDIF.
ENDLOOP.

Field symbols and references have similar impacts on readability, but a field symbol has advantages in terms of execution performance and therefore is recommended.

Mesh

Cогласно определению, меш — это специальная структура. Компоненты этой структуры называют узлами. Узлы являются либо структурированными внутренними таблицами, либо ссылочными переменными, которые указывают на структурированные внутренние таблицы.

Meshes represent a new type 

1) TYPES: 
BEGIN OF t_manager,
manager_name TYPE char10,
salary TYPE int4,
END OF t_manager,
tt_manager TYPE SORTED TABLE OF t_manager WITH UNIQUE KEY manager_name.

2) TYPES: 
BEGIN OF t_developer,
name TYPE char10,
salary TYPE int4,
manager_name TYPE char10,
END OF t_developer,
tt_developer TYPE SORTED TABLE OF t_developer WITH UNIQUE KEY name.

дополнение WITH UNIQUE KEY или WITH NON-UNIQUE KEY обязательно.

3) TYPES: 
BEGIN OF MESH tm_team, managers TYPE tt_manager 
ASSOCIATION my_employee TO developers ON manager = name, 
developers TYPE tt_developer 
ASSOCIATION my_manager TO managers ON name = manager, 
END OF MESH tm_team.

APPEND VALUE #( name = 'Jerry' salary = 1000 manager_name = 'Jason' ) TO lt_developer.
APPEND VALUE #( name = 'Tom' salary = 2000 manager_name = 'Thomas') TO lt_developer.
APPEND VALUE #( manager_name = 'Jason' salary = 3000 ) TO lt_manager.
APPEND VALUE #( manager_name = 'Thomas' salary = 3200 ) TO lt_manager.

ls_crm_team-developers = lt_developer.
ls_crm_team-managers = lt_manager.

DATA(line) = ls_crm_team-developers\my_manager[ ls_crm_team-developers[ name = 'Jerry' ] ].

Dynamic where condition in loop

LOOP AT itab ASSIGNING <fs> WHERE (cond) ENDLOOP

Get Index of a row using line_index

SYNTAX: <lv_index> = line_index( <ITAB> [ <COLUMN1> = <VALUE> <COLUMN2> = <VALUE> ... ] ).

Assign a component of selected table line to a field symbol

TRY.
assign component 4 of structure it_abc[ 2 ] to  field-symbol(<fs_abc_comp>).
CATCH cx_sy_itab_line_not_found.
...
ENDTRY.

Get the line from the internal table 

DATA(ls_flight) = flight_tab[ KEY id COMPONENTS carrid = 'TA' connid = '0942' ] 
-> Exception CX_SY_ITAB_LINE_NOT_FOUND 

Assign a line from an internal table to a field symbol

TRY.
assign it_abc[ 1 ]-it_xyz[ 1 ] to field-symbol(<fs_abc_line>).
CATCH cx_sy_itab_line_not_found.
...
ENDTRY.

Нужно обязательно проверить sy-subrc и IS ASSIGNED

Check if the line exists in an internal table

IF line_exists( it_abc[ key = 'X' ] ).

Count the number of lines in an internal table

  • Without condition: ASSERT lines( itab ) = lines.
  • With condition: DATA(lv_count) = REDUCE i( INIT i = 0 FOR wa IN lt_mara WHERE ( aenam EQ 'MARSLAN' ) NEXT i = i + 1 ).

Internal Table Queries with REDUCE

The purpose of REDUCE is to reduce an internal table into a single variable (e.g., a sum or a count), which already demonstrates that REDUCE is not a trivial operator.

*instead of making a LOOP inside LOOP between two tables

With REDUCE it is possible to do a mathematical operation grouping by the items of a certain table for example. An example:
  LOOP AT it_ekpo
      ASSIGNING FIELD-SYMBOL(<fs_ekpo>).
      <fs_ekpo>-netwr = REDUCE netwr( INIT val TYPE netwr
                                      FOR wa IN
                                      FILTER #( it_komv
                                                USING KEY key_kposn
                                                WHERE kposn EQ CONV #( <fs_ekpo>-ebelp ) )
                                      NEXT val = val + wa-kwert ).
    ENDLOOP.

Iteration Using REDUCE with Short Names
DATA(sum) = REDUCE #( SUM = 0 FOR i = 1 UNTIL i > lines( items ) NEXT sum = sum + items[ i ]-amount ).

Reduce for Inline Calculation
DATA(net_amount) = REDUCE #( INIT amount TYPE netwr
FOR item IN sales_order-items
NEXT amount = amount + item-net_amount ).

Grouping Internal Tables

LOOP AT lt_open_items INTO DATA( ls_open_items) GROUP BY ( kunnr = ls_open_items-kunnr ) ASCENDING ASSIGNING FIELD-SYMBOL( <customer_items> ).

CLEAR lt_open_items_for_customer.

LOOP AT GROUP <customer_items> ASSIGNING FIELD-SYMBOL( <ls_items>.
APPEND <ls_items> TO lt_open_items_for_customer.
ENDLOOP. "Items for one customer
process_open_items->( EXPORTING it_items = lt_open_items_for_customer CHANGING ct_proposals = ct_proposals ).

ENDLOOP. "Grouped Open Items

Stories...

Change Purchase Orders

To update a list of purchase order items via BAPI_PO_CHANGE each BAPI call is done with the collected list of items belonging to a single purchase order. I used to do it like this

METHOD update.
  DATA ls_item LIKE LINE OF it_item.
  DATA lt_item TYPE tt_item.

  LOOP AT it_item INTO ls_item.
    AT NEW ebeln.
      CLEAR lt_item.
    ENDAT.

    APPEND ls_item TO lt_item.

    AT END OF ebeln.
      update_po( lt_item ).
    ENDAT.
  ENDLOOP.
ENDMETHOD.                    "update

I can now create a GROUP at Purchase Order level:

  METHOD update.
    LOOP AT it_item INTO DATA(ls_item) GROUP BY ls_item-ebeln INTO DATA(lv_ebeln).
      update_po( VALUE #( FOR g IN GROUP lv_ebeln ( g ) ) ).
    ENDLOOP.
  ENDMETHOD.

Priority Queue for Messages

The BAPI call returns messages of type A, E, X, I, W, S. I have pick one to be displayed in an ALV output, I do not care which info/warning/status message is selected, but if error messages (A, E, X) should be preferred.

The design is to order the return messages according to type and pick up the message with the highest priority for display. In a first approach, I assume type X does not occur so lexical ordering AEISWX is appropriate:

METHOD get_current_message.
  DATA lt_return LIKE it_return.
  DATA ls_return TYPE bapiret2.
* Priority
  SORT lt_return BY type.
  LOOP AT lt_return INTO ls_return.
    EXIT.
  ENDLOOP.
  rv_message = ls_return-message.
ENDMETHOD.                    "get_current_message

With GROUP BY it would be:

 METHOD get_current_message.
*   Priority
    LOOP AT it_return INTO DATA(ls_return) GROUP BY ls_return-type.
      rv_message = ls_return-message.
      RETURN.
    ENDLOOP.
  ENDMETHOD.

And it is now easy to roll out my custom ordering logic

  METHOD priority.
    rv_prio = SWITCH #( iv_type WHEN 'A' THEN 0
                                WHEN 'X' THEN 1
                                WHEN 'E' THEN 2
                                WHEN 'W' THEN 3
                                WHEN 'S' THEN 4
                                ELSE 5 ).
  ENDMETHOD.

  METHOD get_current_message.
    LOOP AT it_return INTO DATA(ls_return) GROUP BY priority( ls_return-type ).
      rv_message = ls_return-message.
      RETURN.
    ENDLOOP.
  ENDMETHOD.

Wait, I could use the standard translate( ) function here

  METHOD get_current_message.
    LOOP AT it_return INTO DATA(ls_return)
      GROUP BY translate( val = ls_return-type from = `AXEWS ` to = `012345` ).
      rv_message = ls_return-message.
      RETURN.
    ENDLOOP.
  ENDMETHOD.

Filter IDocs by Segment

To filter out entries containing a given segment from a list of IDocs, I could do

* we check witch idoc message types contains the required segment
  lt_edidc[] = ct_edidc[].
  SORT lt_edidc BY idoctp cimtyp.
  DELETE ADJACENT DUPLICATES FROM lt_edidc COMPARING idoctp cimtyp.

  LOOP AT lt_edidc ASSIGNING <ls_edidc>.
    REFRESH lt_syntax.
    CALL FUNCTION 'EDI_IDOC_SYNTAX_GET'
      EXPORTING
        pi_idoctyp      = <ls_edidc>-idoctp
        pi_cimtyp       = <ls_edidc>-cimtyp
      TABLES
        pt_syntax_table = lt_syntax
      EXCEPTIONS
        OTHERS          = 0.
    READ TABLE lt_syntax TRANSPORTING NO FIELDS WITH KEY segtyp = iv_segnam.
    IF sy-subrc NE 0.
      DELETE ct_edidc WHERE idoctp = <ls_edidc>-idoctp
                        AND cimtyp = <ls_edidc>-cimtyp.
    ENDIF.
  ENDLOOP.

But now that I think in groups, I will GROUP BY:

METHOD filter_by_segment.
    DATA lt_syntax TYPE STANDARD TABLE OF edi_iapi06.

    LOOP AT ct_edidc INTO DATA(ls_edidc)
      GROUP BY ( idoctp = ls_edidc-idoctp
                 cimtyp = ls_edidc-cimtyp )
      WITHOUT MEMBERS ASSIGNING FIELD-SYMBOL(<ls_group>).

      CLEAR lt_syntax[].
      CALL FUNCTION 'EDI_IDOC_SYNTAX_GET'
        EXPORTING
          pi_idoctyp      = <ls_group>-idoctp
          pi_cimtyp       = <ls_group>-cimtyp
        TABLES
          pt_syntax_table = lt_syntax
        EXCEPTIONS
          OTHERS          = 0.

      CHECK NOT line_exists( lt_syntax[ segtyp = iv_segnam ] ).
      DELETE ct_edidc WHERE idoctp = <ls_group>-idoctp
                        AND cimtyp = <ls_group>-cimtyp.
    ENDLOOP.

  ENDMETHOD.

The WITHOUT MEMBERS addition is needed because entries in the GROUP are changed.

Report Contract Release Documentation

From a list of purchasing contract items I compare the release history with the sum of invoices. A s those are value contracts an aggregation of the items is made to compare at contract header level. Without GROUP BY, LOOP AT NEW helps:

  METHOD add_data.
    DATA lv_logsy TYPE logsys.
    DATA ls_totals TYPE lcl_contract=>ts_totals.
    DATA lv_error TYPE flag.
    DATA lo_contract TYPE REF TO lcl_contract.
    FIELD-SYMBOLS <ls_ekab> TYPE ts_ekab.

    CHECK mt_ekab IS NOT INITIAL.

    lv_logsy = get_logsys( ).
    CREATE OBJECT lo_contract
      EXPORTING
        io_ekab = me
        io_rfc  = io_rfc.

*   Value Contracts
    LOOP AT mt_ekab ASSIGNING <ls_ekab>.

      AT NEW konnr.
        lo_contract->consumption( EXPORTING iv_konnr = <ls_ekab>-konnr
                                  IMPORTING es_totals = ls_totals
                                            ev_error = lv_error ).
      ENDAT.

      <ls_ekab>-logsy = lv_logsy.
      <ls_ekab>-wrbtr = read_history( <ls_ekab> ).
      IF <ls_ekab>-erekz EQ abap_true.
        <ls_ekab>-total = <ls_ekab>-wrbtr.
      ELSE.
        <ls_ekab>-total = <ls_ekab>-netwr.
      ENDIF.
      IF lv_error EQ abap_false.
        <ls_ekab>-rewwb = ls_totals-value.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.

But grouping with GROUP BY is simpler to grasp

  METHOD add_data.
    CHECK mt_ekab IS NOT INITIAL.
    DATA(lv_logsy) = get_logsys( ).
    DATA(lo_contract) = NEW lcl_contract( io_ekab = me
                                          io_rfc = io_rfc ).
    LOOP AT mt_ekab INTO DATA(ls_ekab) GROUP BY ls_ekab-konnr INTO DATA(lv_konnr).

      lo_contract->consumption( EXPORTING iv_konnr = lv_konnr
                                IMPORTING es_totals = DATA(ls_totals)
                                          ev_error = DATA(lv_error) ).

      LOOP AT GROUP lv_konnr ASSIGNING FIELD-SYMBOL(<ls_ekab>).
        <ls_ekab>-logsy = lv_logsy.
        <ls_ekab>-wrbtr = read_history( <ls_ekab> ).
        <ls_ekab>-total = SWITCH #( <ls_ekab>-erekz WHEN 'X' THEN <ls_ekab>-wrbtr
                                                             ELSE ls_ekab>-netwr ).
        IF lv_error EQ abap_false.
          <ls_ekab>-rewwb = ls_totals-value.
        ENDIF.
      ENDLOOP.
    ENDLOOP.
  ENDMETHOD.

Aggregate Function SUM like SQL’s GROUP BY

Next – building a subtotal like the aggregate GROUP BY for database tables

  METHOD convert.

    CLEAR et_comm.
    LOOP AT it_ekab ASSIGNING FIELD-SYMBOL(<ls_ekab>)
        GROUP BY ( ekgrp2 = <ls_ekab>-ekgrp2
                   konnr =  <ls_ekab>-konnr
                   logsy = <ls_ekab>-logsy ) INTO DATA(ls_group).
   
      DATA(ls_comm) = VALUE ts_comm( ekgrp2 = ls_group-ekgrp2
                                     konnr = ls_group-konnr
                                     logsy = ls_group-logsy ).
      LOOP AT GROUP ls_group INTO DATA(ls_ekab).
        ls_comm-ktwrt = ls_ekab-ktwrt.        " Contract target value
        ls_comm-total = ls_ekab-rewwb.        " Contract sum of releases
        ls_comm-kwaers = ls_ekab-kwaers.      " Contract currency

        ADD ls_ekab-total TO ls_comm-wrbtr.   " Sum invoices or PO value if no EREKZ
      ENDLOOP.
      ls_comm-exceeded = xsdbool( ls_comm-wrbtr + ls_ekab-rewwb > ls_comm-ktwrt ).

      APPEND ls_comm TO et_comm.
    ENDLOOP.

  ENDMETHOD.

Now, downgrading is pain,

  METHOD convert.
    DATA ls_comm TYPE ts_comm.
    FIELD-SYMBOLS <ls_ekab> TYPE ts_ekab.

    CLEAR et_comm.

    TYPES: BEGIN OF ts_key,
             ekgrp2 TYPE ekgrp,
             konnr TYPE konnr,
             logsy TYPE logsys,
           END OF ts_key.
    DATA lt_key TYPE SORTED TABLE OF ts_key WITH UNIQUE KEY ekgrp2 konnr logsy.
    DATA ls_key LIKE LINE OF lt_key.

    LOOP AT mt_ekab ASSIGNING <ls_ekab>.
      MOVE-CORRESPONDING <ls_ekab> TO ls_key.
      INSERT ls_key INTO TABLE lt_key.
    ENDLOOP.

    LOOP AT lt_key INTO ls_key.

      LOOP AT it_ekab ASSIGNING <ls_ekab> WHERE ekgrp2 = ls_key-ekgrp2
                                            AND konnr = ls_key-konnr
                                            AND logsy = ls_key-logsy.
        AT FIRST.
          MOVE-CORRESPONDING <ls_ekab> TO ls_comm.
        ENDAT.
        ls_comm-ktwrt = <ls_ekab>-ktwrt.        " Contract target value
        ls_comm-total = <ls_ekab>-rewwb.        " Contract sum of releases
        ls_comm-kwaers = <ls_ekab>-kwaers.      " Contract currency

        ADD <ls_ekab>-total TO ls_comm-wrbtr.   " Sum invoices or PO value if no EREKZ
        AT LAST.
          DATA lv_sum LIKE ls_comm-wrbtr.

          lv_sum = ls_comm-wrbtr + <ls_ekab>-rewwb.
          IF lv_sum > ls_comm-ktwrt.
            ls_comm-to_be_checked = abap_true.
          ELSE.
            ls_comm-to_be_checked = abap_false.
          ENDIF.
          APPEND ls_comm TO et_comm.
        ENDAT.
      ENDLOOP.
    ENDLOOP.

  ENDMETHOD.                    "convert

Expressions

Modern ABAP introduced expressions, creating a conflict almost by design as new features were hitherto added to ABAP as new statements.

◉ A statement does something: statements work on data; they are imperative and applied in sequence.
◉ An expression returns a value: pure functions (without side effect) are composable. But note ABAP does not try to be a functional language, the type system does not support complex/algebraic data types

So GROUP BY also has an expression oriented form.

  METHOD convert.
    et_comm = VALUE #( FOR GROUPS ls_group OF <ls_ekab> IN it_ekab
                     GROUP BY ( ekgrp2 = <ls_ekab>-ekgrp2
                                konnr =  <ls_ekab>-konnr
                                logsy = <ls_ekab>-logsy

                                ktwrt = <ls_ekab>-ktwrt    " target value
                                total = <ls_ekab>-rewwb    " sum of releases
                                kwaers = <ls_ekab>-kwaers  " Contract currency
                                exceeded = <ls_ekab>-exceeded

                                rewwb = <ls_ekab>-rewwb
                                count = GROUP SIZE )
             ( VALUE #( LET lv_sum = REDUCE wrbtr( INIT lv_val TYPE wrbtr
                                                   FOR ls_ekab IN GROUP ls_group
                                                   NEXT lv_val = lv_val + ls_ekab-total )
               IN BASE CORRESPONDING ts_comm( ls_group )
               wrbtr = lv_sum  " Sum invoices or PO value if no EREKZ
               to_be_checked = xsdbool( lv_sum + ls_group-rewwb > ls_group-ktwrt ) ) ) ).
  ENDMETHOD.


Extracting One Table from Another (HASHED or SORTED table)

DATA( lt_average ) = FILTER #( lt_all  USING KEY field_key WHERE col_1 > 25 AND col2 < 75 ).
DATA(extract) = FILTER #( spfli_tab IN filter_tab WHERE cityfrom = cityfrom AND cityto = cityto ).

DATA: lt_flights_all TYPE STANDARD TABLE OF spfli 
                     WITH NON-UNIQUE SORTED KEY carrid
                     COMPONENTS carrid,
           lt_flight_lh  TYPE STANDARD TABLE OF spfli.

SELECT *  FROM spfli INTO TABLE @lt_flights_all.
lt_flight_lh = FILTER #( lt_flights_all USING KEY carrid WHERE carrid = 'LH ' ).

По нескольким полям
DATA: lt_splfi_key TYPE SORTED TABLE OF  spfli WITH NON-UNIQUE KEY carrid connid.
DATA: lt_filter_tab_new TYPE SORTED TABLE OF spfli WITH NON-UNIQUE KEY  carrid connid.
 
SELECT * FROM spfli INTO TABLE lt_splfi_key.
lt_filter_tab_new = VALUE spfli_tab( carrid = 'LH ' ( connid = '0401' ) ( connid = '0402' ) ).
DATA(lt_splfi_result_new) = FILTER #( lt_splfi_key IN lt_filter_tab_new
                                           WHERE carrid = carrid AND  connid = connid ).

For each

DATA messages TYPE SORTED TABLE OF t100 WITH NON-UNIQUE KEY sprsl msgnr.
SELECT * FROM t100 WHERE arbgb = 'SABAPDEMOS' INTO TABLE @messages.

value_tab = VALUE #( FOR wa IN messages WHERE ( sprsl = 'E' ) ( wa ) ).
ASSERT value_tab = FILTER #( messages WHERE sprsl = 'E' ).

Working with references and field-symbols

Example §1
DATA(lr_ref) = REF #( lt_itab[ bukrs = ’1234’ belnr = ‘1234567890’ gjahr = ‘2018’ ] ).

Example §2
DATA: lz_counter TYPE REF TO i.
DATA: lt_sa0_key LIKE gt_sa0_key.
lt_sa0_key = CORRESPONDING #( it_sa0_key DISCARDING DUPLICATES ).
lz_counter = REF #( lt_sa0_key[ 1 ]-counter ).
lz_counter->* = lines( it_sa0_key ).

Add a line into an internal table

INSERT VALUE #( ... ) INTO TABLE itab.
Comment: INSERT INTO TABLE works with all tables and key types, thus making it easier for you to refactor the table's type and key definitions if your performance requirements change.

Add one more line in a loop with the base operator
lt_items = VALUE #( BASE lt_items (        itm_number = <ls_pos>-itm_number
material = <ls_pos>-material
net_price = <ls_pos>-net_price
net_value = <ls_pos>-net_value
req_qty = <ls_pos>-req_qty
s_proc_ind = <ls_pos>-s_proc_ind
sales_unit = <ls_pos>-sales_unit
short_text = <ls_pos>-short_text ) ).

Filling Internal Tables from Other Tables Using FOR

DATA(lt_table_to fill) = VALUE lt_table_to fill( FOR ls_line IN lt_another_table WHERE ( column_a < 20 ) field1 = ls_line-column_a field2 = ls_line-column_b) ).

Creating Short-Lived Variables Using LET

DO LINES( lt_table[] ) TIMES.

DATA( ld_line ) = VALUE string(
LET
temp_var1 = lo_iterator->get_next( )
temp_var2 = lt_table[ sy-index ]-key_field
date_string = |{ temp_var1 } { temp_var2} |.
WRITE:/ ld_line.
ENDDO.

*These variables (temp_var1, temp_var2) only exist while creating data using constructor expressions.

Virtual sort 

 data gt_fio type table of gs_fio.
 
     append value #( pernr = '90000001' fio = 'Ivanov I.I.'  bukrs = '0001'     ) to gt_fio.
     append value #( pernr = '00000002' fio = 'Petrov P.P.'  bukrs = '0002'     ) to gt_fio.
     append value #( pernr = '90000999' fio = 'Sidorov S.S.' bukrs = '0000'     ) to gt_fio.
     append value #( pernr = '00000001' fio = 'Ivanov A.A.'  bukrs = '0001'     ) to gt_fio.
     append value #( pernr = '99999999' fio = 'Ivanov B.B.'  bukrs = '9999'     ) to gt_fio.
 
 
   data(lt_index_sort) = cl_abap_itab_utilities=>virtual_sort(
                   im_virtual_source =
                     value #(
 
                       ( source     = ref #( gt_fio )
                         components =
                           value #( ( name = 'bukrs'  descending = abap_true )
                                    ( name = 'fio' ) ) )
                              )
                                                             ).
cl_demo_output=>display( lt_index_sort ).

Logical expressions

Control flow

COND Replacing IF Block
DATA(is_authorized) = COND #(
WHEN authroized_document_type = abap_true AND
authroized_organization = abap_true
THEN authorized
ELSE unauthorized ).


COND with Inline Field Symbol Assignment
DATA(businesspartner) = COND #(
WHEN type = employee
THEN
LET <id> = employees[ employee_id = employee_id ]-businesspartner
IN <id>
WHEN type = contractor
THEN
LET <id> = contractors[ contractor_id = contractor_id ]-businesspartner
IN <id> ).

Case Considering the Single-Responsibility Principle

CASE vehicle_type.
WHEN 'TRAIN'.
duration = calculate_train_duration( ).
WHEN 'PLANE'.
duration = calculate_plane_duration( ).
WHEN 'CAR'.
duration = calculate_car_duration( ).
ENDCASE.

SWITCH for Inline Assignment
DATA(id_to_display) = SWITCH string( id_type
WHEN 'EMPLOYEE' THEN partner-employee_id
WHEN 'EXT_WORK' THEN partner-assignment_id
WHEN 'CUSTOMER' THEN partner-customer_id
ELSE THROW unknown_type( ).

DO 1 TIMES to Avoid Multiple IFs
DO 1 TIMES.
DATA(messages) = precheck_input( input ).
CHECK NOT line_exists( messages[ type = error ] ).
prepare_processing(
EXPORTING input = input
IMPORTING prepared_input = data(prepared_input)
messages = messages ).
CHECK NOT line_exists( messages[ type = error ] ).
messages = process( prepared_input ).
CHECK NOT line_exists( messages[ type = error ] ).
ENDO.
result = COND #( WHEN line_exists( messages[ type = error ] )
THEN failed
ELSE success ).

Cond
DATA(messages) = input_complete_processing( input ). result = COND #( WHEN line_exists( messages[ type = error ] ) THEN failed ELSE success ).


Calculation in logical expressions

CHECK lcl=>mi( 1 ) + abs( -2 ) >= 3.​
CHECK lcl=>mx( x1 ) BIT-OR x2 <> BIT-SET( 4 ).​
CHECK `ab` && 'cd' CN 'xyz'.

Corresponding

(0) With a structure: struct2 = CORRESPONDING #( struct1 MAPPING b1 = a1 ).

(1) DATA(lt_malzeme) = CORRESPONDING tt_malz( lt_mara ).

(2) DATA(lt_malzeme) = CORRESPONDING tt_malz( lt_mara MAPPING material = matnr EXCEPT meins ). 

(3) DATA(itab2) = VALUE t_itab2
( FOR wa IN itab1 WHERE ( col1 < 10 ) ( col1 = wa-col2 col2 = wa-col3 ) ).

(4.1) With the BASE (structure)
struct2 = CORRESPONDING #( BASE ( struct2 ) struct1 ). (4.1) With the BASE (table)
itab = VALUE #( ( 1 ) ( 2 ) ( 3 ) ).
itab = VALUE #( BASE itab ( 4 ) ( 5 ) ( 6 ) ).
The result of the second expression is initialized with itab and then the other lines are added.
After the second assignment, itab contains 1, 2, 3, 4, 5, 6.

String Processing

Removing Leading Zeroes via a Formatting Option

ld_message = |{ ld_delivery ALPHA = OUT }|.

Building Up a String Using Pipes

LD_RESULT = |Local Object { ld_number } / { ld_status }|.

String Templates

lv_string = | Hello my Name is { SY-UNAME } and today is { SY-DATUM date = user } |.

Working with the references

DATA(gz_matnr) = REF #( gv_matnr )
DATA(slfight_ref) = REF #( sflight_tab[ KEY primary_key COMPONENTS carrid = p_carrid connid = p_connid fldate = p_fldate ] )

Grouping with LOOP

LOOP AT GROUP BY

Grouping by one column
LOOP AT spfli_tab INTO wa
GROUP BY wa-carrid.
out->write( wa-carrid ).
ENDLOOP.

Members of one column groups
LOOP AT spfli_tab INTO wa
GROUP BY wa-carrid.
CLEAR members.
LOOP AT GROUP wa INTO member.
members = VALUE #( BASE members ( member ) ).
ENDLOOP.

out->write( members ).
ENDLOOP.

Grouping by one column 
LOOP AT spfli_tab INTO wa
GROUP BY wa-carrid
INTO DATA(key).
out->write( key ).
ENDLOOP.

Members of one column groups

LOOP AT spfli_tab INTO wa
GROUP BY wa-carrid
INTO key.
CLEAR members.
LOOP AT GROUP key INTO member.
members = VALUE #( BASE members ( member ) ).
ENDLOOP.
out->write( members ).
ENDLOOP.

Two column groups without members
LOOP AT spfli_tab INTO wa GROUP BY ( key1 = wa-carrid 
                                                                        key2 = wa-airpfrom
                                                                        index = GROUP INDEX 
                                                                        size = GROUP SIZE ) WITHOUT MEMBERS
                                 INTO DATA(keysplus).
out->write( keysplus ).
ENDLOOP.

By a package

DATA(n) = 10.
DATA group LIKE itab.
LOOP AT itab INTO DATA(wa)
GROUP BY ( sy-tabix - 1 ) DIV n + 1.
CLEAR group.
LOOP AT GROUP wa ASSIGNING FIELD-SYMBOL(<wa>).
group = VALUE #( BASE group ( <wa> ) ).
ENDLOOP.
cl_demo_output=>write( group ).
ENDLOOP.

Grouping with LOOP Using Column Values

LOOP AT flights INTO DATA(flight)
GROUP BY ( carrier = flight-carrid cityfr = flight-cityfrom
size = GROUP SIZE index = GROUP INDEX )
ASCENDING REFERENCE INTO DATA(group_ref).

LOOP AT GROUP group_ref ASSIGNING FIELD-SYMBOL(<flight>).
members = VALUE #( BASE members ( <flight> ) ).
ENDLOOP.

ENDLOOP.

Grouping with LOOP Using a Comparison

 DATA members LIKE numbers.
    LOOP AT numbers INTO DATA(number)
         GROUP BY COND string(
           WHEN number <= threshold THEN |LE { threshold }|
                                    ELSE |GT { threshold }| )
         ASSIGNING FIELD-SYMBOL(<group>).
      out->begin_section( <group> ).
      CLEAR members.
      LOOP AT GROUP <group> REFERENCE INTO DATA(member_ref).
        members = VALUE #( BASE members ( member_ref->* ) ).
      ENDLOOP.

Working with objects


Если исходящие параметры метода в Inline,  то нужно обязательно проверить, чтобы они были не в LOOP. А еще лучше задать локальные пременные.

Enumeration

CLASS shirt DEFINITION. PUBLIC SECTION. TYPES: 
BEGIN OF ENUM tsize, size_s, size_m, size_l, size_xl, 
END OF ENUM tsize. 
METHODS constructor IMPORTING size TYPE tsize. ... PRIVATE SECTION. DATA size TYPE tsize. ENDCLASS.
CLASS shirt IMPLEMENTATION. 
METHOD constructor. me->size = size. 
ENDMETHOD. ENDCLASS.

Enumeration types belong in classes like constants to the static attributes 
TYPES BEGIN OF ENUM enum_type [STRUCTURE struc] [BASE TYPE dtype]. TYPES val1 [VALUE IS INITIAL], TYPES val2 [VALUE val], TYPES val3 [VALUE val], ... TYPES END OF ENUM enum_type [STRUCTURE struc].

CL_ABAP_ENUMDESCR

Static vs Private attribute

Instance attributes are created at a new memory location every single time. However, only one memory allocation is stored for a static attribute. A lifetime of a static attribute depends on the internal session of the program, not on a single user session or ABAP memory.
If the dynamic type for CREATE OBJECT obj TYPE (type) does not match the static type, the exception CX_SY_CREATE_OBJECT_ERROR is thrown

Create an object of a class 

DATA(lo_obj) = NEW zcl_class( ).
DATA lo_obj TYPE REF TO zcl_class. lo_obj = NEW #( ).

Do casting to another object 

DATA(lo_obj2) = CAST zcl_002( lo_obj1 ).

Is Instance Of

IF lo_obj2 IS INSTANCE OF lo_obj1. " ... ENDIF.

Case type of

CASE TYPE OF oref.​
WHEN TYPE lcl_class.​
ENDCASE.

Default Ignore

INTERFACE zif_demo PUBLIC. METHODS: method1 DEFAULT IGNORE, method2.

Using Constructor Operator REF to Fill a TYPE REF TO DATA Parameter

"VALUE is an IMPORTING parameter TYPE ANY
IF value IS SUPPLIED.
lo_obj->log_value( REF#( value )).
ENDIF.

Local classes

  • Definition part
    ******************************************
    CLASS lcl_airplane DEFINITION.
    *------------------------------
  • Public section
    *------------------------------
    PUBLIC SECTION.
     TYPES: t_name(25) TYPE c.
    METHODS:
     constructor,
     set_attributes IMPORTING p_name       TYPE t_name
                              p_planetype  TYPE saplane-planetype,
     display_attributes,
     display_n_o_airplanes.
    *------------------------------
  • Private section
    *------------------------------
      PRIVATE SECTION.
    *   Private attributes
        DATA: name(25) TYPE c,
              planetype TYPE saplane-planetype.
    *   Private static attribute
        CLASS-DATA n_o_airplanes TYPE i.
    ENDCLASS.
    ******************************************
  • Implementation part
    ******************************************
    CLASS lcl_airplane IMPLEMENTATION.
     METHOD constructor.
    *   Counts number of instances
      n_o_airplanes = n_o_airplanes + 1.
     ENDMETHOD.
     METHOD set_attributes.
      name = p_name.
      planetype = p_planetype.
     ENDMETHOD.
     METHOD display_attributes.
      Write:/ 'name ', name , 'plane type ', planetype.
     ENDMETHOD.
     METHOD display_n_o_airplanes.
      WRITE: / 'No. planes:', n_o_airplanes.
     ENDMETHOD.
    ENDCLASS.
    ******************************************
    *& Start of Selection
    ******************************************
    START-OF-SELECTION.
  • Create reference to class lcl_airplane
    DATA: airplane1 TYPE REF TO lcl_airplane,
          airplane2 TYPE REF TO lcl_airplane.
    CREATE OBJECT airplane1.
    CALL METHOD: airplane1->display_n_o_airplanes.
    CREATE OBJECT airplane2.
    CALL METHOD: airplane1->set_attributes
                     EXPORTING p_name = 'Chidanand'
                               p_planetype = 'A380'.
    ******************************************
    *& End of selection
    ******************************************
    END-OF-SELECTION.
  • Using methods
      CALL METHOD: airplane1->display_n_o_airplanes,
                   airplane1->display_attributes.

Error handling

MESSAGE addition to RAISE EXCEPTION RAISE EXCEPTION TYPE cx_dyn_t100 MESSAGE e888(sabapdemos) WITH 'I' 'am' 'an' 'Exception!
Exceptions in conditional expressions with THROW DATA(iflag) = COND i( WHEN cflag = abap_true THEN 1 WHEN cflag = abap_false THEN 0 ELSE THROW cx_demo_dyn_t100( MESSAGE e888(sabapdemos) WITH 'Illegal value!') ) ).

Exception Class with textid

CLASS cx_order DEFINITION PUBLIC
INHERITING FROM cx_static_check FINAL CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_t100_dyn_msg .
INTERFACES if_t100_message .
CONSTANTS:
BEGIN OF cx_example,
msgid TYPE symsgid VALUE 'ORDER',
msgno TYPE symsgno VALUE '003',
attr1 TYPE scx_attrname VALUE 'order_id',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF cx_example.
DATA order_id TYPE vbeln READ-ONLY.
METHODS constructor
IMPORTING
textid LIKE if_t100_message=>t100key OPTIONAL
previous LIKE previous OPTIONAL
order_id TYPE vbeln OPTIONAL.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS cx_example IMPLEMENTATION.
METHOD constructor ##ADT_SUPPRESS_GENERATION.
super->constructor( previous = previous ).
me->order_id = order_id.
CLEAR me->textid.
IF textid is initial.
if_t100_message~t100key = cx_example.
ELSE.
if_t100_message~t100key = textid.
ENDIF.
ENDMETHOD.
ENDCLASS.

Convert Function Module Exception to T100 Dynamic Exception

METHOD read_address.
CALL FUNCTION 'ADDRESS_GET'
EXPORTING
address_id = address_id
IMPORTING
complete_address = complete_address
EXCEPTIONS
address_invalid = 1
error_message = 2.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_function_call
MESSAGE ID sy-msgid
TYPE sy-msgty
NUMBER sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.
ENDMETHOD.

Method Signature Raising Static Exception

CLASS cx_invalid_user_name DEFINITION
INHERITING FROM cx_static_check.
METHODS read_by_user name
IMPORTING
user_name TYPE string
RAISING
cx_invalid_username.

Raise Exception New

METHOD save.
IF status = inconsistent.
RAISE EXCEPTION NEW cx_business_error(
reason = co_inconsistent ).
ENDIF.
ENDMETHOD.

Raise Exception Type with Message

METHOD save.
IF status = inconsistent.
RAISE EXCEPTION TYPE cx_business_error
MESSAGE ID 'MSGID'
NUMBER 123
TYPE 'E'
WITH co_inconsistent.
ENDIF.
ENDMETHOD.

Catch Single Exception

TRY.
 calculate_income( order ).
CATCH cx_currency_conversion INTO DATA(currency_exception).
 send_exception_to_ui( currency_exception ).
ENDTRY.

Usage of IS INSTANCE OF in CATCH Block

TRY.
method_call( ).
CATCH cx_root INTO data(exception).
IF cx_root IS INSTANCE OF cx_specific_exception.
handle_specific_exceptions( exception ).
ELSE.
RAISE EXCEPTION exception.
ENDTRY.

Wrapping foreign_exception

METHOD calculate_tax.
DATA(calculation_enginge) = tax_factory=>get_enginge( ).
TRY.
tax = calculation_enginge->calculate(
amount = amount
country = country ).
CATCH cx_tax_ennginge_exception INTO DATA(foreign_exception)
RAISE EXCEPTION NEW own_exception_type(
previous = foreign_exception ).
ENDTRY.
ENDMETHOD.

CLEANUP for Exceptions

TRY.
TRY.
do_something( ).
CATCH exception_a.
do_something_else( ).
CLEANUP INTO DATA(exception).
first_do_this( exception ).
ENDTRY.
CATCH exception_b INTO DATA(exception_b).
error_handling( exception_b ).
ENDTRY.

Exceptions are for errors 

Anti-Pattern: Exceptions Used in a Normal Flow
TRY.
read_order_from_db( order ).
CATCH INTO DATA(not_found_exception).
persist_new_order( order ).
ENDTRY.

Pattern: Check Instead of Forcing Errors
IF order_not_persisted( order ).
persist_new_order( order ).
ENDIF.
 

Converting Messages into Exceptions

  RAISE EXCEPTION TYPE cx_demo_dyn_t100
    MESSAGE ID    sy-msgid
            TYPE   sy-msgty
            NUMBER sy-msgno
            WITH   sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

Passing the message to another Exception 

...catch ycx_lo_batch_master into data(lx_exception).
raise exception type ycx_mbgmcr_handler
message id lx_exception->if_t100_message~t100key-msgid
type 'E'
number lx_exception->if_t100_message~t100key-msgno
with lx_exception->if_t100_dyn_msg~msgv1
lx_exception->if_t100_dyn_msg~msgv2
lx_exception->if_t100_dyn_msg~msgv3
lx_exception->if_t100_dyn_msg~msgv4.

SQL

Virtual columns

select from SFLIGHT join SCARR on SFLIGHT~CARRID = SCARR~CARRID fields SFLIGHT~CARRID, SFLIGHT~CONNID, SFLIGHT~FLDATE, SCARR~CARRNAME, case when SFLIGHT~FLDATE < @SY-DATUM then @ABAP_TRUE end as OUTDATED into table @data(LT_SFLIGHT).

Join: select fields 

select from SPFLI 
inner join SCARR on SPFLI~CARRID = SCARR~CARRID 
fields SPFLI~*
SCARR~CARRNAME 
into table @data(LT_FLIGHTS_COMPLETE).

Join: with Where parameter 

select from SPFLI 
left outer join SCARR on SPFLI~CARRID = SCARR~CARRID and 
SPFLI~COUNTRYFR = @LK_COUNTRY_BY  and 
SPFLI~COUNTRYTO = @LK_COUNTRY_BY 
fields SPFLI~CONNID, SPFLI~CARRID, SCARR~CARRNAME into table @data(LT_FLIGHTS_DE).

Existence check

select single @ABAP_TRUE 
from SCARR where CARRID eq @LK_CARRIER_FA 
into @data(L_CARRIER_EXISTS).

Select with a Package 

SELECT * FROM MARA INTO TABLE IT_MARA PACKAGE SIZE 100 UP TO 1000 ROWS
  WHERE MTART = P_MTART .
  IF SY-SUBRC = 0.
    APPEND LINES OF IT_MARA TO IT_MARA1. "append data to another internal table
  ENDIF.
ENDSELECT.

SELECT - FROM @itab

DATA result1 LIKE itab.
SELECT table_line
       FROM @itab AS numbers
       INTO TABLE @result1.
cl_demo_output=>write( result1 ).

Case

select from SBOOK fields CARRID, CONNID, 

case SMOKER 
when 'X' then 'SMOKER' 
else 'NON SMOKER' 
end as SMOKER 

into table @data(LT_SMOKER). 

Casting 

select from SPFLI fields 
cast( CARRID as char( 20 ) ) as CARRID_TEXT, 
cast( CONNID as char( 4 ) ) as CONNID_TEXT 
into table @data(LT_SPFLI_CAST).

Arithmetics

select CARRID, CONNID, 
cast( SEATSMAX as fltp ) / cast( 2 as fltp ) as SEATS 
from SFLIGHT into table @data(LT_FLIGHT).

Union

DATA prog_range TYPE RANGE OF trdir-name.

SELECT 'I' AS sign, 'EQ' AS option, obj_name AS low, ' ' AS high
       FROM tadir
       WHERE pgmid = 'R3TR' AND object = 'PROG' AND devclass = @devclass
UNION
SELECT 'I' AS sign, 'CP' AS option, obj_name && '*' AS low, ' ' AS high
        FROM tadir
        WHERE pgmid = 'R3TR' AND object = 'CLAS' AND devclass = @devclass
*Not perfect, from 7.51 on the following is better
*SELECT 'I' AS sign, 'CP' AS option, concat( rpad( obj_name, 30, '=' ) , '*' ) AS low, 
*       ' ' AS high
*       FROM tadir
*       WHERE pgmid = 'R3TR' AND object = 'CLAS' AND devclass = @devclass
UNION
SELECT 'I' AS sign, 'EQ' AS option, 'SAPL' && obj_name AS low, ' ' AS high
       FROM tadir
       WHERE pgmid = 'R3TR' AND object = 'FUGR' AND devclass = @devclass
UNION
SELECT 'I' AS sign, 'CP' AS option, 'L' && obj_name && '+++' AS low, ' ' AS high
       FROM tadir
       WHERE pgmid = 'R3TR' AND object = 'FUGR' AND devclass = @devclass
       INTO TABLE @prog_range.

Comments