Modern ABAP

Constructor Expressions

(C) Brandeis Consulting

Introduction

Constructor expressions are a very useful and powerful enhancement of the ABAP language.
They always start with a constructor operator.

Common Features of Constructor Expressions

  • All constructor expressions return a value.
  • The syntax is always
    <constructor-operator> <datatype>( ... )

Simple Example with the VALUE Operator

TYPES: BEGIN OF ts_data,
          a     TYPE i,
          b(10) TYPE c,
        END OF ts_data.
TYPES tt_data TYPE STANDARD TABLE OF ts_data WITH DEFAULT KEY.
DATA  lt_data TYPE tt_data.

lt_data = VALUE tt_data( ( a = 1  b = 'ABC'  )
                         ( a = 13 b = 'Hello') ).
(C) Brandeis Consulting

List of Constructor Operators

  • VALUE and NEW create new data objects, where the latter returns a reference to it.
  • COND and SWITCH are conditional expressions. They return different values depending on logical conditions.
  • CORRESPONDING creates a new data object based on an existing one.
  • REF creates a reference like GET REFERENCE OF.
  • EXACT performs calculations only if they can be done without rounding errors; otherwise, an exception is raised.
  • REDUCE derives a value from an internal table.
  • CONV and CAST convert data types.
  • FILTER creates a new internal table by filtering an existing one.
(C) Brandeis Consulting

The Data Type of Constructor Expressions

Explicit Data Type

An explicit data type can be specified, such as a

  • Data element
  • Table type
  • Structure type
  • Class name

Type Inference with #

If the hash sign # is used instead of a concrete type, the system tries to infer the type from the context, e.g.

  • From the expected type on the lhs of an assignment
  • From a method parameter type

This is similar to inline declarations with DATA(...), where the type is also inferred from the context.

Example for EXACT - Class ZBC_OTHER_EXPRESSIONS

TRY.
    DATA(result) = EXACT int4( 10 / input ).
    out->write( |The result is exact { result }| ).
  CATCH cx_sy_conversion_rounding INTO DATA(lx_conv).
    out->write( |The result was rounded to { lx_conv->value }| ).
ENDTRY.
(C) Brandeis Consulting

VALUE Operator - Overview

Without Arguments

Without additional arguments, the VALUE operator creates an initial data object for any data type.

Structures

VALUE structype( field1 = value1 field2 = value2 ... )

Tables

VALUE tabletype( ( field1 = value1 field2 = value2 ... ) " Row 1
                 ( ... )                                " Row 2
                 ... )
(C) Brandeis Consulting

VALUE Operator with Constant Data

The most important operator, since it allows you to construct internal tables with fixed content without many APPENDs.

DATA lr_data TYPE RANGE OF dats.

lr_data = VALUE #( ( low = sy-datum sign = 'I' option = 'GE' )
                   ( low = '99991231' sign = 'I' option = 'LE' ) ).

SELECT *
  FROM zbc_tasks
  WHERE due_date IN @lr_data
  INTO TABLE @DATA(result).

Especially in unit tests, this is extremely useful since we can easily create test and comparison data.
Instead of literals, variables can of course also be used.

(C) Brandeis Consulting

Internal Table Extension with BASE in VALUE

If you want to add rows from an existing table to a new one, you can use the keyword BASE:

data(first_table) = value tt_demo( curr = 'EUR' account = '12322' ( date = sy-datum     amount = 123 )
                                                                  ( date = sy-datum + 1 amount = 130 ) ).
out->write( name = |\nFirst Table| data = first_table ).

data(second_table) = value tt_demo( base first_table
                                    curr = 'EUR' account = '77777' ( date = sy-datum     amount = 12 )
                                                                   ( date = sy-datum + 1 amount = 11 ) ).
out->write( name = |\nSecond Table| data = second_table ).
First Table
DATE        AMOUNT  ACCOUNT  CURR
2022-09-29  123.0   12322    EUR
2022-09-30  130.0   12322    EUR

Second Table
DATE        AMOUNT  ACCOUNT  CURR
2022-09-29  123.0   12322    EUR
2022-09-30  130.0   12322    EUR
2022-09-29  12.0    77777    EUR
2022-09-30  11.0    77777    EUR
(C) Brandeis Consulting

Example: VALUE Operator in BEx Variable Exits

Classic

DATA: loc_var_range type RRRANGEEXIT,
      l_s_range type rrrangesid,
      en_year(4) type c,
      f_day_year(8) type c,
      en_mmdd(4) type c,
      range_low(8) type c.
IF I_STEP = 2.
LOOP AT i_t_var_range INTO loc_var_range
                      WHERE VNAM = 'ZENTRYDATE'.
clear l_s_range.
range_low = loc_var_range-low.
en_year = range_low+0(4).
en_year = en_year - 1.
en_mmdd = range_low+4(4).
clear range_low.
CONCATENATE en_year '0101' INTO f_day_year.
CONCATENATE en_year en_mmdd INTO range_low.
l_s_range-low = f_day_year.
l_s_range-high = range_low.
l_s_range-sign = 'I'.
l_s_range-opt = 'BT'.
APPEND l_s_range TO e_t_range.
ENDLOOP.
ENDIF.

With VALUE Operator

if i_step = 2.
 try.
  data(entrydate) = i_t_var_range[ vnam = 'ZENTRYDATE' ]-low.

  data(lastyear) = |{ entrydate(4) - 1 }|.
  data(monthday) = entrydate+4(4).

  et_range = value #( base et_range
                    ( low  = lastyear && '0101'
                      high = lastyear && monthday
                      sign = 'I'
                      opt  = 'EQ' ) )
  catch cx..
    "Variable not found.
  endtry.
endif.
(C) Brandeis Consulting

VALUE for Tables: Common Components

Values that are identical in several rows can be defined before the first row using that value:

lt_range = value #( sign = 'I' option = 'EQ' ( low = '20221031'  )
                                             ( low = '20230406'  )
                               option = 'BT' ( low = '20221102'  high = '20221104' )
                                             ( low = '20221221'  high = '20230107' )
                                             ( low = '20230530'  high = '20230609' )
                                             ( low = '20230727'  high = '20230909' ) ).

Fields filled outside must not be filled again inside the parentheses.

(C) Brandeis Consulting

Counter Loops with FOR in Expressions

DATA lt_int TYPE TABLE OF i.

lt_int = VALUE #( FOR i = 1
                  THEN i + 1
                  UNTIL i >= 10
                  ( i )  ).

out->WRITE( lt_int ).
  • After FOR, a variable is initialized
  • After THEN, the variable can be modified
  • UNTIL or WHILE defines a loop exit condition
  • After the condition, an expression follows; in VALUE, it usually creates a table row
(C) Brandeis Consulting

Table Loops with FOR in Expressions

With FOR <row> IN <table> you can loop over an internal table:

SELECT summary,
       task_id
  FROM zbc_tasks
  INTO TABLE @DATA(lt_data)
  UP TO 10 ROWS.

TYPES tt_tmp LIKE lt_data.

DATA(lt_tmp) = VALUE tt_tmp( FOR ls_data IN lt_data WHERE ( task_id < 4  )
                              ( summary = to_upper( ls_data-summary ) task_id = 42 )  ).

out->write( lt_tmp ).
  • The WHERE condition is optional and must be in parentheses.
  • Group processing is possible with FOR GROUPS.
  • Optionally, the current loop index can be assigned with INDEX INTO <indexvariable>.
(C) Brandeis Consulting

NEW

The NEW constructor operator can be used in two ways:

  1. To instantiate objects with
    NEW <classname>( <constructor parameters> )
  2. To create data objects by reference
data(lr_tmp) = new tt_demo( ( account = '123' amount = '12.34' curr = 'EUR' )
                            ( account = '125' amount = '12.34' curr = 'EUR' ) ).
assign lr_tmp->* to field-symbol(<fs>).
out->write( <fs> ).

The NEW operator is otherwise similar to VALUE:

  • You can use BASE to inherit table contents.
  • You can use FOR to create loops.
(C) Brandeis Consulting

The CONV Constructor Operator

The CONV operator can be used to convert the data type of a data object.
CONV <target type>( <data object> ).

This is especially useful when you would otherwise need an additional helper variable, as in the following example:

With helper variable

METHOD demo_conv.
  data lv_text type char8.
  lv_text = sy-datum.
  my_method( text = lv_text ).
ENDMETHOD.

With CONV operator

METHOD demo_conv.
  my_method( text = CONV #( sy-datum ) ).
ENDMETHOD.

Especially for method calls, the target data type can be inferred automatically.
This is extremely convenient — but also dangerous. See the Clean Code recommendations.

(C) Brandeis Consulting

Converting Object References with the CAST Operator

With CAST, we can perform up-casts or down-casts along an ABAP class inheritance hierarchy.

METHOD demo_cast.
  data ls_task type zbc_tasks.

  data(lo_structdescr) = cast cl_abap_structdescr( cl_abap_structdescr=>describe_by_data( ls_task ) ).

  data(lt_components) = lo_structdescr->get_components(  ).
ENDMETHOD.

If the conversion is not possible, an exception of type CX_SY_MOVE_CAST_ERROR is raised.

(C) Brandeis Consulting

EXACT Expressions

EXACT performs assignments only when they can be done without loss (e.g., truncating strings or rounding). Otherwise, the exception class CX_SY_CONVERSION_ERROR is raised.

EXACT performs calculations only when they can be done without rounding errors. Otherwise, the exception class CX_SY_CONVERSION_ROUNDING is raised.
The rounded result can be accessed in the CATCH block.

(C) Brandeis Consulting

Creating References to Data with REF

The constructor operator REF creates data references to existing objects or table expressions.
REF <datatype>|#( <data object> )
This corresponds to the traditional statement:

GET REFERENCE OF <data object> INTO <reference variable>.

Together with inline declarations, this allows a more elegant way to obtain a reference variable — often without explicitly knowing the type.

DATA(lr_components) = REF #( lt_components ).
(C) Brandeis Consulting

The CORRESPONDING Constructor Operator

CORRESPONDING allows you to create a new structured data object (table or structure) from another one by copying fields with identical names.
It is similar to MOVE-CORRESPONDING.

DATA: BEGIN OF ls_task_small,
        task_id   TYPE zbc_tasks-task_id,
        summary   TYPE zbc_tasks-summary,
        new_field TYPE char10,
      END OF ls_task_small.

SELECT SINGLE *
  FROM zbc_tasks
  INTO @DATA(ls_task_original).

" MOVE-CORRESPONDING ls_task_original TO ls_task_small.
ls_task_small = corresponding #( ls_task_original ).
All examples use structures, but the same applies 1:1 to internal tables.

SAP Documentation: CORRESPONDING

(C) Brandeis Consulting

CORRESPONDING with BASE

An important difference from MOVE-CORRESPONDING is that CORRESPONDING creates a new data object.
Thus, all fields that do not exist in the source remain initial.

Using BASE, you can specify a structure whose content is copied beforehand into the result of the constructor operator.
This can prefill initial values.

If the target variable is given, the behavior is identical to MOVE-CORRESPONDING.

ls_task_small = CORRESPONDING #( BASE ( ls_task_small )
                                 ls_task_original ).
(C) Brandeis Consulting

CORRESPONDING with MAPPING

If the target structure has fields with different names, you can map them explicitly with MAPPING.

DATA: BEGIN OF ls_task_small,
        id    TYPE zbc_tasks-task_id,
        title TYPE zbc_tasks-summary,
      END OF ls_task_small.

SELECT SINGLE *
  FROM zbc_tasks
  INTO @DATA(ls_task_original).

ls_task_small = corresponding #( ls_task_original
                                MAPPING id    = task_id
                                        title = summary ).
(C) Brandeis Consulting

CORRESPONDING with EXCEPT

If not all identically named fields should be transferred to the target structure, you can exclude them explicitly using EXCEPT.

DATA: BEGIN OF ls_task_small,
        id       TYPE zbc_tasks-task_id,
        title    TYPE zbc_tasks-summary,
        assignee TYPE zbc_tasks-assignee,
      END OF ls_task_small.

SELECT SINGLE *
  FROM zbc_tasks
  INTO @DATA(ls_task_original).

ls_task_small = corresponding #( ls_task_original
                                MAPPING id    = task_id
                                        title = summary
                                EXCEPT assignee ).
(C) Brandeis Consulting

CORRESPONDING with Lookup

A special form of the CORRESPONDING operator is a lookup on another internal table.
This is similar to a LEFT OUTER (Equi-) JOIN with a cardinality of :1 in SQL. That means:

  • If a record is found in the lookup table, the corresponding data is transferred according to the rules of the CORRESPONDING operator.
  • If no record is found, the source line remains unchanged.
  • The join conditions always compare with =, and conditions are combined with AND.
  • The target fields for the lookup must already exist in the original table.
DATA lt_lookup TYPE HASHED TABLE OF I_CountryText WITH UNIQUE KEY country.
DATA(lt_orig) = VALUE tt_demo( ( country = 'DE' )
                               ( country = 'US' ) ).
SELECT * FROM i_countrytext WHERE language = 'D' INTO TABLE @lt_lookup.

DATA(lt_new) = CORRESPONDING tt_demo( lt_orig FROM lt_lookup
                                              USING country = country
                                              MAPPING country_text = CountryName  ).
out->write( lt_new ).
(C) Brandeis Consulting

Conditional Expressions with the SWITCH Operator

The SWITCH operator corresponds to a simple CASE expression in SQL.
The value returned depends on comparisons of a fixed comparison value with constants.

Syntax SWITCH

SWITCH <Returntype>( <ComparisonValue> WHEN <Const1> THEN <Expression1>
                                  [WHEN <Const2> THEN <Expression2>]
                                   ...
                                  [ELSE <ExpressionN>] )

Example

DATA(result) = switch tt_demo( sy-datum WHEN '20220929' THEN VALUE #( ( amount = 10 curr = 'EUR'  ) )
                                        WHEN '20220930' THEN VALUE #( ( amount = 11 curr = 'EUR'  ) )
                                        ELSE                 VALUE #( ( amount = 9 curr = 'USD' ) ) ).
out->write( result ).

If no condition matches and no ELSE expression is defined, the initial value of the return type is returned.

(C) Brandeis Consulting

Conditional Expressions with the COND Operator

The COND operator corresponds to a "searched CASE" or CASE WHEN expression in SQL.
Independent conditions are checked sequentially.
The first condition evaluating to TRUE determines the return value.
If none matches, the ELSE value is returned.
If ELSE is not defined, an initial value is returned.

Syntax COND

COND <Returntype>(  WHEN <Condition1> THEN <Expression1>
                   [WHEN <Condition2> THEN <Expression2>]
                   ...
                   [ELSE <ExpressionN>] )

Example

DATA(result) = COND tt_demo( WHEN sy-datum = '20220929' THEN VALUE #( ( amount = 10 curr = 'EUR'  ) )
                             WHEN sy-langu = 'D'        THEN VALUE #( ( amount = 11 curr = 'EUR'  ) )
                             ELSE                            VALUE #( ( amount = 9 curr = 'USD' ) ) ).
(C) Brandeis Consulting

Filtering Internal Tables with FILTER (1/2)

The FILTER operator allows you to create internal tables by filtering another internal table — either using a WHERE clause or another table.

SAP Documentation – Basic Filtering

When using FILTER, the filter criteria must be part of a sorted or hashed key; otherwise, a syntax error occurs.

Example: Filter with WHERE

DATA lt_data TYPE sorted TABLE OF I_CountryText WITH UNIQUE KEY language country.
SELECT * FROM i_countrytext INTO TABLE @lt_data.

out->write( filter #( lt_data where language = 'D' ) ).
(C) Brandeis Consulting

Filtering Internal Tables with FILTER (2/2)

Example: FILTER with Another Table

Alternatively, filtering can be done based on another internal table — equivalent to an INNER JOIN in SQL.

DATA lt_data TYPE sorted TABLE OF I_CountryText WITH UNIQUE KEY country.
DATA(lt_filter) = VALUE tt_demo( ( country = 'DE' )
                                 ( country = 'US' ) ).
SELECT * FROM i_countrytext WHERE language = 'D' INTO TABLE @lt_data.

DATA(lt_new) = filter #( lt_data in lt_filter where country = country  ).

out->write( lt_new ).
(C) Brandeis Consulting

REDUCE Expressions

The REDUCE operator reduces an internal table to a single value.
This resulting value may itself be another table.

SELECT summary
  FROM zbc_tasks
  INTO TABLE @DATA(lt_tasks)
  UP TO 10 ROWS.

DATA(lv_result) = REDUCE string( INIT r  TYPE string
                                 FOR line IN lt_tasks
                                 NEXT
                                  r &&= line-summary(1)
                                  ).
out->write( lv_result ).
out->write( lt_tasks ).
(C) Brandeis Consulting

Syntax of the REDUCE Operator

Simplest Form

REDUCE <Returntype>( INIT <Variable Declarations>
                     FOR <FOR-Expression>
                     NEXT <Result Construction> ).

Advanced Options

  • You can declare helper variables before the variable declaration using LET.
  • Multiple variables can be declared; the first one is the return variable and must be convertible to the Returntype.
  • You can loop over multiple internal tables using multiple FORs.
  • In the NEXT section, you can also construct internal tables.
(C) Brandeis Consulting

References & Examples for REDUCE

(C) Brandeis Consulting

Clean Code Recommendations

  • Do not implement overly complex logic — keep operations simple.
  • Keep internal variables short and meaningful.
  • Remember: for the debugger, this is a single execution step.
  • Type conversion with CONV can be handy in method calls where strict type checks are enforced.
    However, it bypasses these checks, shifting potential errors from compile-time (syntax errors) to runtime (dumps).
(C) Brandeis Consulting