This is a test message to test the length of the message box.
Login
ABAP Deep Dive Table access
Created by Software-Heroes

ABAP Deep Dive - Table access (internal)

3544

In this article, let's take a look at table access to internal tables and how they replace READ TABLE.

The new table access has been possible for quite a while now. Tables are read like arrays in other languages and access is therefore much shorter. Previously, tables were primarily accessed and entries determined using the READ TABLE; this is now all possible using the new statement.

 

Preparation

Before we start with the examples, a short preparation of the types and variables that we will use in the further examples. we define two types for use and two variables that contain data:

TYPES:
  BEGIN OF ts_data,
    ident TYPE c LENGTH 3,
    text  TYPE string,
    date  TYPE d,
  END OF ts_data,
  tt_data TYPE SORTED TABLE OF ts_data
    WITH UNIQUE KEY ident
    WITH NON-UNIQUE SORTED KEY bydate COMPONENTS date,

  BEGIN OF ts_deep,
    deep_key TYPE c LENGTH 3,
    data     TYPE tt_data,
  END OF ts_deep,
  tt_deep TYPE STANDARD TABLE OF ts_deep WITH EMPTY KEY.

DATA:
  mt_data TYPE tt_data,
  mt_deep TYPE tt_deep.

 

Structure TS_DATA contains some sample data and is provided in table type TT_DATA. It is a sorted table with a unique key and a second key for the date. It should be noted here that each additional key on an internal table also produces administration costs for keeping the index up to date. In an additional method, we initialize the data in the variables with sample data:

mt_data = VALUE #(
  ( ident = 'A' text = 'Entry for A' date = '20220125' )
  ( ident = 'B' text = 'Entry for B' date = '20220225' )
  ( ident = 'C' text = 'Entry for C' date = '20220225' )
  ( ident = 'D' text = 'Entry for D' date = '20220125' )
  ( ident = 'K' text = 'Entry for K' date = '20220425' )
  ( ident = 'L' text = 'Entry for L' date = '20220325' )
  ( ident = 'N' text = 'Entry for N' date = '20220525' )
  ( ident = 'O' text = 'Entry for O' date = '20220425' )
).

mt_deep = VALUE #(
  ( deep_key = 'D1' data = mt_data )
  ( deep_key = 'D2' data = mt_data )
  ( deep_key = 'D3' data = mt_data )
).

 

Read via index

The easiest reading is through the index, in most cases you want to read and process the first row of a table. Simply write the index in square brackets after the table, don't forget the spaces in between. The inline declaration works here as you are already used to:

" Read table with index 1
DATA(ls_index) = mt_data[ 1 ].

" Read table with index 5
ls_index = mt_data[ 5 ].

 

Read field

You don't want to have the whole row, but just read a field from the table for a specific row? No problem, just access the field behind the square brackets, Eclipse supports you with the Content Assist (CTRL + SPACE). Works the same for a field assignment as it does for the inline declaration:

" Read text field
DATA(ld_text) =  mt_data[ 2 ]-text.

" Read date field
DATA(ld_date) =  mt_data[ 5 ]-date.

 

Read via key

So far we have mainly read the rows using the index, but this only works to a limited extent for SORTED and HASHED tables. To read about a key, you can specify the key in the square brackets instead of the index. Simply enter the field name and comparison, if there are several key fields simply list the fields and the query. If you want to read about another key, you have to specify the key, only specifying the field does not work. There is a short form and a long form:

" Read for primary key
DATA(ls_key) = mt_data[ ident = 'B' ].

" Read for secondary key date 
ls_date = mt_data[ KEY bydate COMPONENTS date = '20220325' ].

" Read for secondary key date (short version)
DATA(ls_date) = mt_data[ KEY bydate date = '20220525' ].

 

Not found

If there is no entry for your query, then the system throws a handleable exception that you can catch with TRY/CATCH. If you don't catch the exception anywhere, processing will stop:

" Index not found
TRY.
    DATA(ls_index) =  mt_data[ 10 ].
  CATCH cx_sy_itab_line_not_found.
ENDTRY.

" Key not found
TRY.
    DATA(ls_key) =  mt_data[ ident = 'X' ].
  CATCH cx_sy_itab_line_not_found.
ENDTRY.

 

Additional functions

With the new table accesses there are also some new functions for working with the tables. LINE_EXISTS checks the subsequent query to see if it returns a result. If you use these, you don't have to catch an exception. This works so far for the index and the key. With LINE_INDEX you get the index for a query on the table (by key). If the line with the key does not exist, the return value is set, no exception needs to be caught:

" Index not found
IF NOT line_exists( mt_data[ 10 ] ).
ENDIF.

" Key not found
IF NOT line_exists( mt_data[ ident = 'X' ] ).
ENDIF.

" Index for key
DATA(ld_tabix) = line_index( mt_data[ ident = 'D' ] ).

 

Find index

In the past, a READ was often used to determine the insertion position in order to create an entry in the table (BINARY SEARCH). In most cases, this statement looked like this:

" Fill sy-tabix with position
READ TABLE mt_data TRANSPORTING NO FIELDS
  WITH TABLE KEY ident = 'E'.

 

There is no new version for this, but it is no longer necessary. By now you should be working with tables that have a specified key. STANDARD keyless tables shouldn't be used that often anymore. Instead, use a sorted table or key and search for the entry, if it doesn't exist, just INSERT a new one and the key will do the rest:

IF line_index( mt_data[ ident = 'E' ] ) = 0.
  " Insert new entry
ENDIF.

 

Change table

How do you actually change individual table entries in a table? Two variants are available for this, one via a reference and one by directly adapting the entry in the table. If you want to match an entire line, then you can read a line and have the reference returned to you. If you then change the fields of the reference, the entry in the table also changes. In the second variant, you can also simply change the content of a field directly; read access gives you the option:

" Change values in line
DATA(lr_data) = REF #( mt_data[ 2 ] ).
lr_data->date = '20230101'.

" Change field of line
mt_data[ ident = 'N' ]-date = '20230202'.

 

The content in the two rows of the table has changed after the execution, changes to table contents have never been so easy:

 

Performance

For all the ease of access, however, you should also keep an eye on performance. A query can be implemented poorly all too quickly, especially if you want to read individual fields. In the following example, the fields are assigned individually:

" 3 read accesses
DATA(ld_key) = mt_data[ ident = 'N' ]-ident.
DATA(ld_longtext) = mt_data[ ident = 'N' ]-text.
DATA(ld_date) = mt_data[ ident = 'N' ]-date.

 

However, this also results in 3 read accesses to the data and the access must also be implemented 3 times. If this is only done once, that's fine. With more accesses, the whole line should be read and assigned to a variable, which can then be used to supply the fields better and saves multiple implementations:

" 1 read access
DATA(ls_data) = mt_data[ ident = 'N' ].
ld_key = ls_data-ident.
ld_longtext = ls_data-text.
ld_date = ls_data-date.

 

Filter data sets

In the next step, we only need a subset from the table to start further processing. Classically, you could map this using a LOOP and use a WHERE condition to transfer the data records to the new table. However, the table must be defined for this. In the LOOP we specify the secondary key so that the performance is higher:

DATA:
  lt_filtered_loop TYPE tt_data,
  ld_filter_by     TYPE d VALUE '20220225'.

" Filter with loop
LOOP AT mt_data INTO DATA(ls_data) USING KEY bydate WHERE date = ld_filter_by.
  INSERT ls_data INTO TABLE lt_filtered_loop.
ENDLOOP.

 

The shorter version also works with the new FILTER statement, provided there is a key in the table for which the filter is to be applied. Filter can infer the target data type from the passed table, so we can use #. If we don't go through the primary key, we still give the corresponding key (like above):

" New filter statement
DATA(lt_filtered) = FILTER #( mt_data USING KEY bydate WHERE date = ld_filter_by ).

 

Nested access

What about deep structures? In the past, many READs were necessary to read through such structures; today, access can be carried out in one step. Since we can continue working on the result of the query, access could look like this:

" Nested access to data
DATA(ld_nested_field) = mt_deep[ deep_key = 'D2' ]-data[ 1 ]-text.

 

In the first step, we read from the MT_DEEP table with a key and refer to the DATA field, which is a table. Here we read the first entry and let us return the text for it, which we store in a variable using an inline declaration.

 

VALUE

When it comes to the VALUE statement, there are also some simplifications for the new table accesses. We described above how you can catch non-existent lines. These checks help to react to such situations, but what about when you need a simple IF-THEN-ELSE logic? Here is an example of how you can map this with the value:

" When not found, return empty line
DATA(ls_optional) = VALUE #( mt_data[ ident = 'E' ] OPTIONAL ).

" When not found, return the first line 
DATA(ls_default) = VALUE #( mt_data[ 15 ] DEFAULT mt_data[ 1 ] ).

" When not found, return custom line
DATA(ls_default_line) = VALUE #( mt_data[ 15 ] DEFAULT VALUE #( ident = 'Z' text = 'Default is Z' ) ).

 

There are two extensions for the VALUE statement:

  • OPTIONAL - If access to the table fails, then no exception needs to be caught and the result is Optional. If there is no hit, an initial line is returned.
  • DEFAULT - If the first access fails, then a defined row is returned or, as in the example above, the first row of the table can also be read. However, if this read access also fails, the exception must be caught.

 

Complete example

Some of the examples shown are quite extensive, here is the entire executable class with all examples:

CLASS zcl_bs_demo_table_deep DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.

  PROTECTED SECTION.
  PRIVATE SECTION.
    TYPES:
      BEGIN OF ts_data,
        ident TYPE c LENGTH 3,
        text  TYPE string,
        date  TYPE d,
      END OF ts_data,
      tt_data TYPE SORTED TABLE OF ts_data
        WITH UNIQUE KEY ident
        WITH NON-UNIQUE SORTED KEY bydate COMPONENTS date,

      BEGIN OF ts_deep,
        deep_key TYPE c LENGTH 3,
        data     TYPE tt_data,
      END OF ts_deep,
      tt_deep TYPE STANDARD TABLE OF ts_deep WITH EMPTY KEY.

    DATA:
      mt_data TYPE tt_data,
      mt_deep TYPE tt_deep.

    METHODS:
      prepare_data,
      spacer
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      read_by_index
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      read_only_field
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      line_not_found
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      read_by_key
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      itab_checks
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      change_itab_line
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      performance_hint
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      filter_values
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      nested_call
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      find_insert_index
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out,
      optimized_value
        IMPORTING
          io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.


CLASS zcl_bs_demo_table_deep IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    prepare_data( ).
    read_by_index( out ).
    spacer( out ).
    read_only_field( out ).
    spacer( out ).
    read_by_key( out ).
    spacer( out ).
    line_not_found( out ).
    spacer( out ).
    itab_checks( out ).
    spacer( out ).
    find_insert_index( out ).
    spacer( out ).
    change_itab_line( out ).
    spacer( out ).
    performance_hint( out ).
    spacer( out ).
    filter_values( out ).
    spacer( out ).
    nested_call( out ).
    spacer( out ).
    optimized_value( out ).
  ENDMETHOD.


  METHOD prepare_data.
    mt_data = VALUE #(
      ( ident = 'A' text = 'Entry for A' date = '20220125' )
      ( ident = 'B' text = 'Entry for B' date = '20220225' )
      ( ident = 'C' text = 'Entry for C' date = '20220225' )
      ( ident = 'D' text = 'Entry for D' date = '20220125' )
      ( ident = 'K' text = 'Entry for K' date = '20220425' )
      ( ident = 'L' text = 'Entry for L' date = '20220325' )
      ( ident = 'N' text = 'Entry for N' date = '20220525' )
      ( ident = 'O' text = 'Entry for O' date = '20220425' )
    ).

    mt_deep = VALUE #(
      ( deep_key = 'D1' data = mt_data )
      ( deep_key = 'D2' data = mt_data )
      ( deep_key = 'D3' data = mt_data )
    ).
  ENDMETHOD.


  METHOD spacer.
    io_out->write( '---' ).
  ENDMETHOD.


  METHOD read_by_index.
    DATA(ls_index) = mt_data[ 1 ].
    io_out->write( |Read by index 1: { ls_index-ident }| ).

    ls_index = mt_data[ 5 ].
    io_out->write( |Read by index 5: { ls_index-ident }| ).
  ENDMETHOD.


  METHOD read_only_field.
    DATA(ld_text) =  mt_data[ 2 ]-text.
    io_out->write( |Read by index 2 only text: { ld_text }| ).

    DATA(ld_date) =  mt_data[ 5 ]-date.
    io_out->write( |Read by index 5 only date: { ld_date }| ).
  ENDMETHOD.


  METHOD read_by_key.
    DATA(ls_key) = mt_data[ ident = 'B' ].
    io_out->write( |Read by key B: { ls_key-text }| ).

    DATA(ls_date) = mt_data[ KEY bydate date = '20220525' ].
    io_out->write( |Read by sec-key date: { ls_date-text }| ).

    ls_date = mt_data[ KEY bydate COMPONENTS date = '20220325' ].
    io_out->write( |Read by sec-key date with components: { ls_date-text }| ).
  ENDMETHOD.


  METHOD line_not_found.
    TRY.
        DATA(ls_index) =  mt_data[ 10 ].
      CATCH cx_sy_itab_line_not_found.
        io_out->write( |Index 10 not found| ).
    ENDTRY.

    TRY.
        DATA(ls_key) =  mt_data[ ident = 'X' ].
      CATCH cx_sy_itab_line_not_found.
        io_out->write( |Key X not found| ).
    ENDTRY.
  ENDMETHOD.


  METHOD itab_checks.
    IF NOT line_exists( mt_data[ 10 ] ).
      io_out->write( |Index 10 not found| ).
    ENDIF.

    IF NOT line_exists( mt_data[ ident = 'X' ] ).
      io_out->write( |Key X not found| ).
    ENDIF.

    DATA(ld_tabix) = line_index( mt_data[ ident = 'D' ] ).
    io_out->write( |Line-Index for D is: { ld_tabix }| ).
  ENDMETHOD.


  METHOD find_insert_index.
    READ TABLE mt_data TRANSPORTING NO FIELDS
      WITH TABLE KEY ident = 'E'.
    io_out->write( |New entry at position: { sy-tabix }| ).

    IF line_index( mt_data[ ident = 'E' ] ) = 0.
      io_out->write( 'Insert new entry' ).
    ENDIF.
  ENDMETHOD.


  METHOD change_itab_line.
    DATA(lr_data) = REF #( mt_data[ 2 ] ).
    lr_data->date = '20230101'.

    mt_data[ ident = 'N' ]-date = '20230202'.

    io_out->write( |Changed table:| ).
    io_out->write( mt_data ).

    prepare_data( ).
  ENDMETHOD.


  METHOD filter_values.
    DATA:
      lt_filtered_loop TYPE tt_data,
      ld_filter_by     TYPE d VALUE '20220225'.

    LOOP AT mt_data INTO DATA(ls_data) USING KEY bydate WHERE date = ld_filter_by.
      INSERT ls_data INTO TABLE lt_filtered_loop.
    ENDLOOP.
    io_out->write( |Data filtered with loop:| ).
    io_out->write( lt_filtered_loop ).

    DATA(lt_filtered) = FILTER #( mt_data USING KEY bydate WHERE date = ld_filter_by ).
    io_out->write( |Data filtered with FILTER:| ).
    io_out->write( lt_filtered ).
  ENDMETHOD.


  METHOD nested_call.
    DATA(ld_nested_field) = mt_deep[ deep_key = 'D2' ]-data[ 1 ]-text.
    io_out->write( |Nested text from D2/1: { ld_nested_field }| ).
  ENDMETHOD.


  METHOD performance_hint.
    DATA(ld_key) = mt_data[ ident = 'N' ]-ident.
    DATA(ld_longtext) = mt_data[ ident = 'N' ]-text.
    DATA(ld_date) = mt_data[ ident = 'N' ]-date.

    DATA(ls_data) = mt_data[ ident = 'N' ].
    ld_key = ls_data-ident.
    ld_longtext = ls_data-text.
    ld_date = ls_data-date.

    io_out->write( 'For performance check' ).
  ENDMETHOD.


  METHOD optimized_value.
    DATA(ls_optional) = VALUE #( mt_data[ ident = 'E' ] OPTIONAL ).
    io_out->write( |Value of E with Optional:| ).
    io_out->write( ls_optional ).

    DATA(ls_default) = VALUE #( mt_data[ 15 ] DEFAULT mt_data[ 1 ] ).
    io_out->write( |Value of 15 with Default 1:| ).
    io_out->write( ls_default ).

    DATA(ls_default_line) = VALUE #( mt_data[ 15 ] DEFAULT VALUE #( ident = 'Z' text = 'Default is Z' ) ).
    io_out->write( |Value of 15 with Default custom:| ).
    io_out->write( ls_default_line ).
  ENDMETHOD.
ENDCLASS.

 

When executed, the following data is written to the console:

 

Conclusion

The new statement is very powerful, but should be used with care in some places. Our tip is to continue to do without the now "old" statements and only use the new ones.

 

Source:
SAP Documentation - Table expressions
SAP Documentation - FILTER
SAP Documentation - LINE_INDEX


Included topics:
Deep DiveArrayREAD TABLENew ABAP
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


ABAP Deep Dive - FOR (Loops)

Category - ABAP

Let's take a closer look at the FOR loop. How does it work? What do I have to consider and what can I do with it?

04/14/2023

ABAP Deep Dive - VALUE

Category - ABAP

In this article we want to look at the value statement again in all its forms and how you can use it in your daily work.

11/11/2022

ABAP Deep Dive - CORRESPONDING

Category - ABAP

In this article, a little more about the new Corresponding Statement and how to use it in detail. Let's take a look at the additional features.

09/16/2022

ABAP - RETURN value

Category - ABAP

After all these years, the “real” return in ABAP has finally arrived. In this article we will show you how it works and what it can do.

02/13/2024

ABAP Developer still relevant

Category - ABAP

In this article we look at whether ChatGPT can already replace an ABAP developer or whether it can be used as a help in everyday life.

01/06/2023