matinal:SAP ABAP 7.40及以上新语法全新总结

1.1 Inline declarations

Data Declarations

In ABAP you have many operand positions, where the value of the operand is changed by the statement. The most typical of these "write positions" is the left hand side lhs of an assignment.

lhs = rhs.

But of course there are more. The data objects you can use at these write positions are either writable formal parameters of the procedure you are working in or variables declared with DATA in front of the statement.

In many cases the variables filled by a statement are helper variables that you only need close to the statement. For each of  these helper variables you had to write a data declaration with the DATA statement and of course it was your task to give the variable an adequate type.

Well, the operand type of most write positions is statically fixed and well known to the compiler. And this is why ABAP can offer inline data declarations with Release 7.40. The ingredients are so called declaration positions (write positions with fully known operand type)  and the new declaration operator DATA(...).

Let's look at some examples.

Declaration of a lhs-variable for a simple assignment

Before 7.40

DATA text TYPE string.
text = `...`.

With 7.40

DATA(text) = `...`.

Declaration of table work areas

Before 7.40

DATA wa like LINE OF itab.
LOOP AT itab INTO wa.  
  ...
ENDLOOP.

With 7.40

LOOP AT itab INTO DATA(wa).  
  ...
ENDLOOP.

Declaration of a helper variable

Before 7.40

DATA cnt TYPE i.
FIND ... IN ... MATCH COUNT cnt.

With 7.40

FIND ... IN ... MATCH COUNT DATA(cnt).

Declaration of a result

Before 7.40

DATA xml TYPE xstring.
CALL TRANSFORMATION ... RESULT XML xml.

With 7.40

CALL TRANSFORMATION ... RESULT XML DATA(xml).

Declaration of actual parameters

Before 7.40

DATA a1 TYPE ...

DATA a2 TYPE ...

oref->meth( IMPORTING p1 = a1  IMPORTING p2 = a2  ... )

     

With 7.40

oref->meth( IMPORTING p1 = DATA(a1)  IMPORTING p2 = DATA(a2)  ... ).

Declaration of reference variables for factory methods

Before 7.40

DATA ixml           TYPE REF TO if_ixml.
DATA stream_factory TYPE REF TO if_ixml_stream_factory.
DATA document       TYPE REF TO if_ixml_document.

ixml           = cl_ixml=>create( ).
stream_factory = ixml->create_stream_factory( ).
document       = ixml->create_document( ).

With 7.40

DATA(ixml)           = cl_ixml=>create( ).
DATA(stream_factory) = ixml->create_stream_factory( ).
DATA(document)       = ixml->create_document( ).

Field Symbols

For field symbols there is the new declaration operator FIELD-SYMBOL(...) that you can use at exactly three declaration positions.

ASSIGN ... TO FIELD-SYMBOL(<fs>).

LOOP AT itab ASSIGNING FIELD-SYMBOL(<line>).
...
ENDLOOP.

READ TABLE itab ASSIGNING FIELD-SYMBOL(<line>) ...

I guess it is clear to you what happens here.

Outlook

In my upcoming blogs I will make use of inline declarations when introducing other new features. Be prepared for code like this:

TYPES t_itab TYPE TABLE OF i WITH EMPTY KEY.

DATA(itab) = VALUE t_itab( ( 1 ) ( 2 ) ( 3 ) ).

Yes, this is ABAP 7.40 ...

1.2 Constructor expression CORRESPONDING

MOVE-CORRESPONDING FOR Internal Tables

You can use MOVE-CORRESPONDING not only for structures but also for internal tables now. Components of the same name are are assigned row by row. New additions EXPANDING NESTED TABLES and KEEPING TARGET LINES allow to resolve tabular components of structures and to append lines instead of overwriting existing lines.

Example

MOVE-CORRESPONDING itab1 TO itab2 EXPANDING NESTED TABLES

KEEPING TARGET LINES.

More About

ABAP Keyword Documentation

Expressions and Functions

LET Expressions in Constructor Expressions

New LET expressions as sub expressions of constructor expressions allow you to define local variables or field symbols as auxiliary fields of constructor expressions.

Example

LET expression in a table construction using the VALUE operator.

cl_demo_output=>new( )->write(

  VALUE text( LET it = `be` IN

                 ( |To { it } is to do|          )

                 ( |To { it }, or not to { it }| )

                 ( |To do is to { it }|          )

                 ( |Do { it } do { it } do|      ) ) )->display( ).

More About

ABAP Keyword Documentation

CORRESPONDING Operator

The new constructor operator CORRESPONDING allows to execute  a "MOVE-CORRESPONDING" for structures or internal tables at operand positions. Besides assigning components of the same name you can define your own mapping rules.

Example

struct2 = CORRESPONDING #( struct1 MAPPING b4 = a3 ).

More About

ABAP Keyword Documentation

Table Comprehensions

A new FOR sub expression for constructor expressions with operators NEW and VALUE allows to read existing internal tables and to construct new tabular contents from the lines read.

Example

Construction of an internal table itab2 from lines and columns of an internal table itab1. You can of course also use the CORRESPONDING operator to construct the lines.

DATA(itab2) = VALUE t_itab2( FOR wa IN itab1 WHERE ( col1 < 30 )

( col1 = wa-col2 col2 = wa-col3 ) ).

More About

ABAP Keyword Documentation

Meshes

This one is a little bit tricky. Meshes are special structures whose components are internal tables, which can be linked to each other using associations. Associations are evaluated by specifying mesh paths in suitable expressions and statements .The full power of meshes will become more clear in the moment when associations will be supported by Open SQL for database views (CDS views, see below) in the future.

Example

A mesh flights is declared from a mesh type T_FLIGHTS. In T_FLIGHTS you have tabular components as so called mesh nodes that are linked by associations. A structured data object root  is constructed to serve as the start line for the following LOOP over a mesh path. The results are lines from SFLIGHT that are found by following the mesh path evaluating the associations between its mesh nodes.

TYPES: t_scarr   TYPE SORTED TABLE OF scarr WITH UNIQUE KEY carrid,
       t_spfli   TYPE SORTED TABLE OF spfli WITH UNIQUE KEY carrid connid,
       t_sflight TYPE SORTED TABLE OF sflight WITH UNIQUE KEY carrid connid fldate.


TYPES: BEGIN OF MESH t_flights, 
           scarr  TYPE t_scarr ASSOCIATION to_spfli TO spfli ON carrid = carrid

USING KEY primary_key,
           spfli TYPE t_spfli ASSOCIATION to_sflight TO sflight ON carrid = carrid                                    
                                         AND connid = connid  USING KEY primary_key,
          sflight TYPE t_sflight,
         END OF MESH t_flights.                  

        

DATA: flights TYPE t_flights. 

... 

DATA(root) = flights-scarr[ carrname = 'United Airlines' ].

LOOP AT
  flights-scarr\to_spfli[ root ]\to_sflight[ ]
    INTO DATA(wa).
  ...
ENDLOOP.

More About

ABAP Keyword Documentation

Open SQL(New Syntax)

From 7.40, SP05

l   Lists in Open SQL statements can and should be separated by a comma.

l   Host variables in Open SQL statements can and should be escaped by a @.

You will be able to use other new functions that are based on a new SQL parser in the ABAP kernel.

Example

SELECT carrid, connid, fldate FROM sflight 

   INTO CORRESPONDING FIELDS OF TABLE @sflight_tab

      WHERE carrid = @carrier AND  connid = @connection      

         ORDER BY carrid, connid.

More About

ABAP Keyword Documentation

SQL Expressions

You can use SQL expressions in a column list behind SELECT. The result of such an expression is calculated on the database (code push down!) and written into the respective columns of the result set. Operands can be data base columns or host variables.

Possible expressions are

l   elementary values

l   arithmetic expressions

l   arithmetic functions abs, ceil, floor, div, mod

l   castings with cast

l   string concatenations with &&

l   coalesce

l   case

Expressions can be mixed and proritized with parentheses.

Examples

SELECT id, num1, num2, cast( num1 AS fltp ) / cast( num2 AS fltp ) AS ratio,

       div( num1, num2 ) AS div, mod( num1, num2 ) AS mod,

       @offset + abs( num1 - num2 ) AS sum

       FROM demo_expressions INTO CORRESPONDING FIELDS OF TABLE @results

       ORDER BY SUM DESCENDING.

SELECT id, CASE char1

             WHEN 'aaaaa' THEN ( char1 && char2 )

             WHEN 'xxxxx' THEN ( char2 && char1 )

             ELSE @else

           END AS text

       FROM demo_expressions INTO CORRESPONDING FIELDS OF TABLE @results.

 More About

ABAP Keyword Documentation

CDS Views     

The new CDS (Core Data Services) enable you to define Views of the ABAP Dictionary with a DDL (Data Definition Language) in ADT. This DDL encompasses the DDL of SQL and enhances it with the possibility to define annotations and associations. CDS-Views in the Dictionary are not bound to a specific database platform. They provide another way of database independent code push down .You can access CDS-Views with Open SQL.

Example

Definitiion of a simple view based on only one database table. Of course, you can join as you like ...

@AbapCatalog.sqlViewName: 'BPA_VW'

define view business_partner as

     select from snwd_bpa { key bp_id, company_name, bp_role }

You can access the view in ABAP programs e.g. as follows:

SELECT * FROM business_partner INTO TABLE itab ...

More About

ABAP Keyword Documentation

See also the dedicated blog:

New Data Modeling Features in SAP NW ABAP 7.4 SP5 | SAP Blogs

ABAP Managed Database Procedures

ABAP Managed Database Procedures (AMDP) are a class based framework for maintaining and calling stored procedures as so called AMDP procedures from ABAP. An AMDP procedure is implemented in its native DB-language in an AMDP method of an AMDP class. Currently, the only database that supports AMDP is SAP's HANA database.

An AMDP class must implement a specific tag interface. Currently, there is only one namely IF_AMDP_MARKER_HDB. An AMDP class can be maintained in ADT only. An AMDP method looks from its declaration like a normal ABAP method and can be used like that. Only when implementing the method, you denote the database and the DB language. You also must denote the entities to be used. The following is an example for using the language SQLScript of the HANA database:

CLASS cl_demo_amdp DEFINITION PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_amdp_marker_hdb .
    METHODS increase_price
      IMPORTING
        VALUE(clnt) TYPE sy-mandt
        VALUE(inc)  TYPE sflight-price .
ENDCLASS.

CLASS cl_demo_amdp IMPLEMENTATION.
  METHOD increase_price BY DATABASE PROCEDURE FOR HDB
                        LANGUAGE SQLSCRIPT
                        USING sflight.
    update sflight set price = price + :inc
                   where mandt = :clnt;
  ENDMETHOD.
ENDCLASS.

Please note that a simple example as the above one is only to show the syntax. Implementing functionality in an AMDP method means the usage of Native SQL. And for the usage of Native SQL the same holds as before: Stay open as long as possible! It makes absolutely no sense to push down statements to the database that you can also express in Open SQL, because those are pushed down by the Native SQL Interface in exactly the same way or even better!

You use AMDP as a convenient way of programming and handling Native SQL in ABAP only if you really gain something from it. And for that, simple examples are not too easy to find, especially since Open SQL is empowered by new features now (see above).

Example

A simple example of a database functionality that is not readily available in Open SQL is the predefined currency conversion of HANA. An AMDP method that uses this conversion might look as follows:

METHOD convert BY DATABASE PROCEDURE FOR HDB

                           LANGUAGE SQLSCRIPT

                           USING demo_prices.

  PRICES = select * from DEMO_PRICES;

  PRICES =

    CE_CONVERSION (

      :PRICES,

      [ family             = 'currency',

        method             = 'ERP',

        steps              = 'shift,convert,shift_back',

        target_unit        = :TO_CURRENCY,

        client             = :MANDT,

        source_unit_column = "CURRENCY",

        reference_date     = :DATE,

        output_unit_column = "CURRENCY",

        error_handling     = 'keep unconverted' ],

      [ amount AS amount ] );

   replace DEMO_PRICES select * from :PRICES;

ENDMETHOD.

An alternative implementation for databases other than the SAP HANA database must then be provided, e.g. as follows:

METHOD abap_convert.

  DATA prices TYPE STANDARD TABLE OF demo_prices.

  SELECT *

         FROM demo_prices

         INTO TABLE prices.

  LOOP AT prices ASSIGNING FIELD-SYMBOL(<price>).

    CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'

      EXPORTING

        client           = mandt

        date             = date

        foreign_amount   = <price>-amount

        foreign_currency = <price>-currency

        local_currency   = to_currency

      IMPORTING

        local_amount     = <price>-amount

      EXCEPTIONS

        OTHERS           = 4.

    IF sy-subrc <> 0.

      CONTINUE.

    ENDIF.

    <price>-currency = to_currency.

  ENDLOOP.

  MODIFY demo_prices FROM table prices.

ENDMETHOD.

ABAP code calling these methods could look as follows:

IF cl_db_sys=>is_in_memory_db = abap_true.

    NEW cl_demo_sqlscript_curr_conv(

      )->convert(

           EXPORTING

             to_currency      = to_upper( currency )

             mandt            = sy-mandt

             date             = sy-datlo ).

ELSE.

   NEW cl_demo_sqlscript_curr_conv(

     )->abap_convert(

          EXPORTING

            to_currency      = to_upper( currency )

            mandt            = sy-mandt

            date             = sy-datlo ).

ENDIF.

More About

ABAP Keyword Documentation

Further Information

For a complete overview of all ABAP Language News for Release 7.40, SP05 see:

l   ABAP Keyword Documentation

l   ABAP-Schlüsselwortdokumentation

If you are especially interested in ABAP for HANA, also have a look at Jens Weiler's blog:

New ABAP for HANA features in SAP NW 7.4 SP5.

The ABAP Keyword Documentation summarizes the respective ABAP language features under ABAP Keyword Documentation.

Outlook

The next bundled release will be 7.40, SP08 with kernel release 742. Expect some nice features from that one too. I'm already very busy documenting them.

1.3 CL_ABAP_CORRESPONDING

In ABAP, as a rule, the name is not always the game.

But as you all know there is a prominent exception to that rule: All the syntax forms involving CORRESPONDING for assigning structure components that (by chance) have the same name.

l   Before ABAP 7.40, these were mainly MOVE-CORRESPONDING for the components of structures, the CORRESPONDING addition to Open SQL's SELECT, and some obsolete calculation statements.

l   With ABAP 7.40 MOVE-CORRESPONDING was enabled to handle structured internal tables and a new constructor operator CORRESPONDING was introduced that allows an explicit mapping of structure components with different names.

What was still missing?  A dynamic mapping capability! And this was introduced with ABAP 7.50.

The new system class CL_ABAP_CORRESPONDING allows you to assign components of structures or internal tables with dynamically specified mapping rules.

The mapping rules are created in a mapping table that is passed to a mapping object, e.g. as follows:

DATA(mapper) =

  cl_abap_corresponding=>create(

    source      = struct1

    destination = struct2

    mapping     = VALUE cl_abap_corresponding=>mapping_table(

     ( level   = 0

       kind    = cl_abap_coresponding=>mapping_component

       srcname = '...'

       dstname = '...' )

     ( level   = 0

       kind    = cl_abap_coresponding=>mapping_component

       srcname = '...'

       dstname = '...' )

     ( level   = 0

       kind    = cl_abap_coresponding=>mapping_component

       srcname = '...'

       dstname = '...' ) ) ).

This is a simple example, where all structure components are on top level (0) and where all components are to be mapped (kind = cl_abap_coresponding=>mapping_component). More complicated forms involve nested structures and exclusions. With srcname and dstname the component names  can be specified dynamically. The table setup is similar to the mapping-clause of the CORRESPONDING operator.

After creating the mapping object, all you have to do is to execute the assignment as follows:

mapper->execute( EXPORTING source      = struct1

                        CHANGING  destination = struct2 ).

You can do that again and again for all structures or internal tables that have the same types as those used for creating the mapping object.

Not much more to say about that. For details and more examples see CL_ABAP_CORRESPONDING - System Class.

Outlook

Up to now, only the basic form of the CORRESPONDING operator is mirrored in CL_ABAP_CORRESPONDING. But a variant for using a lookup table is already in the queue.

1.4 Constructor expression VALUE

With Release 7.40 ABAP supports so called constructor operators. Constructor operators are used in constructor expressions to create a result that can be used at operand positions. The syntax for constructor expressions is ... operator type( ... ) ...

operator is a constructor operator. type is either the explicit name of a data type or the character #. With # the data type can be dreived from the operand position if the operand type is statically known. Inside the parentheses specific parameters can be specified.

Value Operator VALUE

Value operator VALUE is a constructor operator that constructs a value for the type specified with type.

l   ... VALUE dtype|#( ) ...

constructs an initial value for any data type.

l   ... VALUE dtype|#( comp1 = a1 comp2 = a2 ... ) ...

      constructs a structure where for each component a value can be assigned.

l        ... VALUE dtype|#( ( ... ) ( ... ) ... ) ...

constructs an internal table, where for each line a value can be assigned. Inside inner parentheses you can use the syntax for structures but not the syntax for table lines directly. But you can nest VALUE operators.

Note that you cannot construct elementary values (which is possible with instantiation operator NEW) - simply because there is no need for it.

For internal tables with a structured line type there is a short form that allows you to fill columns with the same value in subsequent lines

VALUE dtype|#( col1 = dobj11 ... ( col2 = dobj12 col3 = dobj13 ... )
                                 ( col2 = dobj22 col3 = dobj23 ... )
                                   ...
               col1 = dobj31 col2 = dobj32 ... ( col3 = dobj33 ... )
                                               ( col3 = dobj43 ... )
               ... ).

Example for initial values

Why would you like to construct an initial value anyhow? Well, you can pass an initial actual parameter to a structured or tabular formal parameter without the need of an initial helper variable now.

CLASS c1 DEFINITION.
  PUBLIC SECTION.
    TYPES: BEGIN OF t_struct,
             col1 TYPE i,
             col2 TYPE i,
           END OF t_struct.
    CLASS-METHODS m1 IMPORTING p TYPE t_struct.
ENDCLASS.

CLASS c1 IMPLEMENTATION.
  METHOD m1.
    ...
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.

c1=>m1( VALUE #( ) ).

Example for structures

Three different ways to construct the same nested structure:

TYPES:  BEGIN OF t_col2,
           col1 TYPE i,
           col2 TYPE i,
        END OF t_col2.

TYPES: BEGIN OF t_struct,
         col1 TYPE i,
         col2 TYPE t_col2,
       END OF t_struct.

DATA: struct TYPE t_struct,
      col2 TYPE t_col2.

"1
struct = VALUE t_struct( col1 = 1
                         col2-col1 = 1
                         col2-col2 = 2 ).

"2

col2   = VALUE   t_col2( col1 = 1
                         col2 = 2 ).
struct = VALUE t_struct( col1 = 1
                         col2 = col2 ).

"3

struct = VALUE t_struct( col1 = 1
                         col2 = VALUE #( col1 = 1
                                         col2 = 2 ) ).

Examples for internal tables

Elementary line type:

TYPES t_itab TYPE TABLE OF i WITH EMPTY KEY.

DATA itab TYPE t_itab.

itab = VALUE #( ( ) ( 1 ) ( 2 ) ).

Structured line type (RANGES table):

DATA itab TYPE RANGE OF i.

itab = VALUE #( sign = 'I'  option = 'BT' ( low = 1  high = 10 )
                                          ( low = 21 high = 30 )
                                          ( low = 41 high = 50 )
                            option = 'GE' ( low = 61 )  ).

Other expressions in VALUE operator

Of course, the arguments of VALUE can be expressions or function calls:

TYPES t_date_tab TYPE TABLE OF string  WITH EMPTY KEY.

DATA(date_tab) = VALUE t_date_tab(
  ( |{ CONV d( sy-datlo - 1 ) DATE = ENVIRONMENT }| )
  ( |{         sy-datlo       DATE = ENVIRONMENT }| )
  ( |{ CONV d( sy-datlo + 1 ) DATE = ENVIRONMENT }| ) ).

So you can do a lot of crazy things now, but be aware of obfuscation  ...

1.5 CREATE DATA / CREATE OBJECT (Constructor expression NEW)

With Release 7.40 ABAP supports so called constructor operators. Constructor operators are used in constructor expressions to create a result that can be used at operand positions. The syntax for constructor expressions is  ... operator type( ... ) ...

Operator is a constructor operator. type is either the explicit name of a data type or the character #. With # the data type can be dreived from the operand position if the operand type is statically known. Inside the parentheses specific parameters can be specified.

Instantiation Operator NEW

The instantiation operator NEW is a constructor operator that creates an object (anonymous data object or instance of a class).

l     ... NEW dtype( value ) ...

Creates an anonymous data object of data type dtype and passes a value to the created object. The value construction capabilities cover structures and internal tables (same as those of the VALUE operator).

l   ... NEW class( p1 = a1 p2 = a2 ... ) ...

Creates an instance of class class and passes parameters to the instance constructor.

l   ... NEW #( ... ) ...

Creates either an anonymous data object or an instance of a class depending on the operand type

You can write a component selector -> directly behind NEW type( ... ).

Example for data objects

Before Release 7.40

FIELD-SYMBOLS <fS> TYPE data.

DATA dref TYPE REF TO data.

CREATE DATA dref TYPE i.

ASSIGN dref->* TO <fs>.

<fs> = 555.

With Release 7.40

DATA dref TYPE REF TO data.

dref = NEW i( 555 ).

Example for instances of classes

Before Release 7.40

DATA oref TYPE REF TO class.

CREATE OBJECT oref EXPORTING ...

With Release 7.40

Either

DATA oref TYPE REF TO class.     ""方法1

oref = NEW #( ... ).

or with an inline declaration

DATA(oref) = NEW class( ... ).    ""方法2

This is the kind of statement NEW is made for. You can also pass it to methods expecting references.

And one for the road ...

TYPES: BEGIN OF t_struct1,
         col1 TYPE i,
         col2 TYPE i,
       END OF t_struct1,
       BEGIN OF t_struct2,
         col1 TYPE i,
         col2 TYPE t_struct1,
         col3 TYPE TABLE OF t_struct1 WITH EMPTY KEY,
       END OF t_struct2,
       t_itab TYPE TABLE OF t_struct2 WITH EMPTY KEY.

DATA(dref) =
  NEW t_itab( ( col1 = 1
                col2-col1 = 1
                col2-col2 = 2
                col3 = VALUE #( ( col1 = 1 col2 = 2 )
                                ( col1 = 3 col2 = 4 ) ) )
              ( col1 = 2
                col2-col1 = 2
                col2-col2 = 4
                col3 = VALUE #( ( col1 = 2 col2 = 4 )
                                ( col1 = 6 col2 = 8 ) ) ) ).

1.6 GET REFERENCE OF (Constructor expression REF)

With Release 7.40 ABAP supports so called constructor operators. Constructor operators are used in constructor expressions to create a result that can be used at operand positions. The syntax for constructor expressions is ... operator type( ... ) ...

Operator is a constructor operator. type is either the explicit name of a data type or the character #. With # the data type can be dreived from the operand position if the operand type is statically known. Inside the parentheses specific parameters can be specified.

Reference Operator REF

This is a short one but can become handy for some purposes.

The reference operator REF constructs a data reference at operand positions.

... REF dtype|#( dobj ) ...

results in a data reference pointing to dobj with the static type specified by type. With other words, REF is the short form for GET REFERENCE OF dobj INTO.

Where do you need it? Always when you have to pass data references to somewhere and don't want to create helper variables for that purpose.

Example for dynamic method call

Using REF for filling the parameter table:

CLASS class DEFINITION.
  PUBLIC SECTION.
    METHODS meth
      IMPORTING p1 TYPE string
                p2 TYPE i.
ENDCLASS.

CLASS class IMPLEMENTATION.
  METHOD meth.
    ...
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.

  DATA(arg1) = `blah`.
  DATA(arg2) = 111.

 DATA(ptab) = VALUE abap_parmbind_tab(
    ( name = 'P1' kind = cl_abap_objectdescr=>exporting value = REF #( arg1 ) )
    ( name = 'P2' kind = cl_abap_objectdescr=>exporting value = REF #( arg2 ) ) ).

  DATA(oref) = NEW class( ).
  CALL METHOD oref->('METH')
    PARAMETER-TABLE ptab.

Example for ADBC

Parameter binding made easy!

DATA(key) = 'LH'.

DATA(sql) = NEW cl_sql_statement( ).

sql->set_param( REF #( sy-mandt ) ).
sql->set_param( REF #( key ) ).

DATA(result) = sql->execute_query(
      `SELECT carrname ` &&
      `FROM scarr ` &&
      `WHERE mandt  = ? AND carrid = ?` ).

DATA name TYPE scarr-carrname.
result->set_param( REF #( name ) ).
result->next( ).

Believe it or not: name contains the carrier name now.

1.7 helper variable for type conversion (Constructor expression CONV and CAST)

With Release 7.40 ABAP supports so called constructor operators. Constructor operators are used in constructor expressions to create a result that can be used at operand positions. The syntax for constructor expressions is ... operator type( ... ) ...

Operator is a constructor operator. type is either the explicit name of a data type or the character #. With # the data type can be dreived from the operand position if the operand type is statically known. Inside the parentheses specific parameters can be specified.

Conversion Operator CONV

The conversion operator CONV is a constructor operator that converts a value into the type specified in type.

... CONV dtype|#( ... ) ...

You use CONV where you needed helper variables before in order to achieve a requested data type.

Example for parameter passing

Method cl_abap_codepage=>convert_to expects a string but you want to convert a text field.

Before Release 7.40

DATA text TYPE c LENGTH 255.

DATA helper TYPE string.

DATA xstr   TYPE xstring.

helper = text.

xstr = cl_abap_codepage=>convert_to( source = helper ).

With Release 7.40

DATA text TYPE c LENGTH 255.

DATA(xstr) = cl_abap_codepage=>convert_to( source = CONV string( text ) ).

In such cases it is even simpler to write

DATA text TYPE c LENGTH 255.

DATA(xstr) = cl_abap_codepage=>convert_to( source = CONV #( text ) ).

Example for influencing a calculation

IF 1 / 3 > 0.

  ...

ENDIF. 

is false, but

IF CONV decfloat34( 1 / 3 ) > 0.

  ...

ENDIF.

is true!

Example for influencing a comparison

The infamous

IF ' ' = ` `.

  ...

ENDIF.

is false. But

IF ' ' = CONV char1( ` ` ).

  ...

ENDIF.

is true!

Casting Operator CAST

The casting operator CAST is a constructor operator that executes an up or down cast for reference variables with the type specified in type.

... CAST dtype|class|interface|#( ... ) ...

l   You use CAST for a down cast where you needed helper variables before in order to cast with ?= to a requested reference type

l   You use CAST for an up cast, e,g, with an inline declaration, in order to construct a more general type.

You can write a component selector -> directly behind CAST type( ... ).

Example from RTTI

Common example where a down cast is needed .

Before Release 7.40

DATA structdescr TYPE REF TO cl_abap_structdescr.
structdescr ?= cl_abap_typedescr=>describe_by_name( 'T100' ).

DATA components  TYPE abap_compdescr_tab.

components = structdescr->components.

With Release 7.40

DATA(components) = CAST cl_abap_structdescr(

  cl_abap_typedescr=>describe_by_name( 'T100' ) )->components.

Example with up cast

The static type of the reference variable iref declared inline should be the interface not the class.

INTERFACE if.
  ...
ENDINTERFACE.


CLASS cl DEFINITION CREATE PRIVATE.
  PUBLIC SECTION.
    INTERFACES if.
    CLASS-METHODS factory RETURNING value(ref) TYPE REF TO cl.
    ...
ENDCLASS.

CLASS cl IMPLEMENTATION.
  METHOD factory.
    ref = NEW #( ).
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  DATA(iref) = CAST if( cl=>factory( ) ).

Example with data objects

A constructor expression with CAST followed by -> is an LHS-expression, you can assign values to it.

TYPES: BEGIN OF t_struc,
        col1 TYPE i,
        col2 TYPE i,
       END OF t_struc.

DATA dref  TYPE REF TO data.
DATA struc TYPE t_struc.

dref = NEW t_struc( ).

CAST t_struc( dref )->col1 = struc-col1.

1.8 Constructor expressions SWITCH or COND

Conditional operators COND and SWITCH

Last but not least, two nice ones, the conditionals COND and SWITCH.

l   ... COND dtype|#( WHEN log_exp1 THEN result1
                  [ WHEN log_exp2 THEN result2 ]
                  ...
                  [ ELSE resultn ] ) ...

constructs a result of the specified type that depends on logical expressions.

l   ... SWITCH dtype|#( operand
                    WHEN const1 THEN result1
                  [ WHEN const2 THEN result2 ]
                  ...
                  [ ELSE resultn ] ) ...

constructs a result of the specified type that depends on a case differentiation.

With other words: IF and CASE as expressions in operand positions!

Example for COND

DATA(time) =

  COND string(

    WHEN sy-timlo < '120000' THEN

      |{ sy-timlo TIME = ISO } AM|

    WHEN sy-timlo > '120000' THEN

      |{ CONV t( sy-timlo - 12 * 3600 )

         TIME = ISO } PM|

    WHEN sy-timlo = '120000' THEN

      |High Noon|

    ELSE

      THROW cx_cant_be( ) ).

Note the THROW. Now you can raise and  throw exceptions ...

Example for SWITCH

CLASS cx_langu_not_supported DEFINITION INHERITING FROM cx_static_check.
ENDCLASS.

CLASS class DEFINITION.
  PUBLIC SECTION.
    METHODS meth IMPORTING iso_langu   TYPE string
                 RETURNING VALUE(text) TYPE string.
ENDCLASS.

CLASS class IMPLEMENTATION.
  METHOD meth.
    ...
  ENDMETHOD.
ENDCLASS.

...

  DATA(text) =
    NEW class(
      )->meth(
          SWITCH #( sy-langu
                    WHEN 'D' THEN `DE`
                    WHEN 'E' THEN `EN`
                    ELSE THROW cx_langu_not_supported( ) ) ).

1.9 READ TABLE Syntax

Table expressions with the syntax

... itab[ ... ] ...

are a new way for accessing table lines in operand positions. You can view a table expression simply as a short form of a READ TABLE statement. The result of a table expression is a single table line. All you have to know is the syntax that does the same as a given READ TABLE statement.

If a table line is not found, the exception CX_SY_ITAB_LINE_NOT_FOUND is raised. No sy-subrc from expressions, of course.

The operand positions where table expressions can be used are read positions but also some write positions where you can modify the resulting table line. Table expressions are LHS-expressions!

Index access using the primary index

The assignment of the table expression

wa = itab[ idx ].

does the same as

READ TABLE itab INDEX idx INTO wa.

Index access using a secondary index

The assignment of the table expression 

wa = itab[ KEY key INDEX idx ].

does the same as

READ TABLE itab INDEX idx USING KEY key INTO wa.

Access using a free key

The assignment of the table expression

wa = itab[ col1 = ... col2 = ... ] 

does the same as

READ TABLE itab WITH KEY col1 = ... col2 = ...  INTO wa.

Key access using a table key

The assignment of the table expressions

wa = itab[ KEY key col1 = ... col2 = ... ].

wa = itab[ KEY key COMPONENTS col1 = ... col2 = ... ].

do the same as

READ TABLE itab WITH TABLE KEY key COMPONENTS col1 = ... col2 = ...  INTO wa.

Influencing the result

With READ TABLE you can read into a work are, assign a field symbol or set a reference. The result of table expressions can be influenced accordingly.

l   ... itab[ ... ] ...

as a rule works like READ TABLE ... ASSIGNING ... . The temporary result of the expression is a field symbol.

l   ... VALUE type( itab[ ... ] ) ...

Forces the expression to work like READ TABLE ... INTO ... . The temporary result of the expression is a data object.

l   ... REF type( itab[ ... ] ) ...

forces the expression to work like READ TABLE ... REFERENCE INTO ... . The temporary result of the expression is a reference variable.

While the third case is important, if you want to work with references to table lines, the results of the other two cases are normally transparent to the user. You don't have to care how the intermediate result is represented internally. But there are some performance considerations. The same rules when to use what hold for table expressions as for READ TABLE. Therefore, the syntax checker might kindly remind you from time to time to place the VALUE operator in front of a table expression (or to leave it away ...).

Chainings

The following chainings with table expressions are possible:

l   ... itab[ ...]-comp

l   ... struct-comp[ ... ] ...

l   ... itab[ ... ][ ... ] ...

and combinations of those

Fun example

TYPES:
  BEGIN OF struc1,
    col1 TYPE i,
    col2 TYPE i,
  END OF struc1,
  itab1 TYPE TABLE OF struc1 WITH EMPTY KEY,
  itab2 TYPE TABLE OF itab1 WITH EMPTY KEY,
  BEGIN OF struc2,
    col1 TYPE i,
    col2 TYPE itab2,
  END OF struc2,
  itab3 TYPE TABLE OF struc2 WITH EMPTY KEY.

DATA(itab) = VALUE itab3(
   ( col1 = 1  col2 = VALUE itab2(
                       ( VALUE itab1(
                           ( col1 = 2 col2 = 3 )
                           ( col1 = 4 col2 = 5 ) ) )
                       ( VALUE itab1(
                           ( col1 = 6 col2 = 7 )
                           ( col1 = 8 col2 = 9 ) ) ) ) )
   ( col1 = 10 col2 = VALUE itab2(
                       ( VALUE itab1(
                           ( col1 = 11 col2 = 12 )
                           ( col1 = 13 col2 = 14 ) ) )
                       ( VALUE itab1(
                           ( col1 = 15 col2 = 16 )
                           ( col1 = 17 col2 = 18 ) ) ) ) ) ).

* Reading the column with value 13 with READ TABLE statements

READ TABLE itab     INTO DATA(wa1) INDEX 2.
READ TABLE wa1-col2 INTO DATA(wa2) INDEX 1.
READ TABLE wa2      INTO DATA(wa3) INDEX 2.
DATA(num1) = wa3-col1.

* Reading the column with value 13 with chained table expressions

DATA(num2) = itab[ 2 ]-col2[ 1 ][ 2 ]-col1.

Unbelievable, is this still ABAP?

1.10 More for Internal Tables

FILTER expressions

The new FILTER operator enables two kinds of filtering an internal table.

FILTER with single values

In this variant, you simply extract the lines from an internal table into a tabular result, that fulfill a simple value condition.

DATA(extract) =

  FILTER #( spfli_tab USING KEY carr_city

              WHERE carrid   = CONV #( to_upper( carrid ) ) AND

                    cityfrom = CONV #( to_upper( cityfrom ) ) ).

As a prerequisite, the filtered table must have a sorted or a hash key (primary or secondary), that is evaluated behind WHERE. You can also extract the lines that do not fulfill the WHERE condition by using an EXCEPT addition.  Of course, you can achieve the same result by using a FOR inside a VALUE ror a REDUCE expression but FILTER allows you to write it shorter and it should be faster.

FILTER with filter table

In this variant, you compare the lines of one table with the contents of another table, the filter table, and you extract those lines, where at least one match is found (say hello to FOR ALL ENTRIES).

TYPES: BEGIN OF filter,
         cityfrom TYPE spfli-cityfrom,
         cityto   TYPE spfli-cityto,
       END OF filter,
       filter_tab TYPE HASHED TABLE OF filter
                  WITH UNIQUE KEY cityfrom cityto.

DATA(filter_tab) = ...        


DATA(extract) =
  FILTER #( spfli_tab IN filter_tab
              WHERE cityfrom = cityfrom  AND cityto = cityto ).

Here, the filter table - that can be specified also as a functional method call - must have a sorted or a hashed key (primary or secondary) that is evaluated.

Standard Value for Table Expressions

Table expressions ITAB[ ...] cannot support SY-SUBRC. Up to now, an exception was raised anytime if a table line specified in the square brackets could not be found. Not everybody liked this behavior.

As a workaround, you can place a table expression inside a VALUE or REF expression, that contains a OPTIONAL or DEFAULT addition. If a line is not found, the OPTIONAL addition returns an initiial line while the DEFAULT addition returns a given value, that can be specified as an expression, especially another table expression.

TYPES:

  BEGIN OF line,

    id    TYPE i,

    value TYPE string,

  END OF line,

  itab TYPE SORTED TABLE OF line WITH UNIQUE KEY id.

DATA(def) = VALUE line( id = 0 value = `not found` ).

...

DATA(result) = VALUE #( itab[ id = ... ] DEFAULT def ).

1.11 New Internal Table Functions (line_exists,line_index)

The most important news for internal tables are the Table expressions that allow the same single line access as the READ TABLE statement. But soon after those were available, the question arised "What about the system fields?" .

We all use the well known patterns

READ TABLE ... TRANSPORTING NO FIELDS.

IF sy-subrc = 0.

  ...

ENDIF.

READ TABLE ... TRANSPORTING NO FIELDS.

DATA(idx) = sy-tabix.

...

Since an expression should be free of side effects it especially should not influence any system field. So what about these patterns?

The answer comes in form of two new built-in functions.

Line existence

A new predicate function that returns true if a line exists:

IF line_exists( itab[ ... ] ).

...

ENDIF.

Where itab[ ... ] is a table expression that works here like READ TABLE ... TRANSPORTING NO FIELDS.

(Note that we have already some other predicate functions in ABAP: matches and the contains-variants in the context of string processing).

Line index

A new table function that returns the number of the line in the index used (primary or secondary index):

DATA(idx)= line_index( itab[ ... ] ).

Where itab[ ... ] is a table expression that works here like READ TABLE ... TRANSPORTING NO FIELDS. If no line is found, the value 0 is returned. For Hash-Tables or hashed secondary keys, always the value -1 is returned.

(Note that this is the second table function in ABAP. The other one is lines).

1.12 Internal Table With Empty KEY

Internal tables with empty key ?

Each internal table has a primary key. Even standard tables have a table key. Key access is not optimized for standard tables but the primary key plays a role if you use SORT itab without explicit sort fields or for table statements with FROM wa.

How do you declare a table? Pragmatically, often as follows:

DATA: BEGIN OF struct,
        col1 TYPE i,
        col2 TYPE i,
      END OF struct.
DATA itab LIKE TABLE OF struct.

Looks good. But now do the following (in all releases):

DATA: BEGIN OF struct,
        col1 TYPE i,
        col2 TYPE i,
      END OF struct.
DATA itab LIKE TABLE OF struct.

struct-col1 = 3. struct-col2 = 1.
APPEND struct TO itab.
struct-col1 = 2. struct-col2 = 2.
APPEND struct TO itab.
struct-col1 = 1. struct-col2 = 1.
APPEND struct TO itab.

DATA jtab LIKE itab.
jtab = itab.

SORT jtab.

IF jtab = itab.
  MESSAGE 'nop!' TYPE 'I'.
ENDIF.

SORT itab does nothing (some people tend to open a ticket now)! Why? Because itab has an empty key!

When you declare a standard table data object without specifying the primary key, the default key is taken. The default key consists of all character and byte like fields of the table structure. If the structure contains only numeric fields, duh! The same would have happened if you declared the DEFAULT KEY explicitly. But note that an empty key is not possible for sorted and hashed tables.

If you declare standard table types, the situation can become even more confusing.

TYPES: BEGIN OF struct,
         col1 TYPE i,
         col2 TYPE i,
       END OF struct.
TYPES itab TYPE STANDARD TABLE OF struct.

Here it is not the default key that is declared implicitly, but the table type is generic regarding its key.

To prove that, write

FIELD-SYMBOLS <itab> TYPE itab.
DATA jtab like <itab>.

You get a syntax error because the field symbol is generic and cannot be used behind LIKE.To get rid of the error you could add WITH DEFAULT KEY to the TYPES itab statement - and have an empty key again!

So, if you want to work with a table key it is always a good idea to explicitly define the key fields. Using the default key - either by chance or explicitly - is almost always critical because:

l   Identifying the key fields only by their data type often leads to unexpected behavior in sorts and other access types.

l   The possibility of an empty standard key in standard tables can cause unexpected behavior.

l   The standard key can contain many key fields, leading to performance problems.

l   In sorted tables and hashed tables the key fields are read-only, which can cause unexpected runtime errors.

But what if you do not care about the key at all? If you simply want to use an internal table as an array that does not depend on key values ?

Before release 7.40, some unexpected behavior could arise for such arrays. With release 7.40 you can specify  ... WITH EMPTY KEY.

When declaring standard table objects or table types. If you don't care about the key, this new addition is always recommended in order to circumvent all problems with the default key or with a generic key.

TYPES itab TYPE STANDARD TABLE OF string WITH EMPTY KEY.

DATA(itab) = VALUE itab(
  ( `I'm going slightly mad` )
  ( `I'm going slightly mad` )
  ( `It finally happened - happened` )
  ( `It finally happened - ooh woh` ) ).

Without explicit key declaration the type would not be usable for the inline data declaration shown here. Since I don't care about the key, I use the empty key. A SORT itab without specifying a sort key will do nothing and produce a warning from the syntax check.

Starting with release 7.40 you declare your standard tables either with a good key or an empty key but never with the chancy default key!

1.13 FOR Expressions

With 7.40, SP05 the first version of the iteration operator FOR was introduced. You can use it in constructor expressions with VALUE and NEW for so called table comprehensions, as e.g.

DATA(itab2) = VALUE t_itab2( FOR wa IN itab1 WHERE ( col1 < 30 )

                             ( col1 = wa-col2 col2 = wa-col3 ) ).

This is an expression enabled version of LOOP AT itab. It didn't take long to ask also for expression enabled versions of DO and WHILE (couldn't stand them in otherwise expression enabled examples any more ...).

Therefore, with 7.40, SP08 we also offer conditional iterations with FOR:

... FOR i = ... [THEN expr] UNTIL|WHILE log_exp ...

You can use FOR in constructor expressions with VALUE and NEW in order to create new internal tables, e.g.:

TYPES:
  BEGIN OF line,
    col1 TYPE i,
    col2 TYPE i,
    col3 TYPE i,
  END OF line,
  itab TYPE STANDARD TABLE OF line WITH EMPTY KEY.

DATA(itab) = VALUE itab(
     FOR j = 11 THEN j + 10 UNTIL j > 40
     ( col1 = j col2 = j + 1 col3 = j + 2  ) ).

gives

COL1

COL2

COL3

11

12

13

21

22

23

31

32

33

Neat, isn't it?

But we don't want to construct internal tables only. Now that we have all kinds of iterations available with FOR, we want to construct arbitrary types. And that's where the new constructor operator REDUCE comes in.

... REDUCE type(

      INIT result = start_value

           ...

      FOR for_exp1

      FOR for_exp2

      ...

      NEXT ...

           result = iterated_value

           ... ) ...

While VALUE and NEW expressions can include FOR expressions, REDUCE must include at least one FOR expression. You can use all kinds of FOR expressions in REDUCE:

l   with IN for iterating internal tables

l   with UNTIL or WHILE for conditional iterations.

Let's reduce an internal table:

DATA itab TYPE STANDARD TABLE OF i WITH EMPTY KEY.
itab = VALUE #( FOR j = 1 WHILE j <= 10 ( j ) ).

DATA(sum) = REDUCE i( INIT x = 0 FOR wa IN itab NEXT x = x + wa ).

First, the table is filled with VALUE and FOR and then it is reduced with REDUCE to the sum of its contents. Note that there is no THEN used to construct the table. If THEN is not specified explicitly, implicitly THEN j = j + 1 is used. Be also aware, that you can place any expression behind THEN, including method calls. You only have to make sure that the end condition is reached within maximal program run time.

Now let's reduce the values of a conditional iteration into a string:

DATA(result) =

  REDUCE string( INIT text = `Count up:`

                 FOR n = 1 UNTIL n > 10

                 NEXT text = text && | { n }| ).

The result is

Count up: 1 2 3 4 5 6 7 8 9 10

These simple examples show the principle. Now imagine, what you can do by mixing REDUCE with all the other expression enabled capabilities. I only say nested REDUCE and VALUE operators ...

To conclude I show a cunning little thing that I use in some of my documentation examples:

TYPES outref TYPE REF TO if_demo_output.

DATA(output) =
  REDUCE outref( INIT out  = cl_demo_output=>new( )
                      text = `Count up:`
                 FOR n = 1 UNTIL n > 11
                 NEXT out = out->write( text )
                      text = |{ n }| ).

output->display( ).

I reduced the values of an iteration into the output list of a display object, oh my  ...

1.14 Boolean type (Predicative Method Calls)

Predicative Method Calls

For quite some time the lack of a real boolean type in ABAP led to a lot of discussions inside and outside of SAP and especially in SCN. People kept asking, why can I not write

IF meth( ... ).

  ...

ENDIF.

The answer always was: Because you have to have a boolean type behind IF and no method can return a boolean type.

But in fact, the solution was absolutely simple and - tatarata! - is made availabe in Release 7.40, SP08.

You can write the so called predicative method call

... meth( ) ...

as a short form of the relational expression

... meth( ) IS NOT INITIAL ...

Now and that's already all! Of course,  methods that return a value of type ABAP_BOOL are especially suited for that purpose. We call those predicate methods now, but it's not a prerequisite to use them.

So you can place simple functional method calls everywhere, where logical expressions are allowed: behind  IF, CHECK, ASSERT, COND, SWITCH, ...

The documentation shows some examples:

IF cl_abap_demo_services=>is_production_system( ).

  cl_demo_output=>display(

     'This demo cannot be executed in a production system' ).

  LEAVE PROGRAM.

ENDIF.

Here, a predicative method is called that returns ABAP_TRUE or ABAP_FALSE.

COND string( WHEN cl_demo_spfli=>get_spfli( to_upper( carrier ) )

               THEN `Filled`

               ELSE `Not filled` )

Here, a normal method is called and the result is true, if the table type return value contains lines.

Why didn't we enable this long before?

New Boolean Function

Did you ever stumble over that one?

IF boolc( 1 = 2 ) = abap_false.
  cl_demo_output=>display_text( 'yes' ).
ELSE.
  cl_demo_output=>display_text( 'no' ).
ENDIF.

The relational expression boolc( 1 = 2 ) = abap_false is false! Why? Because boolc despite its name does not return c but string and the comparison rules for c and string blah, blah, blah ...

Now, with Release 7.40, SP08, we have an new Boolean function.

IF xsdbool( 1 = 2 ) = abap_false.

  cl_demo_output=>display_text( 'yes' ).

ELSE.

  cl_demo_output=>display_text( 'no' ).

ENDIF.

The relational expression xsdbool( 1 = 2 ) = abap_false is true, because xsdbool returns type XSDBOOLEAN from the ABAP Dictionary that is - yes, you guess it -  c of length 1. For the experts among you, XSDBOOLEAN is normally used for special mappings in XML-transformations and was reused here for quite another purpose. And that's were the funny name xsdbool comes from.

The new Boolean function xsdbool can be used everywhere, where comparisons with type c length 1 are needed, especially comparisons with type ABAP_BOOL.But be aware of the fact, that the space that is returned by xsdbool for a false expression is cut in many operand positions, e.g. if you want to convert it with a builtin function like translate.

1.15 LOOP AT ... GROUP BY... / LOOP AT ... GROUP

You know the GROUP BY clause from SQL. There was not such a clause for internal tables up to now. All we had was that clumsy group level processing with statements AT NEW ... that relied on the order of table columns and contents that is sorted respectively.

With release 7.40, SP08 there is a real GROUP BY clause for LOOP AT itab that is much more powerful than the SQL one.

DATA flights TYPE TABLE OF spfli WITH EMPTY KEY.

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

DATA members LIKE flights.
LOOP AT flights INTO DATA(flight)
     GROUP BY ( carrier = flight-carrid cityfr = flight-cityfrom )
              ASCENDING
              ASSIGNING FIELD-SYMBOL(<group>).
  CLEAR members.
  LOOP AT GROUP <group> ASSIGNING FIELD-SYMBOL(<flight>).
    members = VALUE #( BASE members ( <flight> ) ).
  ENDLOOP.
  cl_demo_output=>write( members ).
ENDLOOP.
cl_demo_output=>display( ).

Looks like dreaded nested LOOPs, but it isn't quite that!  What happens here is that the first LOOP statement is executed over all internal table lines in one go and the new GROUP BY addition groups the lines. Technically, the lines are bound internally  to a group that belongs to a group key that is specified behind GROUP BY.The group key is calculated for each loop pass. And the best is, it need not be as simple as using only column values, but you can use any expressions here that normally depend on the contents of the current line, e.g. comparisons, method calls, .... The LOOP body is not evaluated in this phase!

Only after the grouping phase, the LOOP body is evaluated. Now a second (but not nested) loop is carried out over the groups constructed in the first phase. Inside this group loop you can access the group using e.g. the field symbol <group> that is assigned to the group in the above example. If you want to access the members of the group, you can us the new LOOP AT GROUP statement, which enables a member loop within the group loop. In the example, the members are inserted into a member table and displayed.

Here another example, where the group key is evaluated from method calls:

LOOP AT flights INTO DATA(wa)

     GROUP BY ( tz_from = get_time_zone( wa-airpfrom )

                tz_to   = get_time_zone( wa-airpto ) )

     ASSIGNING FIELD-SYMBOL(<group>).

  ...

ENDLOOP.

Of course, there is also expression enabled syntax for grouping internal tables.

In a first step, we get rid of LOOP AT GROUP by replacing it with a FOR expression:

DATA members LIKE flights.

LOOP AT flights INTO DATA(flight)

     GROUP BY ( carrier = flight-carrid cityfr = flight-cityfrom )

              ASCENDING

              ASSIGNING FIELD-SYMBOL(<group>).

  members = VALUE #( FOR m IN GROUP <group> ( m ) ).

  cl_demo_output=>write( members ).

ENDLOOP.

cl_demo_output=>display( ).

The IN GROUP is a new addition to FOR. Second, away with the outer LOOP:

TYPES t_flights LIKE flights.

DATA out TYPE REF TO if_demo_output.

out = REDUCE #( INIT o = cl_demo_output=>new( )

                FOR GROUPS <group> OF flight IN flights

                GROUP BY ( carrier = flight-carrid cityfr = flight-cityfrom )

                  ASCENDING

                LET members = VALUE t_flights( FOR m IN GROUP <group> ( m ) ) IN

                NEXT o = o->write( members ) ).

out->display( ).

FOR GROUPS is another new FOR variant. Believe me, it does the same as the variants above. But for reasons of readability, the combination of LOOP AT GROUP with a FOR IN GROUP within might be the preferable one, at least for this example .

For a deep dive, read this and this!

PS: If you are confused what kind of loops and nesting takes place, have a look at my comment below.

1.16 IS INSTANCE OF

This one is a tribute to the community, to SCN, to you. One of you, Volker Wegert; has blogged his ABAP Wishlist - IS INSTANCE OF some years ago and reminded us again and again. Others backed him and I myself participated a little bit by forwarding the wish to the kernel developers. And constant dripping wears the stone, ABAP 7.50 comes with a new relational expression IS INSTANCE OF, even literally.

If you wanted to find out whether a reference variable of a given static type can point to an object before ABAP 7.50, you had to TRY a casting operation that might look like something as follows:

DATA(typedescr) = cl_abap_typedescr=>describe_by_data( param ).

    DATA:
      elemdescr   TYPE REF TO cl_abap_elemdescr,
      structdescr TYPE REF TO cl_abap_structdescr,
      tabledescr  TYPE REF TO cl_abap_tabledescr.
    TRY.
        elemdescr ?= typedescr.
        ...
      CATCH cx_sy_move_cast_error.
        TRY.
            structdescr ?= typedescr.
            ...
          CATCH cx_sy_move_cast_error.
            TRY.

                tabledescr ?= typedescr.
                ...
              CATCH cx_sy_move_cast_error.
                ...
            ENDTRY.
        ENDTRY.
    ENDTRY.

In this example we try to find the resulting type of an RTTI-operation.

With ABAP 7.50 you can do the same as follows:

    DATA(typedescr) = cl_abap_typedescr=>describe_by_data( param ).
   

IF typedescr IS INSTANCE OF cl_abap_elemdescr.
      DATA(elemdescr) = CAST cl_abap_elemdescr( typedescr ).
      ...
    ELSEIF typedescr IS INSTANCE OF cl_abap_structdescr.
      DATA(structdescr) = CAST cl_abap_structdescr( typedescr ).
      ...
    ELSEIF typedescr IS INSTANCE OF cl_abap_tabledescr.
      DATA(tabledescr) = CAST cl_abap_tabledescr( typedescr ).
      ...
    ELSE.
      ...
    ENDIF.

The new predicate expression IS INSTANCE OF checks, if the dynamic type of the LHS operand is more special or equal to an RHS type. In fact it checks whether the operand can be down casted with that type. In the above example, such a casting takes place after IF, but its your decision if you need it. If you need it, there is even a shorter way to write it. A new variant of the CASE -WHEN construct:!

DATA(typedescr) = cl_abap_typedescr=>describe_by_data( param ).

    CASE TYPE OF typedescr.
      WHEN TYPE cl_abap_elemdescr INTO DATA(elemdescr).
        ...
      WHEN TYPE cl_abap_structdescr INTO DATA(structdescr).
        ...
      WHEN TYPE cl_abap_tabledescr INTO DATA(tabledescr).
        ...
      WHEN OTHERS.
        ...
    ENDCASE.

The new TYPE OF and TYPE additions to CASE and WHEN allow you to write IS INSTANCE OF as a case control structure. The optional INTO addition does the casting for you, I think that's rather cool.

B.T.W., the new IS INSTANCE OF and CASE TYPE OF even work for initial reference variables. Then they check if an up cast is possible.This can be helpful for checking the static types of formal parameters or field symbols that are typed generically. Therefore IS INSTANCE OF is not only instance of but might also be labeled as a type inspection operator.

For more information see:

l   IS INSTANCE OF

http://help.sap.com/abapdocu_750/en/index.htm?file=abenlogexp_instance_of.htm

CASE TYPE OF

http://help.sap.com/abapdocu_750/en/index.htm?file=abapcase_type.htm

1.17 new options for String

After my previous blog about the option of using Method Chaining (Using New ABAP stuff – Method Chaining and CL_SALV_TABLE | SAP Blogs) I thought it would be a good idea to write something about my experiences with new string options like String Templates, Chaining of strings, and built-in string functions. Just as with Method Chaining, using these string options allow for making ABAP code terser, which means that fewer statements are needed for the same functionality, without compromising readability of the code.

What’s it al about?

Several new options for the handling of characters strings were introduced to ABAP in WAS702:

l  String Templates: the option to create a character string out of literal texts, expressions, and control characters .

l  Chaining Operator: chain two character-like operands into one new character string.

l  Character String Functions: built-in functions for searching, processing and comparing strings.

I will first explain these options and give a few examples. The last paragraph explains how the use of string templates, combined with built-in functions and the use of functional methods, limits the number of needed ABAP statements.

Note that the ABAP Keyword documentation contains several built-in examples, related to old and new ABAP features. These examples can be executed and debugged and its code can be analysed. Moreover, the program DEMO_EXPRESSIONS offers 45 examples of character string and byte string processing.

String Templates

The purpose of a string template is to create a new character string out of literal texts and embedded expressions. It largely replaces the use of the WRITE TO statement, which will be explained later on.

A string template is defined by using the | (pipe) symbol at the beginning and end of a template.

DATA: character_string TYPE string.
character_string = |This is a literal text.|.

This example has in fact exactly the same result as:

character_string = `This is a literal text.`.

The added value of a string template becomes clear when combining literal texts with embedded expressions and control characters. Embedded expressions are defined within a string template with curly brackets { expression }.  Note that a space between bracket and expression is obligatory.

An expression can be a data object (variable), a functional method, a predefined function or a calculation expression. Some examples are:

character_string = |{ a_numeric_variable }|.

character_string = |This resulted in return code { sy-subrc }|.

character_string = |The length of text element 001 ({ text-001 }) is { strlen( text-001 ) }|.

Embedded expressions have a default output format, but also several formatting options, comparable to the format options of the WRITE statement. Some examples are:

DATA: amount_field   TYPE vbap-netwr VALUE '1234567.123',
      currency_field TYPE vbap-waerk.
character_string = |{ amount_field CURRENCY = currency_field  NUMBER = USER }|.
character_string = |{ amount_field COUNTRY = 'GB ' }|.

Two limitations for which I haven’t found a solution yet are:

1) I could not find a formatting option for Conversion Exits. Consider the example below: the WRITE….TO… calls the conversion exit KONPD that converts an internal project number to an external project id. The string template inserts the internal project number into the string.

DATA: project_number TYPE proj-pspnr.

SELECT SINGLE pspnr FROM proj INTO project_number .

WRITE project_number TO character_string2.

character_string = |{ project_number }|.

2) The following option for text-elements is not supported in an embedded expression:

WRITE : 'This is a text'(001).

The only option to use a numbered text is in an embedded expression:

character_string = |{ text-001 }|.

However, using the Chaining Operator offers a simple solution for this as is demonstrated below.

Chaining Operator

The Chaining Operator && can be used to create one character string out of multiple other strings and string templates. The use of the chaining operator largely replaces the CONCATENATE statement.

In this example, a number text, a space, an existing character string and a new string template are concatenated into a new character string.

character_string  =  'Text literal'(002) && ` ` && character_string && |{ amount_field NUMBER = USER }|.

Built-in functions

SAP has added several new built-in functions for searching, comparing, and processing character strings.  A few of these functions already existed before release WAS702, like for example, CHARLEN( ) or STRLEN( ). Other statements like FIND, REPLACE or TRANSLATE can now be replaced by built-in functions Also new functions have been added.

Note that these built-in functions can be used as part of string templates or in operand positions. In the ABAP keyword documentation, the added value of these functions is described as followed:

The string functions enable many string processing tasks to be done in operand positions where previously separate statements and auxiliary variables were required

A few examples of built-in functions are:

string_result = to_mixed( val = string_field sep = ` ` ).
string_result = reverse( string_field ).
distance_between_strings = distance( val1 = string_field val2 = string_result ).

The functions to_mixed and reverse speak for themselves. The function distance is a similarity function that calculates the minimum number of steps needed to change one string into another string, the so called Levenshtein distance. I’m still looking for an opportunity to use it in one of my customer assignments

Compacting ABAP code by using new string options

Imagine that you have to create a tab-delimited file from an internal table with a line type, consisting of character and numeric type fields. Furthermore, you want the date and amount fields to be converted into a specific output format in the file.

The definition of the internal table could look like this:

TYPES: BEGIN OF ty_struct,
change_date   TYPE d,
amount        TYPE bf_dmbtr,"numeric type
currency_key  TYPE waers,
text_field    TYPE string,
END OF ty_struct.

Let us focus on the creation of one tab-delimited file entry. Pre-WAS702, this would look like this:

DATA: date_as_a_string TYPE c LENGTH 10,
      amount_as_a_string TYPE c LENGTH 20.

WRITE <input_line>-change_date TO date_as_a_string .
WRITE <input_line>-amount      TO amount_as_a_string CURRENCY input_line>-currency_key.
CONCATENATE date_as_a_string
            amount_as_a_string
            <input_line>-currency_key
            <input_line>-text_field INTO download_line
            SEPARATED BY cl_abap_char_utilities=>horizontal_tab.

Note that you need two variables and two WRITE TO statements to convert the date and amount field to a suitable output format. Using the amount field, which is a numeric field, directly in the CONCATENATE statement will lead to a syntax error.

Using a string template, chaining operator and control characters, these 5 statements can be reduced to only one statement:

download_line =
|{ <input_line>-change_date DATE = USER }\t| &&
|{ <input_line>-amount NUMBER = USER CURRENCY = <input_line>-currency_key }\t| &&
|{ <input_line>-currency_key }\t{ <input_line>-text_field } |.

The tab delimiter is inserted by using the control character \t.

This is just one of many examples that I can think of.  Just consider the following snippets of code and consider the number of intermediate steps your would need to implement this functionality without string templates.

* Include a built-in function

log_information = |Number of entries in download: { lines( download_file ) } |.

* Include a functional method call

log_information = |My IP address = {cl_gui_frontend_services=>get_ip_address()}|

Conclusion

In my daily life as an ABAP programmer, I’m working on customer SAP systems with basis versions ranging from R/3 4.6C up to WAS731 based systems. Not being able to use new string options on several older customer systems can be quite frustrating after having used them on other systems. I once fell into the trap of writing a program using string templates and method chaining. I then tried to install this program on another ECC60 system, but this failed because this system was based on WAS701. I had to rewrite parts of the program: define new intermediate variables, call intermediate methods to get values, and use the WRITE TO statement to get the proper date and amount values in my download.

So, if you’re working on a WAS702vv-based system, I would advice you to start using new ABAP possibilities because they will improve your speed of development and make your programs better maintainable. For me, I just enjoy discovering and trying new options and share my experiences with other programmers.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值