Modern ABAP Syntax in Reports — VALUE, FOR, REDUCE, COND, Table Expressions

Modern ABAP Syntax in Reports: VALUE, FOR, REDUCE, COND, Table Expressions
ABAP Reports Series · Blog 12 of 15

Modern ABAP Syntax: VALUE, FOR, REDUCE, COND, Table Expressions

Intermediate · 22 min read · Needs Blog 4

1. Why modern syntax?

Since ABAP 7.40 (2013) the language has a shorter, expression-based style. Compared with classical ABAP it gives you:

  • Fewer lines, with no long DATA list at the top.
  • Fewer errors, because values are created where they are used.
  • A functional feel: describe the result, not every step.
Check your release

Most of this blog needs ABAP 7.40 or newer. A few operators (FILTER, xsdbool) need 7.40 SP08. Run System > Status in SAP GUI to see your version. Every example has a classical equivalent if you are on an older system.

2. Inline declarations

Declare a variable at the place of its first use with DATA( ). ABAP derives the type from the value.

" Classical
DATA lv_date TYPE d.
lv_date = sy-datum.

" Modern
DATA(lv_date) = sy-datum.

" Other places inline declarations work
SELECT vbeln, erdat FROM vbak INTO TABLE @DATA(lt_vbak) UP TO 10 ROWS.
LOOP AT lt_vbak INTO DATA(ls_vbak).    ENDLOOP.
LOOP AT lt_vbak ASSIGNING FIELD-SYMBOL(<ls>). ENDLOOP.
READ TABLE lt_vbak INTO DATA(ls_row) INDEX 1.
CALL METHOD lo_obj->get( IMPORTING ev_value = DATA(lv_val) ).
Scope: the whole block, not the line

An inline variable is visible from its declaration to the end of the method, form, or event block. You cannot declare the same name twice in that block, even inside a loop.

3. VALUE: build structures and tables

VALUE creates a structure or internal table in one expression. Use the type name, or # to let ABAP infer it from the target.

" Structure
DATA(ls_order) = VALUE ty_order( vbeln = '0000000001' erdat = sy-datum ).

" Table with rows: each row in its own brackets
DATA(lt_orders) = VALUE ty_order_t(
  ( vbeln = '0000000001' erdat = '20261001' netwr = 500 )
  ( vbeln = '0000000002' erdat = '20261002' netwr = 300 ) ).

" Empty table (also the way to clear a table)
lt_orders = VALUE #( ).

" Keep the old rows and add one
lt_orders = VALUE #( BASE lt_orders ( vbeln = '0000000003' ) ).

" Inline in an APPEND
APPEND VALUE #( vbeln = '0000000004' ) TO lt_orders.
  • Perfect for test data, constants tables, and initial values.
  • Without BASE, the new rows replace all existing rows.

4. NEW: create objects

DATA(lo_report) = NEW lcl_report( ).           " object of a class
DATA lo_other TYPE REF TO lcl_report.
lo_other = NEW #( ).                          " type inferred from target

DATA(lr_table) = NEW ty_order_t( ).        " reference to a new table
Not every class can use NEW

Some classes, such as CL_SALV_TABLE, hide their constructor and give you a factory method instead (Blog 15). Use NEW only for classes that allow public instantiation.

5. COND and SWITCH

Both return a value, so you can use an IF-like decision inside an assignment.

" COND: based on conditions (like IF)
DATA(lv_status) = COND string( WHEN sy-subrc = 0 THEN 'OK' ELSE 'ERROR' ).

DATA(lv_size) = COND string(
  WHEN ls_order-netwr > 10000 THEN 'Large'
  WHEN ls_order-netwr >  1000 THEN 'Medium'
  ELSE                              'Small' ).

" SWITCH: based on one value (like CASE)
DATA(lv_text) = SWITCH string( ls_order-vbtyp
  WHEN 'A' THEN 'Inquiry'
  WHEN 'B' THEN 'Quotation'
  WHEN 'C' THEN 'Order'
  ELSE         'Other' ).
CONDSWITCH
LikeIF ... ELSEIFCASE
TestsAny logical conditionsOne value against several constants
Without ELSEResult is the initial valueRaises an exception if nothing matches

Use them for a simple value that depends on a condition. If the branches need several statements, use a normal IF.

6. REDUCE: table to one value

REDUCE walks over a table and produces a single result: a sum, a count, a text.

" Sum
DATA(lv_total) = REDUCE netwr( INIT s = CONV netwr( 0 )
                                FOR ls IN lt_orders
                                NEXT s = s + ls-netwr ).

" Count with a condition
DATA(lv_count) = REDUCE i( INIT c = 0
                           FOR ls IN lt_orders WHERE ( vbtyp = 'C' )
                           NEXT c = c + 1 ).

" Join values into one text
DATA(lv_list) = REDUCE string( INIT t = ``
                            FOR ls IN lt_orders
                            NEXT t = COND #( WHEN t IS INITIAL THEN ls-vbeln
                                                   ELSE |{ t }, { ls-vbeln }| ) ).
  • INIT sets the start value. FOR picks the rows. NEXT says how to update.
  • The type before the bracket (netwr, i, string) is the type of the result.
  • Replaces a LOOP plus a helper variable.

7. FOR: table from table

Inside VALUE, FOR builds a new table from an existing one.

" Map: only the order numbers
DATA(lt_vbeln) = VALUE ty_vbeln_t( FOR ls IN lt_orders ( ls-vbeln ) ).

" Filter: only category C rows
DATA(lt_c) = VALUE ty_order_t( FOR ls IN lt_orders
                                  WHERE ( vbtyp = 'C' ) ( ls ) ).

" Transform: build new rows from old ones
DATA(lt_big) = VALUE ty_order_t(
  FOR ls IN lt_orders WHERE ( netwr > 1000 )
  ( vbeln = ls-vbeln
    erdat = ls-erdat
    netwr = ls-netwr * 2 ) ).

" With the row number
DATA(lt_numbered) = VALUE ty_text_t(
  FOR ls IN lt_orders INDEX INTO lv_idx
  ( |{ lv_idx }: { ls-vbeln }| ) ).
NeedUse
Pick one columnFOR ls IN lt ( ls-field )
Keep matching rowsFOR ls IN lt WHERE ( ... ) ( ls )
Change valuesFill the fields in the row brackets
Keep rows of a sorted or hashed tableFILTER (see bonus operators)

lv_idx in the last example must be declared first as an integer variable. Use FOR to build or reshape a table. Do not use it when every row needs several statements.

8. Table expressions

You met these in Blog 4. Square brackets read a row inside any expression.

DATA(ls_first) = lt_orders[ 1 ].                              " by index
DATA(ls_found) = lt_orders[ vbeln = '0000000002' ].        " by key
DATA(lv_date)  = lt_orders[ 1 ]-erdat.                      " one field
lt_orders[ 1 ]-erdat = sy-datum.                            " write a field

IF line_exists( lt_orders[ vbeln = '0000000009' ] ).
  DATA(lv_pos) = line_index( lt_orders[ vbeln = '0000000009' ] ).
ENDIF.

" Safe read: no exception, empty row if missing
DATA(ls_safe) = VALUE #( lt_orders[ vbeln = '0000000009' ] OPTIONAL ).

" Safe read with your own fallback row
DATA(ls_dflt) = VALUE #( lt_orders[ vbeln = '0000000009' ]
                           DEFAULT ls_fallback ).
A missing row ends the program

If no row matches, a table expression raises CX_SY_ITAB_LINE_NOT_FOUND. It does not set sy-subrc for you to check. Guard it with line_exists( ), OPTIONAL, or TRY ... CATCH.

9. String templates and functions

A string template is text between pipe characters. Anything in curly braces is evaluated and inserted.

DATA(lv_msg) = |Order { lv_vbeln } created on { lv_erdat DATE = USER }|.

|{ lv_amount NUMBER = USER }|                      " number in the user's format
|{ lv_amount CURRENCY = 'INR' }|                " amount with currency decimals
|{ lv_text WIDTH = 20 ALIGN = RIGHT }|             " fixed width, right aligned
|{ lv_vbeln ALPHA = OUT }|                      " remove leading zeros

Templates replace most uses of CONCATENATE ... SEPARATED BY and WRITE ... TO.

String functions

FunctionResult
to_upper( lv_text ) / to_lower( )Changes case
condense( lv_text )Removes extra blanks
strlen( lv_text )Length of the text
substring( val = lv_text off = 0 len = 5 )Part of the text
replace( val = lv_text sub = 'old' with = 'new' )Replaces text
find( val = lv_text sub = 'abc' )Position of the match, or -1
contains( val = lv_text sub = 'abc' )True or false, for use in IF
concat_lines_of( table = lt_text sep = ',' )Joins a table of texts
" Splitting uses the SPLIT statement, with an inline target table
SPLIT lv_text AT ',' INTO TABLE DATA(lt_parts).

10. Bonus operators

OperatorWhat it does
CONV type( value )Converts a value to another type inside an expression
CORRESPONDING #( struct )Copies fields with the same name between structures or tables
xsdbool( condition )Turns a condition into abap_true or abap_false
FILTER #( lt WHERE ... )Fast row filter. The table needs a sorted or hashed key, or a secondary key.
LET ... INDefines a helper variable inside an expression

11. Before and after

The same job, written both ways. Goal: list of order numbers, total value, and a text for each category.

Classical

DATA: lt_vbeln TYPE ty_vbeln_t,
      ls_vbak  TYPE ty_order,
      lv_total TYPE netwr,
      lv_text  TYPE string.

LOOP AT lt_orders INTO ls_vbak.
  APPEND ls_vbak-vbeln TO lt_vbeln.
ENDLOOP.

CLEAR lv_total.
LOOP AT lt_orders INTO ls_vbak.
  lv_total = lv_total + ls_vbak-netwr.
ENDLOOP.

CASE ls_vbak-vbtyp.
  WHEN 'C'.  lv_text = 'Order'.
  WHEN 'A'.  lv_text = 'Quotation'.
  WHEN OTHERS. lv_text = 'Other'.
ENDCASE.

Modern

DATA(lt_vbeln) = VALUE ty_vbeln_t( FOR ls IN lt_orders ( ls-vbeln ) ).

DATA(lv_total) = REDUCE netwr( INIT s = CONV netwr( 0 )
                                FOR ls IN lt_orders
                                NEXT s = s + ls-netwr ).

DATA(lv_text) = SWITCH string( ls_vbak-vbtyp
                          WHEN 'C' THEN 'Order'
                          WHEN 'A' THEN 'Quotation'
                          ELSE         'Other' ).

The modern version has no helper variables to declare or clear, and each result is created in one place.

12. When modern syntax hurts

Shorter is not always clearer
  • Deep nesting. A REDUCE with a COND inside a FOR is hard to read. Rule of thumb: three or more nested expressions, use a classical loop.
  • Several statements per row. Use LOOP.
  • Performance. Modern syntax is usually as fast as the classical code, but not always. Measure big tables (Blog 13).
  • Your team. Colleagues who do not know the syntax cannot maintain it. Agree on a style and comment unusual expressions.

13. Try it yourself

  • Rewrite the grouping example from Blog 4 with VALUE, FOR and REDUCE.
  • Build a test table of 5 orders with VALUE #( ( ... ) ( ... ) ).
  • Total netwr with REDUCE.
  • Keep only vbtyp = 'C' rows with FOR ... WHERE.
  • Join all order numbers with REDUCE and a string template.
  • Compare each result with its classical version side by side.

14. Common mistakes

  • Table expression without a guard. A missing row causes a short dump.
  • Forgetting BASE. VALUE #( ( ... ) ) replaces the whole table.
  • The same inline name twice in one method or event block. It is a syntax error.
  • REDUCE or table expression inside a LOOP over the same table. It can turn a fast job into a nested search.
  • Missing type for COND or VALUE where # cannot be inferred, such as in a DATA( ) declaration.
  • SWITCH without ELSE. An unexpected value raises an exception.
  • Over-nesting. If you need to read it three times, write a loop.
  • Calling split( ) like a function. Use the SPLIT statement.

Up next: Blog 13

Performance and Best Practices. Fast SELECT statements, the right internal table type, avoiding nested loops, and the tools to find slow code.

ABAP Reports: Zero to Hero · Blog 12 of 15
WhatsApp WhatsApp us
Call Now Button