Mike Pokraka
Posted onJune 23, 2018 2 minute read
ABAP tips: Checking existence within an internal table
FollowRSS feedLike
42 Likes 34,072 Views 30 Comments
I got a blog-worthy surprise when I did a quick performance test on two different ways to use the new-ish (7.4) table expressions to perform a search on an internal table.
My scenario involved internal table searches where the values would be missing a lot of the time. The unexpected result was that when a row can’t be found, using table expressions with exceptions is ten times slower on average than all other lookup methods, including the good ol’ fashioned READ TABLE with SY-SUBRC.
Quick primer for those still getting accustomed to 7.4 ABAP (feel free to skip to the results):
With table expressions, finding an entry in a table is really neat. I have a table that includes a field “ID”. So the following READ TABLE can be rewritten using a table expression (the bit in square brackets):
READ TABLE itab INTO row WITH KEY id = find_id.row = itab[ id = find_id ].TRY.row = itab[ id = find_id ].CATCH cx_sy_itab_line_not_found....
ENDTRY.Another alternative is to use a VALUE constructor expression where you can add the keyword OPTIONAL (or DEFAULT) to just initialize any non-found values.
row = VALUE #( itab[ id = find_id ] OPTIONAL ).IF row IS INITIAL....
This particular usage of VALUE may seem a little awkward. I mean, why use x = VALUE #( y ) when you can just write x = y? The VALUE constructor’s sole purpose here is the OPTIONAL bit that lets us do away with the exception.
As I was working on a performance-sensitive component, I tested it to see what performs best.
For completeness, and additionally with suggestions by Jacques Nomssi, Quynh Doan Manh and Uwe Fetzer in the comments, I also added a couple of other ways to look for an entry in a table:
The trusty READ TABLE … WITH KEY, with and without TRANSPORTING NO FIELDS
line_exists( )
ASSIGN itab[ … ] – which (bizarrely) sets SY-SUBRC instead of producing an exception
REF #( ) instead of VALUE #( ).
I tested a million failed lookups on a 7.50 HANA system. Here are the results in microseconds:
line_exists( ) : 610,758
READ TABLE ... TRANSPORTING NO FIELDS : 671,368READ TABLE : 671,115ASSIGN itab[ ... ] : 707,929
REF #( itab[ ... ] OPTIONAL ) : 888,562
VALUE #( itabl[ ... ] OPTIONAL ) : 961,803
TRY ... itab[ ... ] CATCH : 6,325,265I did not expect the last one at all.
So the take-away here for me is that a TRY-CATCH may be easier to read, but should not be used in performance-sensitive code unless you expect the values to be found most of the time. I’m happy to sacrifice a little performance for readability, but this is a significant impact.
I suspect that this applies to CATCH blocks in general, but that’s another analysis for another day.
For comparison, I also re-ran the same but this time with a lookup value that did exist. Two things happened:
the exception was no longer an influence (to be expected)
line_exists( ) became a poor choice in my scenario, because we need to double up the the search in order to also read the record:
IF line_exists( itab[ id = find_id ] ).row = itab[ id = find_id ].If you don’t need the data, line_exists( ) is fastest.
If performance is number 1 priority and you need the data, READ TABLE is fastest.
For compact and/or readable code, use table expressions.
(Yes, I know, ‘new’ ABAP can be used to make code either more readable or more cryptic, but that’s another discussion)
TRY-CATCH with table expressions can be a useful way to structure your code (e.g. to use a single catch handler for multiple expressions), but be mindful of the expected failure rate and performance-criticality of the component. If we’re talking anything less than thousands in a short space of time then you can safely ignore the impact.After several comments and suggestions, I uploaded my test program to GitHub at abap_itab_perf
It was just a quick-n-dirty originally for comparing just two options and just grew as a copy/paste of alternatives. Feel free to play with it or even send any updates back via GitHub.
以下内容整理自 SAP 官方 ABAP 速查表(主题:Internal Tables),作为本文的扩展补充资料。
Internal Tables ...
🟢 Click to expand for more details
Line Type
Table Category
| Category | Internally managed by | Access | Primary table key | When to use | Hints |
|---|---|---|---|---|---|
STANDARD | Primary table index (that's why these tables are called index tables) |
|
|
|
|
SORTED | Primary table index (that's why these tables are called index tables) |
|
... used to sort the table in ascending order. |
|
|
HASHED | Hash algorithm |
| Always unique |
|
|
Key Attributes
Further information
🟢 Click to expand for more details
Primary table key
primary_key, by which it can also be addressed explicitly. However, its use is optional, and it is usually not necessary to specify it explicitly. You can also specify an alias name for the primary key. [!NOTE] The key can consist of individual key fields or the entire line of the internal table. In this case, the pseudo component table_line can be used to denote the primary table key. For non-structured line types, this is the only way to define the key.
Standard key
table_line).Empty key
EMPTY KEY.line type does not contain non-numeric elementary components or if an unstructured line type is tabular. See an excursion regarding this aspect here.
... INTO TABLE @DATA(itab) ... in SELECT statements, the resulting table is a standard table and has an empty key.[!NOTE] Primary table index - Index tables, such as sorted and standard tables, have a primary table index. - Each table line receives a unique line number from the primary table index. - The index updates each time a line is added or removed.
Secondary table keys
sy-tabix is set.READ, LOOP AT or MODIFY statements, you must specify the key explicitly using the appropriate additions, for example, WITH ... KEY ... COMPONENTS or USING KEY.Key (F1 docu for standard ABAP)](https://help.sap.com/doc/abapdocu_latest_index_htm/latest/en-US/index.htm?file=abensecondary_key_guidl.htm "Guideline").
[!NOTE] - See examples of internal table declarations using the table keys mentioned above in the following section. - See more information and examples that focus on table access using keys and index below.
You can declare internal tables and internal table types in ABAP programs using the TYPES and DATA statements. The relevant syntax elements for internal tables are TABLE OF in combination with the additions TYPE or LIKE.
TYPES itab_type1 TYPE STANDARD TABLE OF data_type ... "Standard table type
TYPES itab_type2 LIKE SORTED TABLE OF data_object ... "Sorted table type
DATA itab1 TYPE TABLE OF data_type ... "Standard table by default
DATA itab2 TYPE HASHED TABLE OF data_type ... "Hashed table
DATA itab3 TYPE itab_type1 ... "Based on an existing internal table type
DATA itab4 LIKE itab1 ... "Based on an existing internal table [!NOTE] - If the table category is not specified (... TYPE TABLE OF ...), it is automatically... TYPE STANDARD TABLE OF .... - You can also create global table types in the ABAP Dictionary. This is not covered in the cheat sheet. - Using Runtime Type Creation (RTTC), you can define and create new internal tables and table types as type description objects at runtime. For more information, see the Dynamic Programming ABAP cheat sheet. Also find a snippet on creating internal tables dynamically by specifying the type dynamically below.
The following code snippets contain various internal table declarations. It is intended to demonstrate a selection of the rich variety of possible internal tables mentioned in the previous sections, e.g. in Table Keys in Internal Tables. In the examples, many of the internal tables are created using the structured type of a demo database table in the DDIC. The line type of the database table is automatically used when defining an internal table.
| Creating internal tables with ... | Details/Code Snippet |
| Standard table keys |
|
| Primary table keys |
|
| Empty keys |
|
| Secondary table keys |
|
The examples above demonstrate internal tables that are created using the structured type of a database table in the DDIC. The following example shows the pattern and various examples of declaring internal tables and types by including the local definition of structured data and internal table types for demonstration purposes.
Steps:
This is not necessary if you use, for example, the name of a database table or CDS view in the internal table declaration. In these cases their line type is used automatically.
You can also create an internal table by ...
positions](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abendeclaration_positions.htm) if the operand type can be fully determined, for example, using a DATA statement (or FINAL for immutable variables).
"Internal table creation steps based on a locally declared structure
"1. Defining line type locally
TYPES: BEGIN OF ls_loc,
key_field TYPE i,
char1 TYPE c LENGTH 10,
char2 TYPE c LENGTH 10,
num1 TYPE i,
num2 TYPE i,
END OF ls_loc.
"2. Defining internal table types
TYPES: tt_std TYPE TABLE OF ls_loc WITH EMPTY KEY,
tt_sorted TYPE SORTED TABLE OF ls_loc WITH NON-UNIQUE KEY key_field,
tt_hashed TYPE HASHED TABLE OF ls_loc WITH UNIQUE KEY key_field.
"3. Creating internal tables from locally defined table types
DATA: it22 TYPE tt_std,
it23 TYPE tt_sorted,
it24 TYPE tt_hashed.
"Combining data object and table type definition
DATA it25 TYPE TABLE OF ls_loc WITH NON-UNIQUE KEY key_field.
"LIKE addition
"Internal table based on an already existing internal table using LIKE.
DATA it26 LIKE it25.
"In the following example, an internal table is created containing internal tables
"of the type of itab_a6.
DATA it27 LIKE TABLE OF it25.This section explores various line and table type options when declaring internal tables. Examples have already been covered in the previous sections. Among the options are, for example:
[!NOTE] Types declared in the public visibility section of classes/interfaces are also globally visible and can be used for the creation.
| Subject | Details/Code Snippet |
| Elementary line types |
|
| Structured types |
|
| Table types |
|
| References as line types |
|
"Table declared inline in the context of an assignment
"The examples show the copying of a table including the content
"on the fly and creating the table in one step. The data type of the
"declared variable is determined by the right side.
DATA string_tab TYPE string_table.
DATA(it_a) = string_tab.
DATA(it_b) = it_a.
"Using FINAL for creating immutable variables
FINAL(it_c) = it_a.
"For example, it is not possible to modify such a table in the following position.
"Find more information in the Data Types and Data Objects cheat sheet.
"APPEND INITIAL LINE TO it_c.
"As shown below and in other cheat sheets, constructor operators
"are handy when creating internal tables in place. The following
"examples uses the VALUE operator and an internal table type. You
"may also use the NEW operator to create anonymous data objects.
DATA(it_d) = VALUE string_table( ( `aaa` )
( `bbb` ) ).
TYPES it_type TYPE TABLE OF zdemo_abap_carr with empty key.
DATA(it_e) = VALUE it_type( ( carrid = 'XY' carrname = 'XY Airlines' )
( carrid = 'YZ' carrname = 'Air YZ' ) ).
"Not providing any table lines means the table is initial
"and has the same effect as the declaration of it_g.
DATA(it_f) = VALUE string_table( ).
DATA it_g TYPE string_table.
DATA(it_h) = VALUE it_type( ).
DATA it_i TYPE it_type.
"Excursion
"Table declared inline in the context of a SELECT statement;
"a prior extra declaration of an internal table is not needed.
SELECT * FROM zdemo_abap_fli INTO TABLE @DATA(it_j).
"Instead of
DATA it_k TYPE TABLE OF zdemo_abap_fli WITH EMPTY KEY.
SELECT * FROM zdemo_abap_fli INTO TABLE @it_k.
"Using FINAL
SELECT * FROM zdemo_abap_fli INTO TABLE @FINAL(it_l).[!NOTE] Various ABAP statements populate internal tables. The following sections cover a selection. ABAP SQL SELECT statements are covered further down and in the ABAP SQL cheat sheet.
To copy internal table content from one table to another, you can use the assignment operator. Such an assignment (without a constructor expression) deletes the existing content in the target internal table. The example below assumes that the source and target table have compatible line types. Using inline declaration is helpful to avoid an additional internal table declaration with an appropriate type.
itab = itab2.
DATA(itab3) = itab.
FINAL(itab4) = itab.[!NOTE] - Internal tables can only be assigned to internal tables. - Internal tables can be assigned to each other if their line types are compatible or convertible. - An assignment can trigger an uncatchable exception if, for example, the target table is assigned a duplicate of a unique primary table key or secondary table key. - More information: Conversion Rules for Internal Tables
You can use the ABAP keywords INSERT and APPEND to add lines to internal tables.
🟢 Click to expand for notes on using
APPEND and INSERT
APPEND ...by an index. When the statement is used, the system field sy-tabix is set to the index of the recently added line. sy-tabix is always set to the index with respect to the primary table index.
lines are appended only if they match the sort order, and no duplicate entries are created if the primary table key is unique. Therefore, INSERT should be used when adding lines to sorted tables.
INSERT ...specifying the target index). In doing so, all the following lines are moved down one position.
the table in case of standard tables. However, when using INSERT, sy-tabix is not set unlike APPEND. In case of sorted tables, the line is automatically inserted at the correct position.
INSERT, you can specify the position in the internal table at which lines are inserted after INTO. ... INTO TABLE itab ...: Line is inserted in ...... INTO itab INDEX n: Possible for index tables. The line is inserted before the line with the line number n in the primary table index.keys. If a duplicate is inserted, the insertion fails and the system field sy-subrc is set to 4.
INSERT statement, you are prepared for a potential change of internal table definitions. It covers all table and key types. Consider potential issues when you change table/key types, and you use APPEND in your code. The ABAP cheat sheet examples often use APPEND statements, as standard tables are frequently used and those considerations are not relevant here. Additionally, explore constructor expressions for adding lines to internal tables.
| Subject | Details/Code Snippet |
| Adding a line to an internal table |
The example shows both a structure that is created using the |
| Adding an initial line |
... to an internal table without providing any field values. |
| Adding a line and assigning the added line to a field symbol or data reference variable |
|
| Adding all lines from another internal table |
|
| Adding multiple lines from another internal table with a specified index range |
|
| Inserting one line represented by a structure or multiple lines from another internal table at a specific position |
|
The constructor expressions can be specified in/with various positions/statements in ABAP. In most of the following snippets, simple assignments are demonstrated.
[!NOTE] The following sections cover a selection. There are more constructor expressions used in the context of internal tables (e.g. for creating internal tables). Find more details in the Constructor Expressions cheat sheet.
As mentioned above, table lines that are constructed inline as arguments to the VALUE operator, for example, can be added to internal tables. In the following cases, internal tables are populated using constructor expressions in the context of assignments.
| Subject | Details/Code Snippet |
| Populating an existing internal table by assigning an internal table that is constructed inline |
In the example below, the internal table is populated by assigning an internal table that is constructed inline with the [!NOTE] - The extra pair of parentheses represents a table line. The
|
| Creating an internal table by inline declaration and adding lines with a constructor expression |
|
BASE addition: Adding new lines without deleting existing content |
When you use the above assignments to an existing internal table ( |
LINES OF addition: Adding lines of other tables |
... using the |
Iteration expressions with FOR |
Using the |
| Subject | Details/Code Snippet |
| Copying the content of another (incompatible) internal table |
... using the
|
BASE/KEEPING TARGET LINES additions: Copying content and retaining existing content |
... using the |
MAPPING addition: Assigning components using mapping relationships |
|
EXCEPT addition: Excluding components from the assignment |
... using the
|
DISCARDING DUPLICATES addition: Preventing runtime errors when duplicate lines are assigned |
... to the target table that is defined to accept only unique keys using the
|
DEEP/EXPANDING NESTED TABLES additions: Copying data from deep internal tables |
|
CORRESPONDING with lookup table |
|
[!NOTE] The CL_ABAP_CORRESPONDING class can be used for assignments. Find an example in the Released ABAP Classes cheat sheet.
To create an internal table by copying data from another internal table and filtering out lines that do not meet the WHERE condition, you can use the FILTER operator.
FILTER operator constructs an internal table according to a specified type (which can be an explicitly specified, non-generic table type or the # character as a symbol for the operand type before the first parenthesis).
existing internal table based on conditions specified in a WHERE clause. Note that the table type of the existing internal table must be convertible into the specified target type.
table](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abenconstructor_expr_filter_table.htm).
| Addition | Details |
|---|---|
USING KEY | Specifies the table key used to evaluate the WHERE condition: either a sorted key or a hash key. If the internal table does not have either of these, it must have a secondary table key, which must be specified after USING KEY. |
EXCEPT | Specifying EXCEPT means that those lines of the existing table are used that do not meet the condition specified in the WHERE clause. If EXCEPT is not specified, those lines of the existing table that meet the condition are used. |
Examples:
"FILTER on conditions based on single values
"Assumption: The component num is of type i.
DATA itab1 TYPE SORTED TABLE OF struc WITH NON-UNIQUE KEY num.
DATA itab2 TYPE STANDARD TABLE OF struc WITH NON-UNIQUE SORTED KEY sec_key COMPONENTS num.
DATA itab3 TYPE HASHED TABLE OF struc WITH UNIQUE KEY num.
"The lines meeting the condition are respected.
"Note: The source table must have at least one sorted or hashed key.
"Here, the primary key is used
DATA(f1) = FILTER #( itab1 WHERE num >= 3 ).
"USING KEY primary_key explicitly specified; same as above
DATA(f2) = FILTER #( itab1 USING KEY primary_key WHERE num >= 3 ).
"EXCEPT addition
DATA(f3) = FILTER #( itab1 EXCEPT WHERE num >= 3 ).
DATA(f4) = FILTER #( itab1 EXCEPT USING KEY primary_key WHERE num >= 3 ).
"Secondary table key specified after USING KEY
DATA(f5) = FILTER #( itab2 USING KEY sec_key WHERE num >= 4 ).
DATA(f6) = FILTER #( itab2 EXCEPT USING KEY sec_key WHERE num >= 3 ).
"Note: In case of a hash key, exactly one comparison expression for each key component is allowed;
"only = as comparison operator possible.
DATA(f7) = FILTER #( itab3 WHERE num = 3 ).
"Using a filter table
"In the WHERE condition, the columns of source and filter table are compared.
"Those lines in the source table are used for which at least one line in the filter
"table meets the condition. EXCEPT and USING KEY are also possible.
DATA filter_tab1 TYPE SORTED TABLE OF i
WITH NON-UNIQUE KEY table_line.
DATA filter_tab2 TYPE STANDARD TABLE OF i
WITH EMPTY KEY
WITH UNIQUE SORTED KEY line COMPONENTS table_line.
DATA(f8) = FILTER #( itab1 IN filter_tab1 WHERE num = table_line ).
"EXCEPT addition
DATA(f9) = FILTER #( itab1 EXCEPT IN filter_tab1 WHERE num = table_line ).
"USING KEY is specified for the filter table
DATA(f10) = FILTER #( itab2 IN filter_tab2 USING KEY line WHERE num = table_line ).
"USING KEY is specified for the source table, including EXCEPT
DATA(f11) = FILTER #( itab2 USING KEY sec_key EXCEPT IN filter_tab2 WHERE num = table_line ).[!NOTE] More constructor expressions are available to deal with internal tables, for example the REDUCE operator. Find more information and examples in the Constructor Expression cheat sheet.
Using the instance operator NEW, you can create anonymous data objects, such as anonymous internal tables. You can access the lines, components or the entire data objects by dereferencing. For more information, refer to the Dynamic Programming and Constructor Expressions cheat sheets.
TYPES: BEGIN OF s,
a TYPE c LENGTH 3,
b TYPE i,
END OF s,
tab_type TYPE TABLE OF s WITH EMPTY KEY.
"Creating and populating an anonymous data object
DATA(dref_tab) = NEW tab_type( ( a = 'aaa' b = 1 )
( a = 'bbb' b = 2 ) ).
"Access by dereferencing
DATA(copy_deref_itab) = dref_tab->*.
DATA(read_line) = dref_tab->*[ 2 ].
DATA(read_comp) = dref_tab->*[ 1 ]-a.
dref_tab->*[ 1 ]-a = 'zzz'.
ASSERT dref_tab->*[ 1 ]-a = 'zzz'.
INSERT VALUE s( a = 'yyy' b = 3 ) INTO TABLE dref_tab->*.Expand the following collapsible section for example code. To try it out, create a demo class named zcl_demo_abap. If it already exists, reuse it. Otherwise, create a new class with a different name. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example is set up to display output in the console, but only for few data objects. You may want to set a break point at the earliest possible position and walk through the example in the debugger. This will allow you to double-click on data objects and observe how the different statements affect their contents.
🟢 Click to expand for a code example
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
"Populating internal tables by adding a line (structure) using
"APPEND ... TO/INSERT ... INTO
TYPES: BEGIN OF st_a,
num TYPE i,
str TYPE string,
char TYPE c LENGTH 2,
END OF st_a,
ty_tab_a TYPE TABLE OF st_a WITH EMPTY KEY.
DATA it_a TYPE ty_tab_a.
"Adding a line created inline
APPEND VALUE #( num = 1 str = `A` char = 'bb' ) TO it_a.
INSERT VALUE #( num = 2 str = `C` char = 'dd' ) INTO TABLE it_a.
"Adding an existing line
DATA(struc_a) = VALUE st_a( num = 3 str = `E` char = 'ff' ).
"Structure whose components are assigned individually using the
"structure component selector
DATA struc_b TYPE st_a.
struc_b-num = 4.
struc_b-str = `G`.
struc_b-char = 'hh'.
APPEND struc_a TO it_a.
INSERT struc_b INTO TABLE it_a.
**********************************************************************
"INITIAL LINE addition
"Adding an initial line
APPEND INITIAL LINE TO it_a.
INSERT INITIAL LINE INTO TABLE it_a.
**********************************************************************
"ASSIGNING/REFERENCE INTO additions
"Adding a line and assigning the added line to a field symbol or
"data reference variable
"Creating field symbol inline
APPEND VALUE st_a( num = 5 str = `I` char = 'jj' ) TO it_a ASSIGNING FIELD-SYMBOL(<fs_a>).
"Addressing individual components
ASSERT <fs_a>-num = 5.
<fs_a>-num = 123.
FIELD-SYMBOLS <fs_b> TYPE st_a.
DATA(struc_c) = VALUE st_a( num = 6 ).
INSERT struc_c INTO TABLE it_a ASSIGNING <fs_b>.
<fs_b>-str = `K`.
"Adding an initial line
"The examples use data reference variables.
"Using inline declaration
APPEND INITIAL LINE TO it_a REFERENCE INTO DATA(dref_a).
dref_a->num = 7.
DATA dref_b TYPE REF TO st_a.
INSERT INITIAL LINE INTO TABLE it_a REFERENCE INTO dref_b.
dref_b->num = 8.
DO 3 TIMES.
APPEND INITIAL LINE TO it_a REFERENCE INTO dref_b.
dref_b->* = VALUE #( num = sy-index str = sy-index char = sy-index ).
ENDDO.
**********************************************************************
"LINES OF addition
"Adding all lines from another internal table
"Adding lines to one internal table that are all added to
"another one
DATA it_b TYPE ty_tab_a.
INSERT VALUE #( num = 99 str = `L` char = 'mm' ) INTO TABLE it_b.
INSERT VALUE #( num = 100 str = `N` char = 'oo' ) INTO TABLE it_b.
APPEND LINES OF it_b TO it_a.
INSERT LINES OF it_b INTO TABLE it_a.
**********************************************************************
"Adding multiple lines from another internal table with
"a specified index range
APPEND LINES OF it_a FROM 5 TO 7 TO it_b.
APPEND LINES OF it_a FROM 12 TO it_b. "further lines up to the last line
APPEND LINES OF it_a TO 3 TO it_b. "all lines from the start up to the specified index
INSERT LINES OF it_a FROM 8 TO 10 INTO TABLE it_b.
**********************************************************************
"INDEX addition
"Inserting one line or multiple lines from another internal table at
"a specific position. To be used for index tables.
INSERT VALUE #( num = 9 str = `P` char = 'qq' ) INTO it_b INDEX 2.
INSERT LINES OF VALUE ty_tab_a( ( num = 10 str = `R` ) ( num = 11 str = `S` ) ) INTO it_b INDEX 5.
"FROM and TO can also be used
INSERT LINES OF it_a FROM 1 TO 3 INTO it_b INDEX 1.
**********************************************************************
"Using the VALUE operator
DATA(struc_d) = VALUE st_a( num = 11 str = `T` char = 'uu' ).
DATA it_c TYPE ty_tab_a.
"Adding an existing line and a line created inline
it_c = VALUE #( ( struc_d )
( num = 11 str = `V` char = 'ww' ) ).
"Creating an internal table by inline declaration and adding lines with VALUE
DATA(it_d) = VALUE ty_tab_a( ( num = 12 str = `X` char = 'yy' )
( num = 13 str = `Z` char = 'aa' )
( struc_d ) ).
"******* BASE addition *******
"Adding new lines without deleting existing content
it_d = VALUE #( BASE it_d ( num = 14 str = `B` char = 'cc' )
( num = 15 str = `D` char = 'ee' ) ).
"******* LINES OF addition *******
"Adding lines of other tables
it_d = VALUE #( ( LINES OF it_c ) ). "No BASE addition, existing content is deleted
it_c = VALUE #( BASE it_c ( num = 16 str = `F` char = 'gg' )
( LINES OF it_d ) ).
**********************************************************************
"CORRESPONDING operator / MOVE-CORRESPONDING statements
"Creating and populating demo internal tables
TYPES: BEGIN OF st_b,
num TYPE i,
char TYPE c LENGTH 2,
comp_a TYPE string,
END OF st_b,
ty_tab_b TYPE TABLE OF st_b WITH EMPTY KEY,
BEGIN OF st_c,
num TYPE i,
char TYPE c LENGTH 2,
comp_b TYPE string,
END OF st_c,
ty_tab_c TYPE TABLE OF st_c WITH EMPTY KEY.
DATA(it_e_original) = VALUE ty_tab_b( ( num = 1 char = 'aa' comp_a = `B` )
( num = 2 char = 'cc' comp_a = `D` ) ).
DATA(it_f_original) = VALUE ty_tab_c( ( num = 3 char = 'ee' comp_b = `F` )
( num = 4 char = 'gg' comp_b = `H` ) ).
DATA(it_e) = it_e_original.
DATA(it_f) = it_f_original.
"Copying the content of another internal table respecting identically
"named components
"it_f -> it_e
it_e = CORRESPONDING #( it_f ).
"it_e -> it_f
it_e = it_e_original.
MOVE-CORRESPONDING it_e TO it_f.
"******* BASE addition / /KEEPING TARGET LINES addition *******
"Copying content and retaining existing content
it_e = it_e_original.
it_f = it_f_original.
it_e = CORRESPONDING #( BASE ( it_e ) it_f ).
it_e = it_e_original.
it_f = it_f_original.
MOVE-CORRESPONDING it_e TO it_f KEEPING TARGET LINES.
"******* MAPPING addition *******
"Assigning components using mapping relationships
it_e = it_e_original.
it_f = it_f_original.
it_e = CORRESPONDING #( it_f MAPPING comp_a = comp_b ).
it_e = it_e_original.
it_f = CORRESPONDING #( BASE ( it_f ) it_e MAPPING comp_b = comp_a ). "Retaining content with BASE
"******* EXCEPT addition *******
"Excluding components from the assignment
it_e = it_e_original.
it_f = it_f_original.
it_e = CORRESPONDING #( it_f EXCEPT char ).
it_e = it_e_original.
it_f = CORRESPONDING #( it_e MAPPING comp_b = comp_a EXCEPT * ). "Mapping components
"******* DISCARDING DUPLICATES addition *******
"Preventing runtime errors when duplicate lines are assigned
it_e = VALUE #( ( num = 1 char = 'aa' comp_a = `B` )
( num = 1 char = 'cc' comp_a = `D` ) ).
DATA it_g TYPE SORTED TABLE OF st_b WITH UNIQUE KEY num.
"The statement commented out raises the runtime error ITAB_DUPLICATE_KEY.
"it_g = CORRESPONDING #( it_e ).
it_g = CORRESPONDING #( it_e DISCARDING DUPLICATES ).
"******* DEEP addition / EXPANDING NESTED TABLES addition *******
"Handling deep components such as nested internal tables
"Creating and populating demo internal tables
TYPES: BEGIN OF st_d,
char_a TYPE c LENGTH 2,
char_b TYPE c LENGTH 2,
END OF st_d,
BEGIN OF st_e,
char_b TYPE c LENGTH 2,
char_c TYPE c LENGTH 2,
END OF st_e,
BEGIN OF st_f,
comp1 TYPE c LENGTH 2,
comp2 TYPE c LENGTH 2,
comp3 TYPE TABLE OF st_d WITH EMPTY KEY,
END OF st_f,
BEGIN OF st_g,
comp2 TYPE c LENGTH 2,
comp3 TYPE TABLE OF st_e WITH EMPTY KEY,
comp4 TYPE c LENGTH 2,
END OF st_g,
ty_tab_d TYPE TABLE OF st_f WITH EMPTY KEY,
ty_tab_e TYPE TABLE OF st_g WITH EMPTY KEY.
DATA(it_h_original) = VALUE ty_tab_d(
( comp1 = 'a1' comp2 = 'a2' comp3 = VALUE #( ( char_a = 'a3' char_b = 'a4' ) ( char_a = 'a5' char_b = 'a6' ) ) )
( comp1 = 'b1' comp2 = 'b2' comp3 = VALUE #( ( char_a = 'b3' char_b = 'b4' ) ( char_a = 'b5' char_b = 'b6' ) ) ) ).
DATA(it_i_original) = VALUE ty_tab_e(
( comp2 = 'c1' comp3 = VALUE #( ( char_b = 'c2' char_c = 'c3' ) ( char_b = 'c4' char_c = 'c5' ) ) comp4 = 'c6' )
( comp2 = 'd1' comp3 = VALUE #( ( char_b = 'd2' char_c = 'd3' ) ( char_b = 'd4' char_c = 'd5' ) ) comp4 = 'd6' ) ).
DATA(it_h) = it_h_original.
DATA(it_i) = it_i_original.
"Compare the output of the examples
out->write( `******* CORRESPONDING *******` ).
"Note: The following example uses just CORRESPONDING. The outcome of the assignment
"is a different one compared to using DEEP. Refer to the ABAP Keyword Documentation
"for more details.
it_h = CORRESPONDING #( it_i ).
out->write( it_h ).
out->write( `******* CORRESPONDING ... DEEP *******` ).
it_h = CORRESPONDING #( DEEP it_i ).
out->write( it_h ).
out->write( `******* CORRESPONDING ... DEEP BASE *******` ).
it_h = it_h_original.
it_h = CORRESPONDING #( DEEP BASE ( it_h ) it_i ).
out->write( it_h ).
out->write( `******* MOVE-CORRESPONDING ... EXPANDING NESTED TABLES *******` ).
it_h = it_h_original.
MOVE-CORRESPONDING it_i TO it_h EXPANDING NESTED TABLES.
out->write( it_h ).
out->write( `******* MOVE-CORRESPONDING ... EXPANDING NESTED TABLES KEEPING TARGET LINES *******` ).
it_h = it_h_original.
MOVE-CORRESPONDING it_i TO it_h EXPANDING NESTED TABLES KEEPING TARGET LINES.
out->write( it_h ).
ENDMETHOD.
ENDCLASS.There are three different ways to specify the line to read:
The following code snippets include READ TABLE statements and table expressions to read from internal tables. Note that you can also use ABAP SQL SELECT statements to read from internal tables. This is covered further down.
| Target Area | Notes |
|
Data object |
|
|
Field symbol |
for example, using an inline declaration (
modifying the table line directly.
|
|
Data reference variable |
|
[!TIP] Which to use then? Since all syntax options basically provide similar
functionality, your use case, the performance or readability of the code may play a role. For more information, see the programming guidelines for the target area (F1 docu for standard ABAP). A use case for INTO dobj is when the table should not be changed using the copied table line. However, copying comes at a performance cost. Imagine that your table contains many columns or nested components. In this case, it is advisable not to copy at all (although you can use the TRANSPORTING addition to restrict the fields to be copied).
| Statement | Details/Code Snippet |
READ TABLE |
The following example shows |
| Table expressions |
Using table expressions, the read result is stored in a variable that can be declared inline. The number in the square brackets represents the index. A line that is not found results in an runtime error. To avoid an error, you can use a Find more information about syntax related to table expressions further down. |
| Avoiding an exception when using table expressions |
When you read a non-existent line using a table expression, you may not want to throw an exception. You can also embed the table expression in a constructor expression using the |
Lines can be read by explicitly specifying the table keys or the alias names, if any.
| Statement | Details/Code Snippet |
READ TABLE |
|
| Table expressions |
Find more information about syntax related to table expressions further down. |
The specified components used as keys need not be part of a table key.
| Statement | Details/Code Snippet |
READ TABLE |
|
| Table expressions |
|
When reading single lines in general, you can also address individual components of the line using the component selector - (or the object component selector -> or the dereferencing operator ->* in the case of data reference variables).
You can also use ASSIGN statements to assign components (and more) to field symbols. See the Example: Exploring READ TABLE Statements and Table Expressions below.
DATA(comp1) = it[ b = 2 ]-c.
READ TABLE it INTO DATA(wa) WITH KEY b = 2.
DATA(comp2) = wa-c.
READ TABLE it ASSIGNING FIELD-SYMBOL(<fs>) WITH KEY b = 2.
DATA(comp3) = <fs>-c.
READ TABLE it REFERENCE INTO DATA(dref) WITH KEY b = 2.
DATA(comp4) = dref->c.
"It is also possible to specify the dereferencing operator
"with the component selector.
DATA(comp5) = dref->*-c.
"Note: When using field symbols and data reference variables as target areas,
"and you modify their content (the examples above only show reads), you can directly
"edit the line in the internal table (because the field symbols/data reference
"variable points to the line). This does not apply to work areas that creates
"local copies. However, you can modify the content of the work area and then,
"for example, use a MODIFY statement to edit the line in the internal table as
"shown further down. sy-subrc) and stored in the target area, and what the index of the line is (sy-tabix). TYPES: BEGIN OF s_demo,
comp1 TYPE i,
comp2 TYPE i,
END OF s_demo,
tab_type_sorted TYPE SORTED TABLE OF s_demo WITH UNIQUE KEY comp1,
tab_type_std TYPE STANDARD TABLE OF s_demo WITH NON-UNIQUE KEY comp1,
tab_type_hashed TYPE HASHED TABLE OF s_demo WITH UNIQUE KEY comp1,
tab_type_std_sorted_sec_key TYPE STANDARD TABLE OF s_demo WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY sec_so COMPONENTS comp1,
tab_type_std_hash_sec_key TYPE STANDARD TABLE OF s_demo WITH EMPTY KEY
WITH UNIQUE HASHED KEY sec_ha COMPONENTS comp1. .
DATA(itab_sorted) = VALUE tab_type_sorted( ( comp1 = 1 comp2 = 11 )
( comp1 = 2 comp2 = 22 )
( comp1 = 3 comp2 = 33 ) ).
DATA itab_std TYPE tab_type_std.
itab_std = itab_sorted.
DATA itab_hashed TYPE tab_type_hashed.
itab_hashed = itab_sorted.
DATA itab_std_so_sec TYPE tab_type_std_sorted_sec_key.
itab_std_so_sec = itab_sorted.
DATA itab_std_ha_sec TYPE tab_type_std_hash_sec_key.
itab_std_ha_sec = itab_sorted.
"--------------- Line found ---------------
"Standard table
READ TABLE itab_std ASSIGNING FIELD-SYMBOL(<fs1>) WITH TABLE KEY primary_key COMPONENTS comp1 = 1.
ASSERT sy-subrc = 0.
ASSERT sy-tabix = 1.
ASSERT <fs1> IS ASSIGNED.
"Sorted table
READ TABLE itab_sorted ASSIGNING FIELD-SYMBOL(<fs2>) WITH TABLE KEY primary_key COMPONENTS comp1 = 1.
ASSERT sy-subrc = 0.
ASSERT sy-tabix = 1.
ASSERT <fs2> IS ASSIGNED.
"Note: sy-tabix is not set here as a hash key is used.
READ TABLE itab_hashed ASSIGNING FIELD-SYMBOL(<fs3>) WITH TABLE KEY primary_key COMPONENTS comp1 = 1.
ASSERT sy-subrc = 0.
ASSERT sy-tabix = 0.
ASSERT <fs3> IS ASSIGNED.
"Standard table, sorted secondary table key
READ TABLE itab_std_so_sec ASSIGNING FIELD-SYMBOL(<fs4>) WITH TABLE KEY sec_so COMPONENTS comp1 = 1.
ASSERT sy-subrc = 0.
ASSERT sy-tabix = 1.
ASSERT <fs4> IS ASSIGNED.
"Standard table, hashed secondary table key
READ TABLE itab_std_ha_sec ASSIGNING FIELD-SYMBOL(<fs5>) WITH TABLE KEY sec_ha COMPONENTS comp1 = 1.
ASSERT sy-subrc = 0.
ASSERT sy-tabix = 0.
ASSERT <fs5> IS ASSIGNED.
"--------------- Line not found ---------------
"Standard table, primary table key
READ TABLE itab_std ASSIGNING FIELD-SYMBOL(<fs6>) WITH TABLE KEY primary_key COMPONENTS comp1 = 10.
ASSERT sy-subrc = 4.
ASSERT sy-tabix = 0.
ASSERT <fs6> IS NOT ASSIGNED.
"Sorted table, primary table key
"Note: A binary search is performed in this case and a line was not found (sy-subrc = 8).
" The sy-tabix value is set to the number of table lines plus 1. The value 8 for sy-subrc
" also means not found, but indicates that sy-tabix is set in a different way.
READ TABLE itab_sorted ASSIGNING FIELD-SYMBOL(<fs7>) WITH TABLE KEY primary_key COMPONENTS comp1 = 10.
ASSERT sy-subrc = 8.
ASSERT sy-tabix = 4.
ASSERT <fs7> IS NOT ASSIGNED.
"Hashed table, primary table key
READ TABLE itab_hashed ASSIGNING FIELD-SYMBOL(<fs8>) WITH TABLE KEY primary_key COMPONENTS comp1 = 10.
ASSERT sy-subrc = 4.
ASSERT sy-tabix = 0.
ASSERT <fs8> IS NOT ASSIGNED.
"Standard table, sorted secondary table key
READ TABLE itab_std_so_sec ASSIGNING FIELD-SYMBOL(<fs9>) WITH TABLE KEY sec_so COMPONENTS comp1 = 10.
ASSERT sy-subrc = 8.
ASSERT sy-tabix = 4.
ASSERT <fs9> IS NOT ASSIGNED.
"Standard table, hashed secondary table key
READ TABLE itab_std_ha_sec ASSIGNING FIELD-SYMBOL(<fs10>) WITH TABLE KEY sec_ha COMPONENTS comp1 = 10.
ASSERT sy-subrc = 4.
ASSERT sy-tabix = 0.
ASSERT <fs10> IS NOT ASSIGNED.READ TABLE ... WHERE ... searches for the first line that matches a WHERE condition.sy-tabix is set when a line is found, while sy-subrc is set to either 0 or 4.WHERE condition can use comparison and predicate expressions (see the code snippet comments below for examples).WHERE condition.TRANSPORTING NO FIELDS and USING KEY additions are possible.WHERE conditions are possible.READ TABLE statement can be expressed using WITH KEY, this approach - instead of using a WHERE condition - is recommended as it is more performant. A syntax warning occurs but can be suppressed with the pragma ##read_where_ok.WHERE condition:"Creating and populating demo internal tables
TYPES: BEGIN OF s_demo,
comp1 TYPE i,
comp2 TYPE c LENGTH 5,
comp3 TYPE i,
comp4 TYPE c LENGTH 5,
END OF s_demo,
t_type_so TYPE SORTED TABLE OF s_demo WITH UNIQUE KEY comp1 WITH NON-UNIQUE SORTED KEY sk COMPONENTS comp3.
DATA(itab) = VALUE t_type_so( ( comp1 = 1 comp2 = 'lorem' comp3 = 30 comp4 = 'ipsum' )
( comp1 = 2 comp2 = 'dolor' comp3 = 20 comp4 = 'sit' )
( comp1 = 3 comp2 = 'amet' comp3 = 40 comp4 = 'hello' )
( comp1 = 4 comp2 = 'world' comp3 = 50 comp4 = 'ABAP' )
( comp1 = 5 comp2 = 'test' comp3 = 10 comp4 = '' ) ).
DATA wa TYPE s_demo.
*&---------------------------------------------------------------------*
*& WHERE condition with comparison expressions
*&---------------------------------------------------------------------*
"Examples: =/EQ, <>/NE, >/GT, </LT, >=,GE, <=/LE,
" CO, CN, CA, NA, CS, NS, CP, NP,
" [NOT] BETWEEN ... AND
" [NOT] IN ranges_tables
READ TABLE itab INTO wa WHERE comp1 > 3.
ASSERT sy-tabix = 4.
"'or' also available in other lines in this component, but the first found line
"is returned (lines 1, 2, 4)
READ TABLE itab INTO wa WHERE comp2 CS 'or'.
ASSERT sy-tabix = 1.
"'d' occurs in lines 2, 4
READ TABLE itab INTO wa WHERE comp2 CS 'd'.
ASSERT sy-tabix = 2.
READ TABLE itab INTO wa WHERE comp2 CS 'd'.
ASSERT sy-tabix = 2.
*&---------------------------------------------------------------------*
*& WHERE condition with predicate expressions
*&---------------------------------------------------------------------*
"Examples: IS [NOT] INITIAL
" IS [NOT] BOUND
" IS [NOT] INSTANCE OF
READ TABLE itab INTO wa WHERE comp1 > 4 AND comp4 IS INITIAL.
ASSERT sy-tabix = 5.
*&---------------------------------------------------------------------*
*& WITH KEY instead of WHERE condition
*&---------------------------------------------------------------------*
"Syntax warning in READ TABLE ... WHERE ... statements
READ TABLE itab INTO wa WHERE comp4 IS INITIAL.
ASSERT sy-tabix = 5.
"For a better performance, the previous statement should be
"replaced by a READ TABLE ... WITH KEY ... statement.
READ TABLE itab INTO wa WITH KEY comp4 = ''.
ASSERT sy-tabix = 5.
"You can also suppress the syntax warning by a pragma.
READ TABLE itab INTO wa WHERE comp4 IS INITIAL ##read_where_ok.
ASSERT sy-tabix = 5.
*&---------------------------------------------------------------------*
*& Further additions
*&---------------------------------------------------------------------*
"TRANSPORTING NO FIELDS addition is possible
READ TABLE itab TRANSPORTING NO FIELDS WHERE comp2 CS 'd'.
ASSERT sy-tabix = 2.
"USING KEY addition
READ TABLE itab USING KEY primary_key INTO wa WHERE comp2 CS 't'.
ASSERT sy-tabix = 3.
READ TABLE itab USING KEY sk INTO wa WHERE comp3 > 40.
ASSERT sy-tabix = 5.
*&---------------------------------------------------------------------*
*& Excursions
*&---------------------------------------------------------------------*
"Note the comparison rules for character-like data types
READ TABLE itab INTO wa WHERE comp2 = 'lorem' ##read_where_ok.
ASSERT sy-tabix = 1.
"In the following case, the length of the comp2 value increased to match the
"length of the specified text field, i.e. the surplus characters from the right
"side text field are not truncated. As a consequence, the line is not found.
DATA(some_text) = 'loremXYZ'.
READ TABLE itab INTO wa WHERE comp2 = some_text ##read_where_ok.
ASSERT sy-tabix = 0 AND sy-subrc <> 0.
"When using READ TABLE ... WITH KEY ... the behavior is different. In that
"case, the surplus characters are truncated because of a conversion. Therefore,
"the following statement finds a line.
READ TABLE itab INTO wa WITH KEY comp2 = some_text.
ASSERT sy-tabix = 1 AND sy-subrc = 0.
"Note: The read target can only be placed before WHERE conditions.
"The following statements are not possible.
"READ TABLE itab WHERE comp2 CS 'd' INTO wa.
"READ TABLE itab WHERE comp2 CS 'd' TRANSPORTING NO FIELDS.
"READ TABLE ... WHERE ... statements can replace LOOP AT ... WHERE ...
"statements including EXIT.
LOOP AT itab INTO wa WHERE comp2 CS 'd'.
ASSERT sy-tabix = 2.
EXIT.
ENDLOOP.
*&---------------------------------------------------------------------*
*& Dynamic WHERE condition
*&---------------------------------------------------------------------*
"Character-like data objects or standard tables with character-like line type
"can be specified
DATA(dyn_where_cond_str) = `comp2 CS 'd'`.
READ TABLE itab INTO wa WHERE (dyn_where_cond_str).
ASSERT sy-tabix = 2.
DATA(dyn_where_cond_tab) = VALUE string_table( ( `comp2` ) ( `CS` ) ( `'d'` ) ).
READ TABLE itab INTO wa WHERE (dyn_where_cond_tab).
ASSERT sy-tabix = 2.... TRANSPORTING NO FIELDS: It is only checked whether the line exists. No target area is specified. As mentioned above, the system fields are filled.
DATA(stringtab) = VALUE string_table( ( `abc` ) ( `def` ) ( `ghi` ) ).
READ TABLE stringtab WITH KEY table_line = `def` TRANSPORTING NO FIELDS.
ASSERT sy-subrc = 0.
ASSERT sy-tabix = 2.
READ TABLE stringtab WITH KEY table_line = `xyz` TRANSPORTING NO FIELDS.
ASSERT sy-subrc = 4.
ASSERT sy-tabix = 0.... TRANSPORTING ...: Specifying fields to be transported. The addition cannot be used with the ASSIGNING and REFERENCE additions.
TYPES: BEGIN OF demo_struc_tr,
comp1 TYPE i,
comp2 TYPE c LENGTH 3,
END OF demo_struc_tr,
ty_tab_demo_tr TYPE TABLE OF demo_struc_tr WITH EMPTY KEY.
DATA(it_tr) = VALUE ty_tab_demo_tr( ( comp1 = 1 comp2 = `abc` )
( comp1 = 2 comp2 = `def` ) ).
READ TABLE it_tr INTO DATA(line_tr) INDEX 1 TRANSPORTING comp2.
ASSERT line_tr-comp1 IS INITIAL.
"ALL FIELDS addition is available, explicitly stating that all fields
"should be transported.
READ TABLE it_tr INTO line_tr INDEX 1 TRANSPORTING ALL FIELDS.... COMPARING ...:
TRANSPORTING ...COMPARING ALL FIELDS compares all components, COMPARING NO FIELDS compares no componentssy-subrc: 0 is set if the content of compared components is identical,otherwise it is 2. Found lines are nevertheless assigned independently of the comparison.
TYPES: BEGIN OF demo_struc_compare,
comp1 TYPE i,
comp2 TYPE c LENGTH 3,
comp3 TYPE string,
END OF demo_struc_compare,
ty_tab_demo_compare TYPE TABLE OF demo_struc_compare WITH EMPTY KEY.
DATA(it_compare) = VALUE ty_tab_demo_compare( ( comp1 = 1 comp2 = 'abc' comp3 = `zzz` )
( comp1 = 2 comp2 = 'def' comp3 = `yyy` )
( comp1 = 2 comp2 = 'ghi' comp3 = `yyy` ) ).
DO 3 TIMES.
DATA(line_compare) = VALUE demo_struc_compare( comp1 = 1 ).
READ TABLE it_compare INTO line_compare INDEX sy-index COMPARING comp1.
CASE sy-index.
WHEN 1.
ASSERT sy-subrc = 0.
WHEN 2.
ASSERT sy-subrc = 2.
WHEN 3.
ASSERT sy-subrc = 2.
ENDCASE.
line_compare = VALUE demo_struc_compare( comp3 = `yyy` ).
READ TABLE it_compare INTO line_compare INDEX sy-index COMPARING comp3 TRANSPORTING comp2 comp3.
CASE sy-index.
WHEN 1.
ASSERT sy-subrc = 2.
ASSERT line_compare-comp1 IS INITIAL.
WHEN 2.
ASSERT sy-subrc = 0.
ASSERT line_compare-comp1 IS INITIAL.
WHEN 3.
ASSERT sy-subrc = 0.
ASSERT line_compare-comp1 IS INITIAL.
ENDCASE.
ENDDO.TYPES c3 TYPE c LENGTH 3.
TYPES ty_tab_casting TYPE TABLE OF c3 WITH EMPTY KEY.
DATA(it_casting) = VALUE ty_tab_casting( ( 'abc' ) ( 'def' ) ).
"Field symbol created inline (i.e. with the generic type 'data' implicitly)
"In this case, the CASTING and ELSE UNASSIGN additions are not available.
READ TABLE it_casting ASSIGNING FIELD-SYMBOL(<a>) INDEX 1.
"******* CASTING addition *******
"To use the addition, the field symbol must be either completely typed, or
"typed with one of the generic built-in ABAP types c, n, p, or x.
TYPES c2 TYPE c LENGTH 2.
FIELD-SYMBOLS <b> TYPE c2.
READ TABLE it_casting ASSIGNING <b> CASTING INDEX 2.
ASSERT <b> = 'de'.
"******* ELSE UNASSIGN addition *******
"The field symbol is unassigned if no table line is found. The addition
"can be used together with the CASTING addition.
"The following example loops 3 times across an internal table that has
"two lines. The sy-index value is used as the index value of the READ TABLE
"statement. The example demonstrates that when a line is not found, the field
"symbol is unassigned. In case of the first READ TABLE statement that does not
"specify ELSE UNASSIGN, the field symbol remains assigned.
FIELD-SYMBOLS <c> TYPE c2.
DO 3 TIMES.
READ TABLE it_casting ASSIGNING FIELD-SYMBOL(<d>) INDEX sy-index.
READ TABLE it_casting ASSIGNING FIELD-SYMBOL(<e>) ELSE UNASSIGN INDEX sy-index.
READ TABLE it_casting ASSIGNING <c> CASTING ELSE UNASSIGN INDEX sy-index.
IF sy-index = 3.
ASSERT <d> = `def`.
ASSERT <e> IS NOT ASSIGNED.
ASSERT <c> IS NOT ASSIGNED.
ENDIF.
ENDDO.You may stumble on the BINARY SEARCH addition to READ TABLE statements for an optimized access. However, it is recommended to use sorted tables or secondary table keys for an optimized access. Find information and an example in the expandable section below.
READ TABLE itab WITH KEY ... BINARY SEARCH ...🟢 Click to expand for more information and a code example
READ TABLE without BINARY SEARCH: Table is accessed linearlyREAD TABLE with BINARY SEARCH: Table is accessed using a binary search BINARY SEARCH addition is particularly more efficient for larger tables when accessing data often. BINARY SEARCH is suitable for standard tables that do not have a secondary key defined and when you need to make multiple read accesses to the table (however, note the costs of a previous sorting) BINARY SEARCH can only be used with index tables and not with hashed tables. If the table is a sorted table and the read access uses a free key, the addition can only be applied when the initial part of the table key is specified. I.e. if key components are a, b, and c, the addition can be used by specifying a alone, a and b, or a, b, and c. However, it means that the syntax can be specified without errors. The BINARY SEARCH specifiation has no effect and is redundant. Syntactically not possible with BINARY SEARCH (for example): b and c without a, or any other non-key component (because it cannot be sorted according to the non-key component). BINARY SEARCH addition is not available for table expressions. If KEY ... is specified, an optimized search is performed by default. There are no performance differences between using the READ TABLE statement and table expressions.To try out the following example, create a demo class named zcl_demo_abap and paste the code into it. After activation, choose F9 in ADT to execute the class. The example is set up to display output in the console.
The example includes multiple reads on standard internal tables using a READ TABLE statement ...
BINARY SEARCH by applying a sorting of the table a) every time and b) only once (the assumption is that after sorting once, only read accesses follow). BINARY SEARCH (by applying a sorting of the table a) every time and b) only once (the assumption is that after sorting once, only read accesses follow).The total runtime of multiple reads is determined to have a more representative runtime evaluation. The example is intended to demonstrate the performance gain using the BINARY SEARCH addition (and also using a secondary table key). Particularly, the result using BINARY SEARCH and SORT applied only once as well as the reads using the secondary table key should be relatively fast.
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
read_wo_binary_search1 RETURNING VALUE(result) TYPE decfloat34,
read_wo_binary_search2 RETURNING VALUE(result) TYPE decfloat34,
read_with_binary_search1 RETURNING VALUE(result) TYPE decfloat34,
read_with_binary_search2 RETURNING VALUE(result) TYPE decfloat34,
read_w_secondary_table_key RETURNING VALUE(result) TYPE decfloat34.
DATA: ts1 TYPE utclong,
runtime_tab TYPE TABLE OF decfloat34 WITH EMPTY KEY.
"Line type and internal table declarations
TYPES: BEGIN OF demo_struc,
idx TYPE i,
str TYPE string,
num TYPE i,
END OF demo_struc.
"Tables with empty primary table key
DATA itab_std TYPE STANDARD TABLE OF demo_struc WITH EMPTY KEY.
"Table with empty primary table key, secondary table key specified
DATA itab_sec TYPE STANDARD TABLE OF demo_struc WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY sk COMPONENTS idx.
CONSTANTS count TYPE i VALUE 1000.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
"Populating internal tables
DO count TIMES.
INSERT VALUE #( idx = sy-index
str = |INDEX{ sy-index }|
num = sy-index ) INTO TABLE itab_std.
ENDDO.
itab_sec = itab_std.
DATA(res1) = read_wo_binary_search1( ).
out->write( `Total runtime for read operations using READ TABLE with a free key, without the BINARY SEARCH addition, SORT always applied:` ).
out->write( |{ res1 STYLE = SIMPLE DECIMALS = 10 }| ).
DATA(res2) = read_wo_binary_search2( ).
out->write( `Total runtime for read operations using READ TABLE with a free key, without the BINARY SEARCH addition, SORT applied once:` ).
out->write( |{ res2 STYLE = SIMPLE DECIMALS = 10 }| ).
DATA(res3) = read_with_binary_search1( ).
out->write( `Total runtime for read operations using READ TABLE with a free key, with BINARY SEARCH addition, SORT always applied:` ).
out->write( |{ res3 STYLE = SIMPLE DECIMALS = 10 }| ).
DATA(res4) = read_with_binary_search2( ).
out->write( `Total runtime for read operations using READ TABLE with a free key, with BINARY SEARCH addition, SORT applied once:` ).
out->write( |{ res4 STYLE = SIMPLE DECIMALS = 10 }| ).
DATA(res5) = read_w_secondary_table_key( ).
out->write( `Total runtime for read operations using READ TABLE and a secondary table key that corresponds to the free key in the other table:` ).
out->write( |{ res5 STYLE = SIMPLE DECIMALS = 10 }| ).
ENDMETHOD.
METHOD read_wo_binary_search1.
ts1 = utclong_current( ).
DO count TIMES.
**********************************************************************
SORT itab_std BY str num.
READ TABLE itab_std WITH KEY idx = sy-index TRANSPORTING NO FIELDS.
**********************************************************************
ENDDO.
cl_abap_utclong=>diff( EXPORTING high = utclong_current( )
low = ts1
IMPORTING seconds = result ).
ENDMETHOD.
METHOD read_wo_binary_search2.
ts1 = utclong_current( ).
DO count TIMES.
**********************************************************************
IF sy-index = 1.
SORT itab_std BY str num.
ENDIF.
READ TABLE itab_std WITH KEY idx = sy-index TRANSPORTING NO FIELDS.
**********************************************************************
ENDDO.
cl_abap_utclong=>diff( EXPORTING high = utclong_current( )
low = ts1
IMPORTING seconds = result ).
ENDMETHOD.
METHOD read_with_binary_search1.
ts1 = utclong_current( ).
DO count TIMES.
**********************************************************************
SORT itab_std BY str num.
READ TABLE itab_std WITH KEY idx = sy-index BINARY SEARCH TRANSPORTING NO FIELDS.
**********************************************************************
ENDDO.
cl_abap_utclong=>diff( EXPORTING high = utclong_current( )
low = ts1
IMPORTING seconds = result ).
ENDMETHOD.
METHOD read_with_binary_search2.
ts1 = utclong_current( ).
DO count TIMES.
**********************************************************************
IF sy-index = 1.
SORT itab_std BY str num.
ENDIF.
READ TABLE itab_std WITH KEY idx = sy-index BINARY SEARCH TRANSPORTING NO FIELDS.
**********************************************************************
ENDDO.
cl_abap_utclong=>diff( EXPORTING high = utclong_current( )
low = ts1
IMPORTING seconds = result ).
ENDMETHOD.
METHOD read_w_secondary_table_key.
ts1 = utclong_current( ).
DO count TIMES.
**********************************************************************
READ TABLE itab_sec WITH TABLE KEY sk COMPONENTS idx = sy-index TRANSPORTING NO FIELDS.
**********************************************************************
ENDDO.
cl_abap_utclong=>diff( EXPORTING high = utclong_current( )
low = ts1
IMPORTING seconds = result ).
ENDMETHOD.
ENDCLASS.Expand the following collapsible section for example code. To try it out, create a demo class named zcl_demo_abap. If it already exists, reuse it. Otherwise, create a new class with a different name. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example is not set up to display output in the console. You may want to set a break point at the first position possible and walk through the example in the debugger. This will allow you to double-click on data objects and observe how the different statements affect their contents.
🟢 Click to expand for example code
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
"Creating and populating an internal table
TYPES: BEGIN OF st_h,
a TYPE i,
b TYPE c LENGTH 2,
c TYPE string,
END OF st_h,
ty_tab_f TYPE SORTED TABLE OF st_h WITH UNIQUE KEY a WITH NON-UNIQUE SORTED KEY sk COMPONENTS b.
DATA(it_j) = VALUE ty_tab_f( ( a = 1 b = 'zz' c = `B` )
( a = 2 b = 'xx' c = `D` )
( a = 3 b = 'yy' c = `F` ) ).
"******* 1) Reading a single line by index: READ TABLE statements *******
DATA struc_e TYPE st_h.
"In the following example ...
"- a work area is specified as target area.
"- USING KEY is not specified, i.e. the primary table index is used by default.
READ TABLE it_j INTO struc_e INDEX 2.
"Reading into a work area that is created inline
READ TABLE it_j INTO DATA(struc_f) INDEX 3.
"Specifying other target areas: Field symbols and data reference variables
"Here, the target areas are created inline
READ TABLE it_j ASSIGNING FIELD-SYMBOL(<fs_c>) INDEX 1.
READ TABLE it_j REFERENCE INTO DATA(dref_c) INDEX 1.
"******* USING KEY addition *******
"Reading by index and specifying which table index to use
"In the following example, the primary key is specified explicitly and
"addressed using the default name primary_key. It has the same effect
"as the statement below because the primary table index is used by
"default.
READ TABLE it_j INTO struc_e INDEX 1 USING KEY primary_key.
READ TABLE it_j INTO struc_e INDEX 1.
"Specifying the secondary key to use the secondary table index
READ TABLE it_j INTO struc_e INDEX 1 USING KEY sk.
"Using alias names
DATA it_j_alias TYPE SORTED TABLE OF st_h
WITH UNIQUE KEY primary_key ALIAS pk COMPONENTS a
WITH NON-UNIQUE SORTED KEY sk ALIAS sk_alias COMPONENTS b.
it_j_alias = it_j.
READ TABLE it_j_alias INTO struc_e INDEX 1 USING KEY pk.
READ TABLE it_j_alias INTO struc_e INDEX 1 USING KEY sk_alias.
"The following examples use the other key names
READ TABLE it_j_alias INTO struc_e INDEX 1 USING KEY primary_key.
READ TABLE it_j_alias INTO struc_e INDEX 1 USING KEY sk.
"******* Excursion: System field setting with READ TABLE statements *******
"Checking if a line is found and stored in the target area
READ TABLE it_j INTO struc_e INDEX 999.
IF sy-subrc = 0.
...
ELSE.
... "This branch is executed in the example since the line is not found.
ASSERT sy-tabix = 0.
ENDIF.
READ TABLE it_j INTO struc_e INDEX 1.
ASSERT sy-subrc = 0.
ASSERT sy-tabix = 1.
"******* 2) Reading a single line by index: Table expressions *******
"Many of the following examples show the assignment of a line
"using a table expression that is specified on the right side
"of an assignment.
"The target area is declared inline on the left side of an assignment.
DATA(struc_g) = it_j[ 1 ].
"If a table line is not found, a catchable exception is raised. It can
"be caught in a TRY control structure.
TRY.
struc_g = it_j[ 999 ].
CATCH cx_sy_itab_line_not_found.
ENDTRY.
"Table expression on the left side of an assignment
"The following example does not work since the example is a key table.
"it_j[ 1 ] = VALUE #( ).
"Creating and populating a table without a key
DATA it_j_no_key TYPE TABLE OF st_h WITH EMPTY KEY.
it_j_no_key = VALUE #( ( a = 1 b = 'a' c = `b` )
( a = 2 b = 'c' c = `d` )
( a = 3 b = 'e' c = `f` ) ).
it_j_no_key[ 1 ] = VALUE #( b = 'z' ).
ASSERT it_j_no_key[ 1 ]-a IS INITIAL.
ASSERT it_j_no_key[ 1 ]-b = 'z'.
ASSERT it_j_no_key[ 1 ]-c IS INITIAL.
it_j_no_key[ 2 ]-b = 'x'.
it_j_no_key[ 3 ] = VALUE #( ).
ASSERT it_j_no_key[ 3 ] IS INITIAL.
"******* KEY addition *******
"Reading by index and specifying which table index to use
"Primary key's default name
struc_g = it_j_alias[ KEY primary_key INDEX 1 ].
"Primary key alias
struc_g = it_j_alias[ KEY pk INDEX 1 ].
"Secondary key
struc_g = it_j_alias[ KEY sk INDEX 1 ].
"Secondary key alias
struc_g = it_j_alias[ KEY sk_alias INDEX 1 ].
"******* Excursion: Other ways of assignments with table expressions *******
"The examples only read by index. Reading by keys is also possible.
"Assigning a line read by index to a field symbol
FIELD-SYMBOLS <fs_d> TYPE st_h.
ASSIGN it_j[ 1 ] TO <fs_d>.
"The field symbol created inline has a generic type
ASSIGN it_j[ 1 ] TO FIELD-SYMBOL(<fs_d_inl>).
"Copying a table line via a table expression and embedding it in a
"constructor expression
struc_g = VALUE #( it_j[ 3 ] ).
"Reading into data reference variable using the REF operator
DATA(dref_d) = REF #( it_j[ 2 ] ).
"******* OPTIONAL/DEFAULT additions *******
"OPTIONAL: The default value is an initial data object with the data type
DATA(struc_h) = VALUE #( it_j[ 999 ] OPTIONAL ).
ASSERT struc_h = VALUE st_h( ).
"DEFAULT: Specifying a default value for table lines not found
"This value must be convertible to the type of the table expression.
"It can also be a table expression.
struc_h = VALUE #( it_j[ 999 ] DEFAULT it_j[ 1 ] ).
struc_h = VALUE #( it_j[ 999 ] DEFAULT VALUE st_h( c = `null` ) ).
**********************************************************************
"******* 1) Reading a single line using table keys: READ TABLE statements *******
"Creating and populating a demo internal table
TYPES: BEGIN OF st_i,
num TYPE i,
str TYPE string,
char TYPE c LENGTH 2,
END OF st_i.
DATA it_k TYPE SORTED TABLE OF st_i
WITH NON-UNIQUE KEY primary_key ALIAS pk COMPONENTS num
WITH NON-UNIQUE SORTED KEY sec_key ALIAS sk COMPONENTS char.
it_k = VALUE #( ( num = 1 str = `A` char = 'zz' )
( num = 2 str = `C` char = 'yy' )
( num = 3 str = `E` char = 'xx' ) ).
"The following examples use a work area as target. Other target areas are
"possible.
"Primary table key
READ TABLE it_k INTO DATA(struc_i) WITH TABLE KEY primary_key COMPONENTS num = 3.
"Primary table key alias
READ TABLE it_k INTO struc_i WITH TABLE KEY pk COMPONENTS num = 2.
"Secondary table key
READ TABLE it_k INTO struc_i WITH TABLE KEY sec_key COMPONENTS char = 'xx'.
"Secondary table key alias
READ TABLE it_k INTO struc_i WITH TABLE KEY sk COMPONENTS char = 'yy'.
"Reading a line based on keys specified in a work area
"It is a work area containing primary and secondary table key values.
"the line type must be compatible to the internal table.
TYPES st_j LIKE LINE OF it_k.
DATA(pr_key) = VALUE st_j( num = 1 ).
DATA(sec_key) = VALUE st_j( char = 'yy' ).
READ TABLE it_k FROM pr_key INTO struc_i.
"If USING KEY is not specified, the primary table key is used by default.
"Explicitly specifying the primary table key
READ TABLE it_k FROM pr_key USING KEY primary_key INTO struc_i.
"Primary table key alias
READ TABLE it_k FROM pr_key USING KEY pk INTO struc_i.
"Secondary table key
READ TABLE it_k FROM sec_key USING KEY sec_key INTO struc_i.
"Secondary table key alias
READ TABLE it_k FROM sec_key USING KEY sk INTO struc_i.
"******* 2) Reading a single line using table keys: Table expressions *******
"The addition COMPONENTS is optional. Therefore, the statement below
"has the same effect.
"Explicitly specifying the primary table key
struc_i = it_k[ KEY primary_key COMPONENTS num = 1 ].
struc_i = it_k[ KEY primary_key num = 1 ].
"Primary table key alias
struc_i = it_k[ KEY pk num = 2 ].
"Secondary table key
struc_i = it_k[ KEY sec_key char = 'xx' ].
"Secondary table key alias
struc_i = it_k[ KEY sk char = 'zz' ].
**********************************************************************
"Reading a single line using a free key
"Note: Instead if READ TABLE ... WITH TABLE KEY ..., it is ... WITH KEY.
READ TABLE it_k INTO DATA(struc_j) WITH KEY str = `A`.
struc_j = it_k[ str = `C` ].
**********************************************************************
"Examples for addressing individual components of read lines
"The assertions emphasize the difference of work areas and field
"symbols/data reference variables as target areas. Modifying the
"contents of the field symbols/data reference variables means
"modifying the internal table content.
DATA(comp_a) = it_k[ str = `C` ]-num.
READ TABLE it_k INTO DATA(struc_k) WITH KEY str = `A`.
struc_k-num = 123.
ASSERT NOT it_k[ str = `C` ]-num = 123.
DATA(comp_b) = struc_k-num.
READ TABLE it_k ASSIGNING FIELD-SYMBOL(<fs_e>) WITH KEY str = `C`.
"Note: The example table is a sorted table with 'num' as part of
"a unique key. The field value cannot be modified.
"<fs_e>-num = 123.
<fs_e>-char = 'hi'.
ASSERT it_k[ str = `C` ]-char = 'hi'.
DATA(comp_c) = <fs_e>-char.
READ TABLE it_k REFERENCE INTO DATA(dref_e) WITH KEY str = `E`.
dref_e->char = '##'.
ASSERT it_k[ str = `E` ]-char = '##'.
DATA(comp_d) = dref_e->num.
"It is also possible to specify the dereferencing operator together
"with the component selector.
DATA(comp_e) = dref_e->*-char.
**********************************************************************
"Excursion: Using ASSIGN statements to assign to field symbols
TYPES ty_tab_h TYPE TABLE OF st_i WITH EMPTY KEY.
DATA(it_l) = VALUE ty_tab_h( ( num = 1 str = `A` char = 'zz' )
( num = 2 str = `C` char = 'yy' )
( num = 3 str = `E` char = 'xx' ) ).
FIELD-SYMBOLS <fs_f> TYPE st_i-num.
ASSIGN it_l[ str = `C` ]-num TO <fs_f>.
<fs_f> = 99.
ASSERT it_l[ str = `C` ]-num = 99.
FIELD-SYMBOLS <fs_g> TYPE st_i-char.
ASSIGN it_l[ str = `C` ]-char TO <fs_g>.
<fs_g> = 'aa'.
"Field symbol created inline
ASSIGN <fs_g> TO FIELD-SYMBOL(<fs_h>).
<fs_h> = 'bb'.
READ TABLE it_l REFERENCE INTO DATA(dref_f) WITH KEY str = `E`.
ASSIGN dref_f->char TO FIELD-SYMBOL(<fs_i>).
<fs_i> = 'cc'.
"The following example use the generic type any so that anything
"can be assigned.
FIELD-SYMBOLS <fs_j> TYPE any.
"Assigning component
ASSIGN it_l[ str = `C` ]-num TO <fs_j>.
<fs_j> = 123.
"Assigning line
ASSIGN it_l[ str = `C` ] TO <fs_j>.
<fs_j> = VALUE st_i( num = 789 str = `U` char = 'vv' ).
"Assigning the entire table
ASSIGN it_l TO <fs_j>.
<fs_j> = VALUE ty_tab_h( ( num = 1 str = `AB` char = 'ap' ) ).
**********************************************************************
"COMPARING / TRANSPORTING additions to READ TABLE statements
"Comparing fields and specifying fields to be transported
DATA(it_m) = VALUE ty_tab_h( ( num = 1 str = `Z` char = '##' )
( num = 2 str = `Y` char = 'yy' )
( num = 3 str = `X` char = '##' )
( num = 4 str = `W` char = 'ww' )
( num = 5 str = `W` char = '##' )
( num = 6 str = `V` char = '##' )
( num = 7 str = `V` char = '##' )
( num = 7 str = `V` char = '##' )
( num = 8 str = `V` char = 'vv' ) ).
"******* TRANSPORTING NO FIELDS addition *******
"It is only checked whether the line exists. No target area is specified.
"The system fields sy-subrc and sy-tabix are filled. Check also the
"line_exists and line_index functions.
READ TABLE it_m WITH KEY str = `X` TRANSPORTING NO FIELDS.
ASSERT sy-subrc = 0.
DATA(sysubrc) = sy-subrc.
ASSERT sy-tabix = 3.
DATA(sytabix) = sy-tabix.
READ TABLE it_m WITH KEY str = `nope` TRANSPORTING NO FIELDS.
ASSERT sy-subrc = 4.
ASSERT sy-tabix = 0.
"******* TRANSPORTING ... addition *******
"Specifying fields to be transported; cannot be used with the ASSIGNING
"and REFERENCE additions
READ TABLE it_m INTO DATA(struc_l) INDEX 1 TRANSPORTING num char.
ASSERT struc_l-str IS INITIAL.
"If ALL FIELDS is specified, all fields are assigned, which corresponds to the
"example below.
READ TABLE it_m INTO struc_l INDEX 1 TRANSPORTING ALL FIELDS.
READ TABLE it_m INTO struc_l INDEX 1.
"******* COMPARING addition *******
"- Can be used together with and in front of TRANSPORTING ...
"- Compares the specified components
"- ALL FIELDS compares all components, NO FIELDS compares no components
"- Setting of sy-subrc: 0 is set if the content of compared components is identical,
" otherwise it is 2. Found lines are nevertheless assigned independently of the comparison.
"The following examples use a WHILE loop to read all table lines (sy-index represents the
"index value of the primary table index) into a work area.
"The work area is filled before the read for the comparison. Depending on the comparison
"result (by checking the sy-subrc value), the lines are added to different internal tables
"for demonstration purposes. In addition, the 'num' component value is added to a string.
"The examples explore several syntax options.
DATA struc_m LIKE LINE OF it_m.
DATA it_n LIKE it_m.
DATA it_o LIKE it_m.
DATA nums_subrc_0 TYPE string.
DATA nums_subrc_2 TYPE string.
DATA(subrc) = 0.
"Specifying ALL FIELDS
WHILE subrc = 0.
DATA(idx) = sy-index.
struc_m = VALUE #( num = 7 str = `V` char = '##' ).
READ TABLE it_m INTO struc_m INDEX idx COMPARING ALL FIELDS TRANSPORTING ALL FIELDS.
subrc = COND #( WHEN sy-subrc = 0 THEN 0 ELSE sy-subrc ).
IF subrc = 0.
APPEND struc_m TO it_n.
nums_subrc_0 &&= struc_m-num.
ELSEIF subrc = 2.
APPEND struc_m TO it_o.
nums_subrc_2 &&= struc_m-num.
subrc = 0.
ELSE.
EXIT.
ENDIF.
ENDWHILE.
ASSERT nums_subrc_0 = `77`.
ASSERT nums_subrc_2 = `1234568`.
CLEAR: subrc, struc_m, it_n, it_o, nums_subrc_0, nums_subrc_2.
"Specifying specific fields for the comparison and transport
WHILE subrc = 0.
idx = sy-index.
struc_m = VALUE #( num = 1234 str = `NOPE` char = '##' ).
READ TABLE it_m INTO struc_m INDEX idx COMPARING char TRANSPORTING num.
subrc = COND #( WHEN sy-subrc = 0 THEN 0 ELSE sy-subrc ).
IF subrc = 0.
APPEND struc_m TO it_n.
nums_subrc_0 &&= struc_m-num.
ELSEIF subrc = 2.
APPEND struc_m TO it_o.
nums_subrc_2 &&= struc_m-num.
subrc = 0.
ELSE.
EXIT.
ENDIF.
ENDWHILE.
ASSERT nums_subrc_0 = `135677`.
ASSERT nums_subrc_2 = `248`.
CLEAR: subrc, struc_m, it_n, it_o, nums_subrc_0, nums_subrc_2.
WHILE subrc = 0.
idx = sy-index.
struc_m = VALUE #( num = 9999 char = '##' str = `V` ).
READ TABLE it_m INTO struc_m INDEX idx COMPARING char str TRANSPORTING num.
subrc = COND #( WHEN sy-subrc = 0 THEN 0 ELSE sy-subrc ).
IF subrc = 0.
APPEND struc_m TO it_n.
nums_subrc_0 &&= struc_m-num.
ELSEIF subrc = 2.
APPEND struc_m TO it_o.
nums_subrc_2 &&= struc_m-num.
subrc = 0.
ELSE.
EXIT.
ENDIF.
ENDWHILE.
ASSERT nums_subrc_0 = `677`.
ASSERT nums_subrc_2 = `123458`.
**********************************************************************
"CASTING / ELSE UNASSIGN additions to READ TABLE statements
"Additions when assigning the read result to a field symbol
TYPES c3 TYPE c LENGTH 3.
TYPES ty_tab_g TYPE TABLE OF c3 WITH EMPTY KEY.
DATA(itq) = VALUE ty_tab_g( ( 'abc' ) ( 'def' ) ).
"Field symbol created inline (i.e. with the generic type 'data' implicitly)
"In this case, the CASTING and ELSE UNASSIGN additions are not available.
READ TABLE itq ASSIGNING FIELD-SYMBOL(<fs_k>) INDEX 1.
"******* CASTING addition *******
"To use the addition, the field symbol must be either completely typed, or
"typed with one of the generic built-in ABAP types c, n, p, or x.
TYPES c2 TYPE c LENGTH 2.
FIELD-SYMBOLS <fs_l> TYPE c2.
READ TABLE itq ASSIGNING <fs_l> CASTING INDEX 2.
ASSERT <fs_l> = 'de'.
"******* ELSE UNASSIGN addition *******
"The field symbol is unassigned if no table line is found. The addition
"can be used together with the CASTING addition.
"The following example loops 3 times across an internal table that has
"two lines. The sy-index value is used as the index value of the READ TABLE
"statement. The example demonstrates that when a line is not found, the field
"symbol is unassigned. In case of the first READ TABLE statement that does not
"specify ELSE UNASSIGN, the field symbol remains assigned.
DATA string_a TYPE string.
FIELD-SYMBOLS <fs_o> TYPE c2.
DO 3 TIMES.
READ TABLE itq ASSIGNING FIELD-SYMBOL(<fs_m>) INDEX sy-index.
READ TABLE itq ASSIGNING FIELD-SYMBOL(<fs_n>) ELSE UNASSIGN INDEX sy-index.
READ TABLE itq ASSIGNING <fs_o> CASTING ELSE UNASSIGN INDEX sy-index.
IF sy-index = 3.
ASSERT <fs_m> = `def`.
ASSERT <fs_n> IS NOT ASSIGNED.
ASSERT <fs_o> IS NOT ASSIGNED.
ENDIF.
ENDDO.
ENDMETHOD.
ENDCLASS.The previous sections also covered table expressions. This section provides a summary of their use.
[ ... ], specifying a table line.READ TABLE statements, enabling read and write operations in operand positions.READ TABLE, table expressions do not change the sy-tabix system field (except with ASSIGN).CX_SY_ITAB_LINE_NOT_FOUND.The following table demonstrates a selection of subjects where table expressions are applicable.
| Subject | Details/Code Snippet |
| Reading table lines by index |
|
| Reading table lines by table key |
|
| Reading table lines using free keys |
|
| Using the read result in various positions |
This is to emphasize that table expressions can be used in various read positions. The snippet shows a few examples. Check the notes in the ABAP Keyword Documentation topics about the use. |
| Assigning table lines to a field symbol |
|
Assigning table lines to a field symbol and setting sy> components |
|
| Data reference variables pointing to a table line |
|
Specifying table expressions as operands in constructor expressions with VALUE and REF |
|
| Specifying a default value for lines that are not found or marking as optional to avoid an exception |
|
| Field symbols and dereferenced data references specified before the square brackets |
The previous examples use concrete internal table names specified before the square brackets. Field symbols and dereferenced data references are also possible. |
| Reading multiple lines |
Using table expressions, you can read entire lines individually. This is just to emphasize that when reading multiple lines sequentially, |
| Reading individual components of table lines |
|
| Chaining table expressions in the context of nested internal tables |
|
| Table expression result having a reference type enabling chainings with the object component selector |
|
| Table expressions in write positions: Writes on the entire line |
Note that you cannot perform writes on entire lines in the context of key tables as key values cannot be changed. |
| Table expressions in write positions: Writes on individual components |
|
| Dynamic key and component name specifications |
For more information about dynamic programming, refer to the Dynamic Programming cheat sheet. |
| Pitfalls regarding table expressions |
|
If you are interested not only in single table lines, but in the entire table content or in specific parts of it, you can use LOOP AT statements to process table lines sequentially. As above, you can use multiple options for target areas: work area, field symbol, data reference. There are multiple additions to the LOOP AT statements to further restrict the table content to be processed.
Simple form:
"The target is an existing work area.
DATA wa LIKE LINE OF it.
LOOP AT it INTO wa.
"No addition of the loop statement; all lines are processed
"Statements in this block are relevant for each individual table line.
...
ENDLOOP.
"Work area declared inline
LOOP AT itab INTO DATA(wa_inl).
...
ENDLOOP.
"Field symbols
FIELD-SYMBOLS <fs> LIKE LINE OF it.
LOOP AT it ASSIGNING <fs>.
...
ENDLOOP.
LOOP AT it ASSIGNING FIELD-SYMBOL(<fs_inl>).
...
ENDLOOP.
"Data reference variables
DATA dref TYPE REF TO dbtab.
LOOP AT it REFERENCE INTO dref.
...
ENDLOOP.
LOOP AT it REFERENCE INTO DATA(dref_inl).
...
ENDLOOP.
"Table key specification (snippet uses example table from above)
"The specified table key affects the order in which the table lines
"are accessed and the evaluation of the other conditions.
LOOP AT it INTO wa USING KEY primary_key.
"LOOP AT it INTO wa USING KEY pk. "primary key alias
"LOOP AT it INTO wa USING KEY sec_key. "secondary key
"LOOP AT it INTO wa USING KEY sk. "secondary key alias
...
ENDLOOP.STEP addition, which is also available for other ABAP statements. sy-tabix is set to the number of the currently processed tableline. This is not true for hashed tables. There, sy-tabix is 0.
sy-tabix, youshould do so immediately after the LOOP statement (e.g. by storing the value in a helper variable) to avoid possible overwriting in statements contained in the loop block.
The additions of LOOP statements come into play when you want to restrict the table content to be respected by the loop because you do not want to loop over the entire table. Note that the examples only show work areas as target objects to hold the table line read. Other options are possible as shown above.
"FROM/TO: Only for index tables
"Specifying an index range
LOOP AT it INTO wa FROM 2 TO 5.
...
ENDLOOP.
"From specified line until the end
LOOP AT it INTO wa FROM 2.
...
ENDLOOP.
"From first line until the specified line
LOOP AT it INTO wa TO 5.
...
ENDLOOP.
"WHERE clause: Restricting lines based on logical expression
LOOP AT it INTO wa WHERE a > 1 AND b < 4.
...
ENDLOOP.
"No interest in the table content; only relevant system fields are populated
"Mandatory WHERE clause
LOOP AT it TRANSPORTING NO FIELDS WHERE a < 5.
...
ENDLOOP.Find more information in the STEP topic and in the section STEP Addition. Note that the addition is also available for other ABAP statements.
"STEP addition for defining the step size and the direction of the loop
"- Step size: Specified by the absolute value of an integer
"- Direction: Specified by a positive (forward loop) or negative
" (loop in reverse order) integer
"Reversing the loop order using a negative integer
"Each line is read indicated by the absolute value 1
LOOP AT it INTO wa STEP -1.
...
ENDLOOP.
"Forward loop by specifiying a positive integer
"In the example, every second line is read.
"Note: Omitting STEP means STEP 1 by default.
LOOP AT it INTO wa STEP 2.
...
ENDLOOP.
"STEP with other additions
"The example uses the additions FROM and TO.
"Note: If the value after STEP is negative, the value
"after FROM must be greater than the value after TO.
LOOP AT it INTO wa FROM 6 TO 3 STEP -2.
...
ENDLOOP.Iteration expressions with FOR as part of certain constructor expressions allow you to create content of an internal table by evaluating one or more source tables.
TYPES ty_int_tab TYPE TABLE OF i WITH EMPTY KEY.
DATA(int_table_a) = VALUE ty_int_tab( ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) ).
DATA int_table_b TYPE ty_int_tab.
int_table_b = VALUE #( FOR wa_b IN int_table_a ( wa_b * 2 ) ).
"Table Content: 2 / 4 / 6 / 8 / 10
"Instead of, for example, a LOOP statement as follows:
DATA int_table_c TYPE ty_int_tab.
LOOP AT int_table_a INTO DATA(wa_c).
INSERT wa_c * 3 INTO TABLE int_table_c.
ENDLOOP.
"Table content: 3 / 6 / 9 / 12 / 15The expressions are covered in the Constructor Expressions cheat sheet:
REDUCE that is based on iteration expressions: REDUCEABAP keywords such as
CONTINUE (terminating the current loop pass, continuing with the next)CHECK (conditional termination termination of the current loop pass if the logical expression is false), and EXIT (loop terminated completely), are available to exit and interrupt loops. Find more information in the Program Flow Logic cheat sheet.
In the following example, the loop is exited using the EXIT statement when a certain condition is met.
DATA(str_table) = VALUE string_table( ( `a` ) ( `b` ) ( `c` ) ( `d` ) ( `e` ) ( `f` ) ).
LOOP AT str_table INTO DATA(wa).
DATA(tabix) = sy-tabix.
IF wa = `e`.
EXIT.
ENDIF.
ENDLOOP.
ASSERT tabix = 5.SORT statement).sy-tabix) increases accordingly (in the case of index tables, sorted keys).sy-tabix) decreases accordingly.The following examples explore the insertion and deletion of lines when looping across internal tables:
"Creating and populating a demo standard internal table to
"work with in the example loops
TYPES: BEGIN OF s_loop_mod,
text TYPE string,
num TYPE i,
END OF s_loop_mod,
t_loop_mod TYPE TABLE OF s_loop_mod WITH EMPTY KEY.
"Inserting 10 entries into the demo table
DATA(itab_original) = VALUE t_loop_mod( FOR x = 1 WHILE x <= 10 ( text = x ) ).
DATA(itab) = itab_original.
*&---------------------------------------------------------------------*
*& Inserting a line after the current line
*&---------------------------------------------------------------------*
"The example inserts a line after the currently processed line
"using an INSERT statement and specifying the index value
"(sy-tabix value + 1). The 'num' component is assigned the
"current sy-tabix value.
"Note: In all statements, the sy-tabix value is stored in a
"variable right after the LOOP statement. Assume that multiple
"statements are included before the statement that actually uses
"the current sy-tabix value. Other statements that potentially
"change the sy-tabix value might interfere.
"An EXIT statement takes care of exiting the loop. In the example,
"all values of the 'num' component in the original table lines
"(except the first line) are initial as the loop is exited.
LOOP AT itab ASSIGNING FIELD-SYMBOL(<fs>).
DATA(tabix) = sy-tabix.
<fs>-num = tabix.
INSERT VALUE #( text = tabix ) INTO itab INDEX tabix + 1.
IF tabix = 50.
EXIT.
ENDIF.
ENDLOOP.
*Internal table content (parts of it):
*TEXT NUM
*1 1
*1 2
*2 3
*3 4
*4 5
*...
*48 49
*49 50
*50 0
*2 0
*3 0
*4 0
*...
*&---------------------------------------------------------------------*
*& Deleting a line after the current line
*&---------------------------------------------------------------------*
"The example deletes a line after the current line using a
"DELETE statement and the INDEX addition. The index value
"is specified using the current sy-tabix value + 1.
"The 'num' value in the resulting internal table includes
"the sy-tabix value.
itab = itab_original.
LOOP AT itab ASSIGNING <fs>.
tabix = sy-tabix.
<fs>-num = tabix.
DELETE itab INDEX tabix + 1.
ENDLOOP.
*Internal table content:
*TEXT NUM
*1 1
*3 2
*5 3
*7 4
*9 5
*&---------------------------------------------------------------------*
*& Inserting a line before the current line
*&---------------------------------------------------------------------*
"The example insert a line before the currently processed line using
"an INSERT statement. The current sy-tabix value is used as INDEX value,
"moving down the currently processed table line one position.
"In that case, the sy-tabix value increases accordingly.
"Logic:
"- For example, the first line is processed, sy-tabix has the value 1.
"- A line is inserted at this position, moving down the currently processed line
" one position. The moved line is then in the second position (as a new line exists
" in the first position).
"- In the next loop pass, the loop is continued with the third line, i.e.
" sy-tabix has the value 3 in the second loop pass.
"The example includes modifications of the table components. The 'num' value
"is assigned the table index value after inserting the new line. The
"'num' value of existing table lines includes the value of the index before
"inserting the new line + 1.
itab = itab_original.
FIELD-SYMBOLS <line> TYPE s_loop_mod.
DATA new_line_counter TYPE i.
DATA tabix_copy TYPE i.
LOOP AT itab ASSIGNING <fs>.
tabix = sy-tabix.
new_line_counter += 1.
"Asserting that sy-tabix value has changed accordingly.
IF tabix <> 1.
ASSERT tabix = tabix_copy + 2.
ENDIF.
DATA(new_line_text) = |---- New line { new_line_counter } ----|.
INSERT VALUE #( text = new_line_text ) INTO itab INDEX tabix ASSIGNING <line>.
DATA(idx_new) = line_index( itab[ text = new_line_text num = 0 ] ).
<line>-num = idx_new.
DATA(idx_existing) = line_index( itab[ text = <fs>-text num = 0 ] ).
DATA(new_text) = |{ <fs>-text }(existing line, index before insertion: { tabix })|.
<fs>-text = new_text.
<fs>-num = idx_existing.
tabix_copy = tabix.
ENDLOOP.
*Internal table content:
*TEXT NUM
*---- New line 1 ---- 1
*1 (existing line, index before insertion: 1) 2
*---- New line 2 ---- 3
*2 (existing line, index before insertion: 3) 4
*---- New line 3 ---- 5
*3 (existing line, index before insertion: 5) 6
*---- New line 4 ---- 7
*4 (existing line, index before insertion: 7) 8
*---- New line 5 ---- 9
*5 (existing line, index before insertion: 9) 10
*---- New line 6 ---- 11
*6 (existing line, index before insertion: 11) 12
*---- New line 7 ---- 13
*7 (existing line, index before insertion: 13) 14
*---- New line 8 ---- 15
*8 (existing line, index before insertion: 15) 16
*---- New line 9 ---- 17
*9 (existing line, index before insertion: 17) 18
*---- New line 10 ---- 19
*10 (existing line, index before insertion: 19) 20
*&---------------------------------------------------------------------*
*& Deleting a line before the current line
*&---------------------------------------------------------------------*
"The example explores the deletion of a line before the currently
"processed line. The previous line in the table is deleted if
"the value of 'text' (an integer was inserted) is an even number.
"The DELETE statement specifies the index with the current sy-tabix
"value - 1. On deletion, the sy-tabix value is decreased accordingly.
"Before a potential deletion, the currently processed table line is
"copied to another table to visualize the current sy-tabix value in
"the 'num' component.
itab = itab_original.
DATA itab_copy LIKE itab.
LOOP AT itab ASSIGNING <fs>.
tabix = sy-tabix.
<fs>-num = tabix.
INSERT <fs> INTO TABLE itab_copy.
TRY.
IF CONV i( <fs>-text ) MOD 2 = 0.
DELETE itab INDEX tabix - 1.
ENDIF.
CATCH cx_sy_conversion_no_number.
ENDTRY.
ENDLOOP.
*Internal table content (itab):
*TEXT NUM
*2 2
*4 3
*6 4
*8 5
*10 6
*Internal table content (itab_copy):
*TEXT NUM
*1 1
*2 2
*3 2
*4 3
*5 3
*6 4
*7 4
*8 5
*9 5
*10 6
*&---------------------------------------------------------------------*
*& Deleting the currently processed table line
*&---------------------------------------------------------------------*
"The example explores deleting the currently processed table line using
"a string table. So, the DELETE statement specifies the current sy-tabix
"value for INDEX. In that case, the next line moves up one position, and
"the sy-tabix value remains the same, i.e. when sy-tabix is 2, and the
"line is deleted, the value remains 2 and processes the line moved up.
"Creating and populating a demo internal table
DATA(str_tab) = VALUE string_table( ( `a` ) ( `#` ) ( `c` ) ( `#` ) ( `e` )
( `f` ) ( `g` ) ( `#` ) ( `i` ) ( `j` ) ).
LOOP AT str_tab REFERENCE INTO DATA(dref).
tabix = sy-tabix.
IF dref->* CS `#`.
DELETE str_tab INDEX tabix.
ENDIF.
ENDLOOP.
*Internal table content:
*a
*c
*e
*f
*g
*i
*j
*&---------------------------------------------------------------------*
*& Statements clearing the entire internal table are not allowed in loops
*&---------------------------------------------------------------------*
"The entire internal table cannot be deleted within loops.
"The following statements commented out are not possible.
LOOP AT str_tab REFERENCE INTO dref.
"CLEAR str_tab.
"str_tab = VALUE #( ).
ENDLOOP.[MODIFY [TABLE]](https://help.sap.com/doc/abapdocu_cp_index_htm/CLOUD/en-US/index.htm?file=abapmodify_itab.htm) statements provide multiple ways of changing the content of single and multiple table lines by specifying the table key or a table index, without first reading the lines into a target area. Apart from the statements that directly modify internal table lines, several other methods are available for changing table content. This section includes a selection of these methods.
The following example covers these aspects:
MODIFY [TABLE] statementsMODIFY statements with standard tables defining empty keysTRANSPORTING additionUSING KEY addition concerning primary and secondary table keys, including modifications within loops using loop_keyMODIFY and assigning a field symbol or setting a data referenceMODIFY statementsREAD TABLE statementsMODIFY statementVALUE and CORRESPONDING[!NOTE] - The example code contains various comments to provide more details and descriptions in place. Note that you may achieve the same results through different means. - Table modification aspects may also be mentioned in the context ofREAD TABLEandLOOP ATstatements as well as table expressions. Find more details, especially regarding syntax options, in the respective sections. They are not detailed out here. - Key fields of the primary table key of sorted and hashed tables are always read-only. If you try to modify a key field, a runtime error occurs. However, this is not checked until runtime. Find more information in the example. - RegardingMODIFYstatements: - The system fieldsy-subrcis set to0if at least one line was changed. It is set to4if no lines were changed. -MODIFY(and alsoDELETEandINSERT) statements can be specified with and without theTABLEaddition. WithTABLEmeans an index access. WithoutTABLEmeans an access via the table key. - Do not confuse the ABAP statementMODIFYwith the ABAP SQL statementMODIFY.
The following code snippet shows high-level syntax patterns. Find more details and an executable example in the collapsible section below.
*&---------------------------------------------------------------------*
*& MODIFY [TABLE] statements
*&---------------------------------------------------------------------*
"Modifying table lines based on key values
"'line' represents an existing structure including key values. The line
"can also be created inline using constructor expressions.
MODIFY TABLE it FROM line.
MODIFY TABLE it FROM VALUE #( a = 1 b = 2 ... ).
"Modifying table lines via the index
"Note:
"- It is only MODIFY, not MODIFY TABLE.
"- The example statement modifies the line with number 1 in the primary table index.
"- The order of additions may vary.
MODIFY it FROM line INDEX 1.
"Restricting the components modified using the TRANSPORTING addition
"Note:
"- Without the TRANSPORTING addition, the entire line is changed.
"- In case of sorted and hashed tables, key values cannot be specified.
MODIFY TABLE it FROM line TRANSPORTING b c.
"Modifications with the USING KEY addition concerning primary and secondary table keys
"If the addition is not specified, the primary table key is used. Otherwise, it is
"the explicitly specified table key that is used.
"The example statement with the explicitly specified primary table key has the same
"effect as the statement 'MODIFY it FROM line INDEX 1.'.
MODIFY it FROM line USING KEY primary_key INDEX 1.
"Using a secondary table key and an index specification for the secondary table index.
"The example also uses the TRANSPORTING addition to modify only specific fields.
MODIFY it FROM line USING KEY sec_key INDEX 1 TRANSPORTING c d.
"Modifying a line using MODIFY and assigning a field symbol or setting a data reference
"Both of the following examples specify the target inline.
MODIFY it INDEX 1 FROM line ASSIGNING FIELD-SYMBOL(<fs>).
MODIFY it INDEX 2 FROM line REFERENCE INTO DATA(dref).
"Modifying multiple lines in internal tables
"All lines matching the logical expression in the WHERE clause are modified
"as specified in 'line'.
"The additions TRANSPORTING and WHERE are both mandatory; USING KEY is optional.
MODIFY it FROM line TRANSPORTING b c WHERE a < 5.
*&---------------------------------------------------------------------*
*& Modifying read table lines in the context of READ TABLE statements
*&---------------------------------------------------------------------*
"Indirect modification using a subsequent MODIFY statement
READ TABLE it INDEX 1 INTO line.
IF sy-subrc = 0.
"Modifying individual components using the component selector
"Instead of assigning individual components, you can also use constructor expressions
"as shown below.
line-comp1 = 'ABAP'.
...
MODIFY TABLE it FROM line.
ENDIF.
"Direct modification
"Using a field symbol
READ TABLE it INDEX 1 ASSIGNING FIELD-SYMBOL(<line>).
IF sy-subrc = 0.
"Instead of assigning individual components, using constructor expressions
"line-comp1 = 'ABAP'.
"...
<line> = VALUE #( BASE <line> comp1 = ... comp2 = ... ).
ENDIF.
"Using a data reference variable
READ TABLE it INDEX 1 REFERENCE INTO DATA(ref).
IF sy-subrc = 0.
ref->* = VALUE #( BASE ref->* comp1 = ... comp2 = ... ).
ENDIF.
*&---------------------------------------------------------------------*
*& Table expressions
*&---------------------------------------------------------------------*
"Changing the entire table line of a standard table
it[ 1 ] = line.
it[ 1 ] = VALUE #( comp1 = ... comp2 = ... ).
"Changing the values of individual components
it[ 1 ]-comp1 = ...
it[ 2 ]-comp2 = ...
it[ 3 ]-comp3 = ...
*&---------------------------------------------------------------------*
*& Modifying table content within loops
*&---------------------------------------------------------------------*
"Modifying all table lines
"Indirect modification using a work area as target area and a MODIFY statement
LOOP AT it INTO DATA(wa).
wa-comp1 = ...
...
MODIFY it FROM wa.
ENDLOOP.
"Direct modification ...
"... using a field symbol as target area
LOOP AT it ASSIGNING FIELD-SYMBOL(<l>).
<l>-comp1 = ...
...
ENDLOOP.
"... using a data reference variable as target area
LOOP AT it REFERENCE INTO DATA(r).
r->comp1 = ...
...
ENDLOOP.
"Restricting the table lines processed in loops to modify only a
"restricted set of table entries
"Using a WHERE condition to restrict the table lines to be looped across
LOOP AT it REFERENCE INTO r WHERE comp1 >= 3.
r->comp2 = ...
...
ENDLOOP.
"Specifying the FROM and TO additions to set index values to
"restrict the table lines to be looped across.
"Note that you need not mandatorily specify both additions.
"When not specifying FROM, it means starting from 1. When not
"specifying TO, it means looping to the end of the table.
LOOP AT it REFERENCE INTO r FROM 2 TO 4.
r->comp2 = ...
...
ENDLOOP.Expand the following collapsible section for example code. To try it out, create a demo class named zcl_demo_abap and paste the code into it. Note that the example includes code in the global class and the CCIMP include (Local Types tab in ADT). After activation, choose F9 in ADT to execute the class. The example is set up to display output in the console. For more information on the example, see the inline comments.
🟢 Click to expand for example code
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES:
if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
TYPES: BEGIN OF s,
comp1 TYPE i,
comp2 TYPE c LENGTH 10,
comp3 TYPE i,
comp4 TYPE string,
END OF s,
BEGIN OF s4corr,
comp1 TYPE i,
comp2 TYPE c LENGTH 10,
compy TYPE i,
compz TYPE string,
END OF s4corr.
DATA: std_tab TYPE TABLE OF s WITH EMPTY KEY,
sorted_tab TYPE SORTED TABLE OF s WITH UNIQUE KEY comp1,
hashed_tab TYPE HASHED TABLE OF s WITH UNIQUE KEY comp1,
std_tab_w_keys TYPE TABLE OF s WITH NON-UNIQUE KEY comp1
WITH NON-UNIQUE SORTED KEY sk COMPONENTS comp3,
std_tab_w_uni_sk TYPE TABLE OF s WITH NON-UNIQUE KEY comp1
WITH UNIQUE SORTED KEY sk COMPONENTS comp3,
line TYPE s,
line4corr TYPE s4corr.
METHODS:
m01_modify_table_from IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m02_modify_table_sy_subrc IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m03_modify_table_from_std_tab IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m04_modify_index IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m05_modify_transporting IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m06_modify_using_key IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m07_modify_key_pitfalls IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m08_modify_using_key_loop_key IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m09_modify_fs_dref IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m10_modify_multiple_lines IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m11_read_table_modify_target IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m12_table_expressions IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m13_modifications_in_loops IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m14_restrict_modif_in_loops IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
prepare_itabs.
CLASS-METHODS set_example_divider IMPORTING out TYPE REF TO if_oo_adt_classrun_out
text TYPE string.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
"Dynamically calling methods of the class
"The method names are retrieved using RTTI. For more information, refer to the
"Dynamic Programming ABAP cheat sheet.
"Only those methods should be called that follow the naming convention M + digit.
DATA(methods) = CAST cl_abap_classdescr( cl_abap_typedescr=>describe_by_object_ref( me ) )->methods.
SORT methods BY name ASCENDING.
LOOP AT methods INTO DATA(meth_wa).
TRY.
"The find function result indicates that the method name begins (offset = 0) with M and a digit.
IF find( val = meth_wa-name pcre = `^M\d` case = abap_false ) = 0.
"Preparing the demo internal tables before each method call
prepare_itabs( ).
CALL METHOD (meth_wa-name) EXPORTING out = out text = CONV string( meth_wa-name ).
ENDIF.
CATCH cx_root INTO DATA(error).
out->write( error->get_text( ) ).
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD m01_modify_table_from.
set_example_divider( out = out text = |{ text }: Modifying table lines based on key values using MODIFY statements| ).
"After FROM a line with a matching line type is expected.
"The key values determine the line to be modified.
"Populating a structure to have a line based on which an internal table
"line is modified.
"The line includes key values.
line = VALUE #( comp1 = 1 comp2 = '###' comp3 = 555 comp4 = `ABAP` ).
MODIFY TABLE sorted_tab FROM line.
MODIFY TABLE hashed_tab FROM line.
MODIFY TABLE std_tab_w_keys FROM line.
"The following structure does not include all components. Therefore,
"these values are initial and the existing component values in the
"internal table are assigned the initial value.
line = VALUE #( comp1 = 2 comp2 = '***' ).
MODIFY TABLE sorted_tab FROM line.
MODIFY TABLE hashed_tab FROM line.
MODIFY TABLE std_tab_w_keys FROM line.
"The line can also be constructed inline
MODIFY TABLE sorted_tab FROM VALUE #( comp1 = 3 comp2 = 'ZZZ' comp3 = 123 comp4 = `YYY` ).
MODIFY TABLE hashed_tab FROM VALUE #( comp1 = 3 comp2 = 'XXX' comp3 = 456 comp4 = `WWW` ).
MODIFY TABLE std_tab_w_keys FROM VALUE #( comp1 = 3 comp2 = 'VVV' comp3 = 789 comp4 = `UUU` ).
out->write( data = sorted_tab name = `sorted_tab` ).
out->write( |\n| ).
out->write( data = hashed_tab name = `hashed_tab` ).
out->write( |\n| ).
out->write( data = std_tab_w_keys name = `std_tab_w_keys` ).
ENDMETHOD.
METHOD m02_modify_table_sy_subrc.
set_example_divider( out = out text = |{ text }: MODIFY statements and setting sy-subrc| ).
line = VALUE #( comp1 = 1 comp2 = '###' comp3 = 555 comp4 = `ABAP` ).
MODIFY TABLE sorted_tab FROM line.
IF sy-subrc = 0.
out->write( |sy-subrc value: { sy-subrc }| ).
out->write( `Table line was modified` ).
ELSE.
out->write( |sy-subrc value: { sy-subrc }| ).
out->write( `Table line was not modified` ).
ENDIF.
out->write( |\n| ).
"The following example tries to modify a table line based
"on a non-existant key.
line = VALUE #( comp1 = 1000 ).
MODIFY TABLE sorted_tab FROM line.
IF sy-subrc = 0.
out->write( |sy-subrc value: { sy-subrc }| ).
out->write( `Table line was modified` ).
ELSE.
out->write( |sy-subrc value: { sy-subrc }| ).
out->write( `Table line was not modified` ).
ENDIF.
out->write( |\n| ).
out->write( |\n| ).
out->write( data = sorted_tab name = `sorted_tab` ).
ENDMETHOD.
METHOD m03_modify_table_from_std_tab.
set_example_divider( out = out text = |{ text }: MODIFY TABLE statements with standard tables defining empty keys| ).
"Note:
"- Such statements should be avoided due to their non-intuitive semantics.
"- These statements always modify the first line of the table.
"- A syntax warning is displayed. The statements are included in the example
" only for illustration purposes.
"Although the line with comp1, for example, is not found, the first line
"is modified by default.
"To check out the behavior, uncomment the code.
* line = VALUE #( comp1 = 100 comp2 = 'AAA' comp3 = 1000 comp4 = `BBB` ).
*
* MODIFY TABLE std_tab FROM line.
*
* out->write( data = std_tab name = `std_tab` ).
* out->write( |\n| ).
*
* line = VALUE #( comp1 = 200 comp2 = 'CCC' comp3 = 2000 comp4 = `DDD` ).
*
* MODIFY TABLE std_tab FROM line.
*
* out->write( data = std_tab name = `std_tab` ).
ENDMETHOD.
METHOD m04_modify_index.
set_example_divider( out = out text = |{ text }: Modifications via index using MODIFY| ).
"Note that when using the INDEX addition, the statement is MODIFY, not MODIFY TABLE.
line = VALUE #( comp1 = 100 comp2 = 'AA' comp3 = 1000 comp4 = `BB` ).
"Modifying the first line according to the primary table index.
"Note the standard table with empty key. Unlike shown in another method, this modify
"operations work for these tables as the concrete index is specified.
MODIFY std_tab INDEX 5 FROM line.
MODIFY std_tab_w_keys INDEX 4 FROM line.
out->write( data = std_tab name = `std_tab` ).
out->write( |\n| ).
out->write( data = std_tab_w_keys name = `std_tab_w_keys` ).
"--- Note ---
"- The previous examples illustrate table line modification based on an
" existing line. In that line, all components are specified, including the
" primary key field (which is in the case above true for the second standard
" specifying a concrete primary table key). Primary table key fields are not
" write-protected. These fields are non-unique by default.
"- In case of sorted and hashed tables, their primary key fields are write-protected
" and cannot be modified. Therefore, the following, commented out statements
" would cause a runtime error.
"- See the examples about the TRANSPORTING addition, with which key fields can
" be excluded. Without the TRANSPORTING addition, all table line components
" are modified.
"- In case of the example statement using the hashed table, a runtime error is
" caused since explicit or implicit index operations cannot be used on hashed
" tables (regarding the primary table index).
* MODIFY sorted_tab INDEX 3 FROM line.
* MODIFY hashed_tab INDEX 2 FROM line.
ENDMETHOD.
METHOD m05_modify_transporting.
set_example_divider( out = out text = |{ text }: Modifications using the TRANSPORTING addition| ).
"Note:
"- The TRANSPORTING addition can be used to restrict the components to be modified.
"- If TRANSPORTING is not specified, all components are respected for modification.
"- Key fields of the primary table key in sorted and hashed tables are read-only and,
" thus, write-protected. In such cases, using the TRANSPORTING addition is useful to
" exnclude the key fields.
"- Key fields of the secondary table key are only read-only when the key is actually
" used.
line = VALUE #( comp1 = 100 comp2 = 'ABC' comp3 = 1000 comp4 = `DEF` ).
"Index-based modification of a line of a standard table defining an empty key
"The TRANSPORTING addition only specifies some of the fields. The others
"remain unmodified.
MODIFY std_tab INDEX 1 FROM line TRANSPORTING comp1 comp4.
"Key-based modification of a line of a standard table defining a primary table key
"and restricting the value assignment using TRANSPORTING
line = VALUE #( comp1 = 2 comp2 = 'GHI' comp3 = 1000 comp4 = `JKL` ).
MODIFY TABLE std_tab_w_keys FROM line TRANSPORTING comp2.
"Index-based modification of a line of a sorted table and excluding
"the key field.
line = VALUE #( comp1 = 100 comp2 = 'MNO' comp3 = 1000 comp4 = `PQR` ).
MODIFY sorted_tab INDEX 5 FROM line TRANSPORTING comp2 comp3 comp4.
line = VALUE #( comp1 = 5 comp2 = 'STU' comp3 = 1000 comp4 = `VWX` ).
"Not possible due to the key field protection
"MODIFY TABLE hashed_tab FROM line TRANSPORTING comp1 comp2.
MODIFY TABLE hashed_tab FROM line TRANSPORTING comp2 comp3.
out->write( data = std_tab name = `std_tab` ).
out->write( |\n| ).
out->write( data = std_tab_w_keys name = `std_tab_w_keys` ).
out->write( |\n| ).
out->write( data = sorted_tab name = `sorted_tab` ).
out->write( |\n| ).
out->write( data = hashed_tab name = `hashed_tab` ).
ENDMETHOD.
METHOD m06_modify_using_key.
set_example_divider( out = out text = |{ text }: Modifications using the USING KEY addition (1)| ).
"Note:
"If the USING KEY addition is not specified, the primary table key is used by default.
"Otherwise, it is the explicitly specified table key that is used.
line = VALUE #( comp1 = 100 comp2 = 'ABC' comp3 = 1000 comp4 = `DEF` ).
"Specifying the primary table key explicitly using the predefined name primary_key
MODIFY std_tab_w_keys USING KEY primary_key INDEX 1 FROM line.
DATA(mod1) = std_tab_w_keys[ 1 ].
"The result corresponds to the following statement. The primary table
"key is used by default.
MODIFY std_tab_w_keys INDEX 1 FROM line.
DATA(mod2) = std_tab_w_keys[ 1 ].
ASSERT mod1 = mod2.
"The following statement is not possible as the primary table key field
"is write-protected.
* MODIFY sorted_tab USING KEY primary_key INDEX 4 FROM line.
"Using the TRANSPORTING addition to exclude the secondary table key component
MODIFY sorted_tab USING KEY primary_key INDEX 4 FROM line TRANSPORTING comp2 comp3 comp4.
"Specifying a secondary table key
"The following statement is not possible as it would overwrite the
"secondary table key's value.
* MODIFY std_tab_w_keys USING KEY sk INDEX 1 FROM line.
"Using the TRANSPORTING addition to exclude the secondary table key component
"Note: In the example table, the secondary table key (comp3) is a non-unique sorted key.
"Therefore (and due to the demo table content), note the modified table line, which
"is the last line in the example table. The secondary table index is used here. Since
"the original comp3 value is 1 it comes first in the table index.
line = VALUE #( comp1 = 99 comp2 = 'GHI' comp3 = 999 comp4 = `JKL` ).
MODIFY std_tab_w_keys USING KEY sk INDEX 1 FROM line TRANSPORTING comp1 comp2 comp4.
out->write( data = std_tab_w_keys name = `std_tab_w_keys` ).
out->write( |\n| ).
out->write( data = sorted_tab name = `sorted_tab` ).
ENDMETHOD.
METHOD m07_modify_key_pitfalls.
set_example_divider( out = out text = |{ text }: Pitfalls when modifying table content regarding keys| ).
"This example highlights pitfalls regarding duplicate keys
"when modifying internal table content.
"The following line specifies a value for the secondary table key
"component comp3 that already exists.
line = VALUE #( comp1 = 2 comp2 = 'ABC' comp3 = 1 comp4 = `DEF` ).
"Avoiding to run the first statement. It will cause a runtime error.
"Instead, running the second statement that includes the TRANSPORTING
"addition, excluding comp3.
IF 1 = 2.
MODIFY TABLE std_tab_w_uni_sk FROM line.
ELSE.
MODIFY TABLE std_tab_w_uni_sk FROM line TRANSPORTING comp1 comp2 comp4.
ENDIF.
"The following example highlights pitfalls regarding write-protected
"key fields when modifying internal table content.
"The first statement would try to change a write-protected key field.
"Instead, running the second statement that includes the TRANSPORTING
"addition, excluding comp1.
IF 1 = 2.
MODIFY TABLE sorted_tab FROM line.
ELSE.
MODIFY TABLE sorted_tab FROM line TRANSPORTING comp2 comp3 comp4.
ENDIF.
"Secondary table key fields are only read-only when using the key.
"Otherwise, the fields can be modified.
line = VALUE #( comp1 = 1 comp2 = 'GHI' comp3 = 100 comp4 = `JKL` ).
"Comparing the behavior of the following two statements with internal
"tables defining a primary table key and one that also specifies
"a secondary table key.
"The statement with the table defining a unique secondary table key cannot
"be specified. A syntax error is displayed, therefore, the line is commented
"out.
* MODIFY std_tab_w_uni_sk USING KEY sk INDEX 4 FROM line.
"In contrast, the following example statement uses a table that defines a unique
"primary table key. It optionally specifies the USING KEY addition with the
"default primary key name primary_key. This statement does not show a syntax error.
"The key overwriting is only checked until runtime. The statement is not to be
"run as it would cause a runtime error because the key fields are
"write-protected.
IF 1 = 2.
MODIFY sorted_tab USING KEY primary_key INDEX 4 FROM line.
ELSE.
MODIFY sorted_tab USING KEY primary_key INDEX 4 FROM line TRANSPORTING comp2 comp3 comp4.
ENDIF.
out->write( data = std_tab_w_uni_sk name = `std_tab_w_uni_sk` ).
out->write( |\n| ).
out->write( data = sorted_tab name = `sorted_tab` ).
ENDMETHOD.
METHOD m08_modify_using_key_loop_key.
set_example_divider( out = out text = |{ text }: Modifications using the USING KEY addition and modifications within loops using loop_key (2)| ).
line = VALUE #( comp1 = 100 comp2 = 'ABC' comp3 = 1000 comp4 = `DEF` ).
"Index-based modification using the primary table index
"Specifying the primary table key explicitly using the predefined name primary_key
MODIFY std_tab_w_keys USING KEY primary_key INDEX 1 FROM line.
line = VALUE #( comp1 = 200 comp2 = 'GHI' comp3 = 2000 comp4 = `JKL` ).
"Index-based modification using the secondary table index
"Without the TRANSPORTING addition and excluding the secondary table key comp3, the
"statement would show a syntax error.
MODIFY std_tab_w_keys USING KEY sk INDEX 1 FROM line TRANSPORTING comp1 comp2 comp4.
line = VALUE #( comp1 = 3 comp2 = 'MNO' comp3 = 3000 comp4 = `PQR` ).
"Key-based modification using the primary table key
MODIFY TABLE std_tab_w_keys USING KEY primary_key FROM line.
line = VALUE #( comp1 = 400 comp2 = 'STU' comp3 = 4 comp4 = `VWX` ).
"Key-based modification using the secondary table key
MODIFY TABLE std_tab_w_keys USING KEY sk FROM line.
out->write( data = std_tab_w_keys name = `std_tab_w_keys` ).
out->write( |\n| ).
"Resetting the table content
prepare_itabs( ).
"----------- USING KEY loop_key addition -----------
"Notes:
"- Specifying MODIFY ... USING KEY loop_key is required if the table key used by
" the LOOP statement is specified explicitly. Otherwise, the addition USING KEY
" loop_key is optional.
"- The effect is that the current table line of the loop pass is modified.
"- loop_key is a predefined name and must be used.
"LOOP statement specifying the primary table key explicitly
LOOP AT std_tab_w_keys INTO DATA(wa) USING KEY primary_key.
wa-comp2 = 'A' && sy-tabix.
wa-comp4 = 'B' && sy-tabix.
"Unlike the next example, this statement is possible without the TRANSPORTING
"addition regarding the secondary table key. Nevertheless, it is specified
"to have similar examples. In the example case, the secondary table key
"is a non-unique sorted key here. So, uniqueness of the key in case of
"overwriting is not an issue in the example case.
MODIFY std_tab_w_keys USING KEY loop_key FROM wa TRANSPORTING comp2 comp4.
"This statement is not possible. loop_key must be specified.
* MODIFY std_tab_w_keys USING KEY primary_key FROM wa TRANSPORTING comp2 comp4.
ENDLOOP.
out->write( data = std_tab_w_keys name = `std_tab_w_keys` ).
out->write( |\n| ).
"Resetting the table content
prepare_itabs( ).
"LOOP statement specifying the secondary table key explicitly
LOOP AT std_tab_w_keys INTO wa USING KEY sk.
wa-comp2 = 'C' && sy-tabix.
wa-comp4 = 'D' && sy-tabix.
"This statement is not possible without the TRANSPORTING addition
"due to the secondary table key whose overwriting should be prevented.
MODIFY std_tab_w_keys USING KEY loop_key FROM wa TRANSPORTING comp2 comp4.
"This statement is not possible. loop_key must be specified.
* MODIFY std_tab_w_keys USING KEY sk FROM wa TRANSPORTING comp2 comp4.
ENDLOOP.
out->write( data = std_tab_w_keys name = `std_tab_w_keys` ).
ENDMETHOD.
METHOD m09_modify_fs_dref.
set_example_divider( out = out text = |{ text }: Modifying a line and assigning a field symbol or setting a data reference| ).
"When you modify single table lines using MODIFY [TABLE], you can
"assign the successfully modified line to a field symbol or set it
"to a data reference variable.
"--- Assigning the line to a field symbol ---
FIELD-SYMBOLS <fs1> LIKE LINE OF std_tab.
line = VALUE #( comp1 = 100 comp2 = 'ABC' comp3 = 1000 comp4 = `DEF` ).
MODIFY std_tab INDEX 1 FROM line ASSIGNING <fs1>.
"Using inline declaration
line = VALUE #( comp1 = 200 comp2 = 'GHI' comp3 = 2000 comp4 = `JKL` ).
MODIFY std_tab INDEX 2 FROM line ASSIGNING FIELD-SYMBOL(<fs2>).
"--- Setting the line to a data reference variable ---
DATA dref1 TYPE REF TO s.
line = VALUE #( comp1 = 300 comp2 = 'MNO' comp3 = 3000 comp4 = `PQR` ).
MODIFY std_tab INDEX 3 FROM line REFERENCE INTO dref1.
"Using inline declaration
line = VALUE #( comp1 = 400 comp2 = 'STU' comp3 = 4000 comp4 = `VWX` ).
MODIFY std_tab INDEX 4 FROM line REFERENCE INTO DATA(dref2).
out->write( data = <fs1> name = `<fs1>` ).
out->write( |\n| ).
out->write( data = <fs2> name = `<fs2>` ).
out->write( |\n| ).
out->write( data = dref1->* name = `dref1->*` ).
out->write( |\n| ).
out->write( data = dref2->* name = `dref2->*` ).
out->write( |\n| ).
"--- Examples where the assignment/setting is unsuccessful ---
MODIFY std_tab INDEX 123 FROM line ASSIGNING FIELD-SYMBOL(<fs3>).
IF sy-subrc = 0.
out->write( `Assignment successful` ).
ELSE.
out->write( `Assignment not successful` ).
ENDIF.
out->write( |\n| ).
MODIFY std_tab INDEX 123 FROM line REFERENCE INTO DATA(dref3).
IF sy-subrc = 0.
out->write( `Reference setting successful` ).
ELSE.
out->write( `Reference setting not successful` ).
ENDIF.
out->write( |\n| ).
"The ASSIGNING FIELD-SYMBOL addition also supports ELSE UNASSIGN
FIELD-SYMBOLS <fs4> LIKE LINE OF std_tab.
"Assigning a line to the field symbol to ensure that is assigned
"before the MODIFY statements below
ASSIGN line TO <fs4>.
IF sy-subrc = 0.
out->write( `Assignment successful` ).
ELSE.
out->write( `Assignment not successful` ).
ENDIF.
out->write( |\n| ).
"The following example shows two unsuccessful assignments after specifying
"a non-existent table line using INDEX. In the first loop pass, the ELSE
"UNASSIGN addition is not specified. That means, although the modification
"was not successful, the field symbol is still assigned. This is not the
"case after the MODIFY statement in the second loop pass when specifying
"ELSE UNASSIGN.
DO 2 TIMES.
out->write( |------------- Loop pass { sy-index } -------------| ).
out->write( |\n| ).
IF sy-index = 1.
MODIFY std_tab INDEX 123 FROM line ASSIGNING <fs4>.
ELSE.
MODIFY std_tab INDEX 123 FROM line ASSIGNING <fs4> ELSE UNASSIGN.
ENDIF.
IF sy-subrc = 0.
out->write( `Assignment with MODIFY successful` ).
ELSE.
out->write( `Assignment with MODIFY not successful` ).
ENDIF.
out->write( |\n| ).
IF <fs4> IS ASSIGNED.
out->write( `Field symbol is assigned` ).
ELSE.
out->write( `Field symbol is not assigned` ).
ENDIF.
out->write( |\n| ).
ENDDO.
ENDMETHOD.
METHOD m10_modify_multiple_lines.
set_example_divider( out = out text = |{ text }: Modifying multiple table lines using MODIFY statements| ).
"Modifying multiple lines in internal tables is possible using MODIFY statements.
"The additions TRANSPORTING and WHERE are both mandatory. USING KEY is optional.
"All lines matching the logical expression in the WHERE clause are modified
"based on the values a line.
line = VALUE #( comp1 = 123 comp2 = '###' comp3 = 123 comp4 = `***` ).
MODIFY std_tab_w_keys FROM line TRANSPORTING comp2 comp4 WHERE comp1 <= 3.
MODIFY sorted_tab FROM line TRANSPORTING comp2 comp4 WHERE comp1 IS NOT INITIAL.
out->write( data = std_tab_w_keys name = `std_tab_w_keys` ).
out->write( |\n| ).
out->write( data = sorted_tab name = `sorted_tab` ).
ENDMETHOD.
METHOD m11_read_table_modify_target.
set_example_divider( out = out text = |{ text }: Modifying internal table content in the context of READ TABLE statements| ).
"The examples here deal with modifying internal table content in the context of READ TABLE statements.
"The described use case is to modify the content of read table lines.
"When choosing field symbols or data reference variables as target areas, you can directly modify the
"table line content. When choosing a work area as target area, you can indirectly modify the table line,
"e.g. by modifying the copied values in the work area and then modifying the internal table based on
"the changed values and a MODIFY statement.
"The examples use the following for modifying field values:
"- Component selector
"- Constructor expressions such as VALUE and CORRESPONDING
"Indirect modification using a subsequent MODIFY statement
READ TABLE sorted_tab INDEX 1 INTO line.
"Modifying individual components using the component selector
"comp1 is not modified in the example as it is a write-protected key field.
line-comp2 = 'AB'.
line-comp3 = 100.
line-comp4 = `CD`.
MODIFY TABLE sorted_tab FROM line.
"Direct modification
"Using a field symbol
READ TABLE sorted_tab INDEX 2 ASSIGNING FIELD-SYMBOL(<fs>).
<fs>-comp2 = 'EF'.
<fs>-comp3 = 200.
<fs>-comp4 = `GH`.
"Using a data reference variable
READ TABLE sorted_tab INDEX 3 REFERENCE INTO DATA(dref).
"Using the object component selector in case of dereferencing ...
dref->comp2 = 'IJ'.
dref->comp3 = 300.
"... which is a more comfortable option compared to using the
"dereferencing and component selector operators in the following way.
dref->*-comp4 = `KL`.
"Using constructor expressions
"The following example uses a standard table that modifies the entire table line.
READ TABLE std_tab INDEX 1 INTO line.
line = VALUE #( comp1 = 100 comp2 = 'AB' comp3 = 1000 comp4 = `CD` ).
MODIFY std_tab INDEX 1 FROM line.
"Specifying the constructor expression inline
MODIFY std_tab INDEX 2 FROM VALUE #( comp1 = 200 comp2 = 'EF' comp3 = 2000 comp4 = `GH` ).
"Using the BASE addition (see below)
MODIFY std_tab INDEX 3 FROM VALUE #( BASE line comp3 = 3000 comp4 = `IJ` ).
"The following example uses a sorted table.
READ TABLE sorted_tab INDEX 4 REFERENCE INTO dref.
"Key values are write-protected. Using the BASE addition, the not specified
"components (including the key component) are retained and not modified.
dref->* = VALUE #( BASE dref->* comp3 = 400 comp4 = `MN` ).
READ TABLE sorted_tab INDEX 5 ASSIGNING <fs>.
<fs> = VALUE #( BASE <fs> comp3 = 500 comp4 = `OP` ).
out->write( data = std_tab name = `std_tab` ).
out->write( |\n| ).
out->write( data = sorted_tab name = `sorted_tab` ).
out->write( |\n| ).
"The following examples use the CORRESPONDING operator
"For that purpose a demo line has an incompatible type. Some fields have
"identical names, some have not.
line4corr = VALUE #( comp1 = 12 comp2 = 'ZY' compy = 34 compz = `XW` ).
"Standard table, modifying the entire line
READ TABLE std_tab_w_keys INDEX 1 INTO line.
line = CORRESPONDING #( line4corr ).
MODIFY std_tab_w_keys INDEX 1 FROM line.
READ TABLE std_tab_w_keys INDEX 2 INTO line.
line4corr = VALUE #( comp1 = 56 comp2 = 'VU' compy = 78 compz = `TS` ).
MODIFY std_tab_w_keys INDEX 2 FROM CORRESPONDING #( line4corr ).
"Applying a mapping for the non-identical components
READ TABLE std_tab_w_keys INDEX 3 INTO line.
line4corr = VALUE #( comp1 = 90 comp2 = 'RQ' compy = 12 compz = `PO` ).
line = CORRESPONDING #( line4corr MAPPING comp3 = compy comp4 = compz ).
MODIFY std_tab_w_keys INDEX 3 FROM line.
"EXCEPT addition (MAPPING can but need not be specified too)
READ TABLE std_tab_w_keys INDEX 4 INTO line.
line4corr = VALUE #( comp1 = 98 comp2 = 'NM' compy = 76 compz = `LK` ).
line = CORRESPONDING #( line4corr MAPPING comp3 = compy comp4 = compz EXCEPT comp2 ).
MODIFY std_tab_w_keys INDEX 4 FROM line.
"Hahsed table, modifying components, excluding the key fields
READ TABLE hashed_tab WITH KEY comp1 = 1 INTO line.
line4corr = VALUE #( comp1 = 11 comp2 = 'AB' compy = 22 compz = `CD` ).
"Using BASE to retain the key value
line = CORRESPONDING #( BASE ( line ) line4corr MAPPING comp3 = compy comp4 = compz EXCEPT comp1 ).
MODIFY TABLE hashed_tab FROM line.
out->write( data = std_tab_w_keys name = `std_tab_w_keys` ).
out->write( |\n| ).
out->write( data = hashed_tab name = `hashed_tab` ).
ENDMETHOD.
METHOD m12_table_expressions.
set_example_divider( out = out text = |{ text }: Modifying internal table content using table expressions| ).
"Changing the entire table line of a standard table
line = VALUE #( comp1 = 100 comp2 = 'AB' comp3 = 1000 comp4 = `CD` ).
std_tab[ 1 ] = line.
"Note: See further approaches using constructor expressions in the method
"that includes READ TABLE statements. These approaches are not detailed out
"here.
std_tab[ 2 ] = VALUE #( comp1 = 200 comp2 = 'EF' comp3 = 2000 comp4 = `GH` ).
"Changing the values of individual components
std_tab[ 3 ]-comp2 = 'IJ'.
std_tab[ 3 ]-comp4 = `KL`.
std_tab[ 4 ]-comp3 = 4000.
"Changing the entire line is not possible for tables with unique keys
* sorted_tab[ 1 ] = line.
"Changing individual non-key fields
sorted_tab[ 1 ]-comp2 = 'AB'.
sorted_tab[ 2 ]-comp3 = 3000.
"Multiple table expression specifications for the same line is not advisable
"due to the repeated read access
"Alternatively, you may want, for example, set the line to a data reference variable
"or assign it to a field symbol.
DATA(dref) = REF #( sorted_tab[ 3 ] ).
dref->comp2 = 'CD'.
dref->comp3 = 3000.
dref->comp4 = `EF`.
ASSIGN sorted_tab[ 4 ] TO FIELD-SYMBOL(<fs>).
<fs>-comp2 = 'GH'.
<fs>-comp3 = 4000.
<fs>-comp4 = `IJ`.
out->write( data = std_tab name = `std_tab` ).
out->write( |\n| ).
out->write( data = sorted_tab name = `sorted_tab` ).
ENDMETHOD.
METHOD m13_modifications_in_loops.
set_example_divider( out = out text = |{ text }: Modifying table content in all table rows in a loop| ).
"LOOP AT statements without any additions regarding restrictions (WHERE condition or
"index specifications) loop over the entire table, enabling the modification of
"all table lines.
"Indirect modification using a work area as target area and a MODIFY statement
LOOP AT sorted_tab INTO DATA(wa).
wa-comp2 = 'A' && sy-tabix.
wa-comp3 = sy-tabix.
wa-comp4 = 'B' && sy-tabix.
MODIFY sorted_tab FROM wa.
ENDLOOP.
"Direct modification using a field symbol as target area
DATA count TYPE i.
LOOP AT hashed_tab ASSIGNING FIELD-SYMBOL(<fs>).
count += 1.
<fs>-comp2 = 'C' && count.
"Note: No primary table index in case of hashed table. Therefore,
"the sy-tabix value is initial.
<fs>-comp3 = sy-tabix.
<fs>-comp4 = 'D' && count.
ENDLOOP.
"Direct modification using a data reference variable as target area
LOOP AT std_tab REFERENCE INTO DATA(dref).
"Key field in standard tables not write-protected
dref->comp1 = sy-tabix.
dref->comp2 = 'E' && sy-tabix.
dref->comp3 = sy-tabix.
dref->comp4 = 'F' && sy-tabix.
ENDLOOP.
out->write( data = sorted_tab name = `sorted_tab` ).
out->write( |\n| ).
out->write( data = hashed_tab name = `hashed_tab` ).
out->write( |\n| ).
out->write( data = std_tab name = `std_tab` ).
ENDMETHOD.
METHOD m14_restrict_modif_in_loops.
set_example_divider( out = out text = |{ text }: Sequentially modify table content restricting the lines that are looped across| ).
"The examples only use data reference variables as target area.
"Using a WHERE condition to restrict the table lines to be looped across
LOOP AT std_tab REFERENCE INTO DATA(dref) WHERE comp1 >= 3.
dref->comp2 = 'G' && sy-tabix.
dref->comp3 = sy-tabix.
dref->comp4 = 'H' && sy-tabix.
ENDLOOP.
"Specifying the FROM and TO additions to set index values to
"restrict the table lines to be looped across.
"Note that you need not mandatorily specify both additions.
"When not specifying FROM, it means starting from 1. When not
"specifying TO, it means looping to the end of the table.
LOOP AT sorted_tab REFERENCE INTO dref FROM 2 TO 4.
dref->comp2 = 'I' && sy-tabix.
dref->comp3 = sy-tabix.
dref->comp4 = 'J' && sy-tabix.
ENDLOOP.
out->write( data = sorted_tab name = `sorted_tab` ).
out->write( |\n| ).
out->write( data = hashed_tab name = `hashed_tab` ).
ENDMETHOD.
METHOD set_example_divider.
out->write( |\n| ).
out->write( |*&{ repeat( val = `-` occ = strlen( text ) + 2 ) }*| ).
out->write( |*& { text }| ).
out->write( |*&{ repeat( val = `-` occ = strlen( text ) + 2 ) }*| ).
out->write( |\n| ).
out->write( |\n| ).
ENDMETHOD.
METHOD prepare_itabs.
std_tab = VALUE #( ( comp1 = 1 comp2 = 'a' comp3 = 5 comp4 = `f` )
( comp1 = 2 comp2 = 'b' comp3 = 4 comp4 = `g` )
( comp1 = 3 comp2 = 'c' comp3 = 3 comp4 = `h` )
( comp1 = 4 comp2 = 'd' comp3 = 2 comp4 = `i` )
( comp1 = 5 comp2 = 'e' comp3 = 1 comp4 = `j` ) ).
sorted_tab = std_tab.
hashed_tab = std_tab.
std_tab_w_keys = std_tab.
std_tab_w_uni_sk = std_tab.
ENDMETHOD.
ENDCLASS.You can use DELETE statements to delete single and multiple lines in internal tables. The following additions can be used: USING KEY (for specifying a table key), FROM/TO (for specifying row ranges), STEP (for specifying the step size), and WHERE (for specifying conditions).
*&---------------------------------------------------------------------*
*& Deleting via index
*&---------------------------------------------------------------------*
"Example: The first line in the table is deleted.
DELETE it INDEX 1.
"If USING KEY is not used, INDEX can only be used with index tables.
"If doing so, it determines the line from the primary table index.
"If a secondary key is specified, the secondary table index is respected
"Example: same as above
DELETE it INDEX 1 USING KEY primary_key.
"Deleting an index range; FROM or TO alone can also be specified
DELETE it FROM 2 TO 5.
*&---------------------------------------------------------------------*
*& Deleting via keys
*&---------------------------------------------------------------------*
"The line must have a compatible type to the tables line type and
"include key values. The first found line with the corresponding keys
"is deleted.
"If the key is empty, no line is deleted.
DELETE TABLE it FROM line.
"Instead of specifying the keys using a data object ("line" above),
"the keys can be specified separately. All key values must be specified.
"Example: Respects keys from primary table index.
DELETE TABLE it WITH TABLE KEY a = 1.
"You can also specify secondary keys.
"Example: Same as above
DELETE TABLE it WITH TABLE KEY primary_key COMPONENTS a = 1.
DELETE TABLE it_sec WITH TABLE KEY sec_key COMPONENTS ...
*&---------------------------------------------------------------------*
*& Deleting multiple lines based on a WHERE condition
*&---------------------------------------------------------------------*
"Specifying the additions USING KEY, FROM, TO is also possible.
DELETE it WHERE a < 6.
"Excursion: Deleting in a LIKE-like fashion you may know from
"ABAP SQL statements.
"The LIKE addition is not available for the WHERE clause in DELETE
"statements for internal tables as is the case for ABAP SQL DELETE statements.
DATA(str_table) = VALUE string_table( ( `abcZ` ) ( `Zdef` ) ( `gZhi` )
( `Zjkl` ) ( `Zmno` ) ( `pqrZ` ) ).
"You can, for example, use logical operators such as CP (conforms to pattern)
"All lines that begin with Z are to be deleted.
DELETE str_table WHERE table_line CP `Z*`.
"Result: abcZ / gZhi / pqrZ
*&---------------------------------------------------------------------*
*& Deleting the current line inside a LOOP statement
*&---------------------------------------------------------------------*
"The following example illustrates deleting the current table line
"using a DELETE statement within a LOOP statement. Lines with even
"numbers are deleted.
"Note:
"- The short form of the DELETE statement always deletes the
" current first line implicitly. It is only possible within a LOOP
" statement and the delete operation is performed on the same internal
" table.
"- The field symbol (or reference variable) should not be used after
" the DELETE statement any more.
DATA itab1 TYPE TABLE OF i WITH EMPTY KEY.
itab1 = VALUE #( ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) ( 6 ) ( 7 ) ( 8 ) ( 9 ) ( 10 ) ).
LOOP AT itab1 ASSIGNING FIELD-SYMBOL(<fs>).
IF <fs> MOD 2 = 0.
DELETE itab1.
ENDIF.
ENDLOOP.
*Table content:
*1
*3
*5
*7
*9
"The following, similar example (uneven numbers are deleted) uses a
"table which is looped over by specifying the addition USING KEY.
"In this case (using LOOP ... USING KEY ...), the short form of the
"DELETE statement cannot be used. Use the DELETE statement with the
"addition USING KEY loop_key to delete the current first line.
"loop_key is a predefined name to be used with DELETE and within
"loops that specify LOOP ... USING KEY .... No other key name is
"possible here.
DATA itab2 TYPE TABLE OF i WITH NON-UNIQUE KEY table_line.
itab2 = VALUE #( ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) ( 6 ) ( 7 ) ( 8 ) ( 9 ) ( 10 ) ).
LOOP AT itab2 USING KEY primary_key REFERENCE INTO DATA(dref2).
IF dref2->* MOD 2 <> 0.
DELETE itab2 USING KEY loop_key.
ENDIF.
ENDLOOP.
*Table content:
*2
*4
*6
*8
*10DELETE DUPLICATES FROM statements delete all lines except the first in groups where multiple lines share the same content in specific components.DELETE ADJACENT DUPLICATES FROM but do not require prior sorting.COMPARING addition:USING KEY.COMPARING comp1 comp2) or use ALL FIELDS to compare all contents (COMPARING ALL FIELDS).table_line in COMPARING table_line is equivalent to COMPARING ALL FIELDS.USING KEY addition:[!NOTE] - Duplicate deletion statements that use the primary table key implicitly should not be used for standard tables whose primary table key is empty. - Using the standard table key as the primary table key may lead to unexpected results. In the case of structured line types, the standard table key can include all character- and byte-like components. Additionally, the standard key of a standard table can be empty. - The system fieldsy-subrcis set to0if at least one row has been deleted and is set to4if no rows were deleted.
Syntax patterns:
"Implicitly using the primary table key
DELETE DUPLICATES FROM it.
"Deletion respecting the values of the entire line
DELETE DUPLICATES FROM it COMPARING ALL FIELDS.
"Only lines are deleted with matching content in specific fields
DELETE DUPLICATES FROM it COMPARING a c.
"Deletion respecting a specified table key
"Using a secondary table key
DELETE DUPLICATES FROM it USING KEY sec_key.
"Using the primary table key by specifying its default name;
"the example statement corresponds to the first statement above
"as it uses the primary table key implicitly
DELETE DUPLICATES FROM it USING KEY primary_key.
"Combining COMPARING and USING KEY
DELETE DUPLICATES FROM it USING KEY primary_key COMPARING a.To try out the following executable example, create a demo class named zcl_demo_abap, or reuse it if it already exists. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example is set up to display output in the console.
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
*&---------------------------------------------------------------------*
*& DELETE DUPLICATES FROM statements
*&---------------------------------------------------------------------*
"Demo structured type
TYPES:
BEGIN OF demo_struc,
a TYPE i,
b TYPE c LENGTH 3,
END OF demo_struc.
"Demo internal tables with different key definitions
DATA it_std_pr_key TYPE TABLE OF demo_struc WITH NON-UNIQUE KEY a.
DATA it_std_sec_key TYPE TABLE OF demo_struc WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY sk COMPONENTS a.
DATA copy_tab TYPE TABLE OF demo_struc WITH EMPTY KEY.
"Populating internal tables with demo data
"Note that the duplicates are intentionally scattered.
copy_tab = VALUE #( ( a = 1 b = 'aaa' )
( a = 3 b = 'ccc' )
( a = 2 b = 'bbb' )
( a = 1 b = 'aaa' )
( a = 4 b = 'eee' )
( a = 1 b = 'ddd' )
( a = 2 b = 'bbb' )
( a = 3 b = 'ccc' )
( a = 2 b = 'aaa' )
( a = 3 b = 'ccc' )
( a = 3 b = 'ddd' ) ).
it_std_pr_key = copy_tab.
it_std_sec_key = copy_tab.
*&---------------------------------------------------------------------*
*& DELETE DUPLICATES statements without further addition
*&---------------------------------------------------------------------*
out->write( `1) No addition (standard table with defined primary table key)` ).
DELETE DUPLICATES FROM it_std_pr_key.
DATA(veri_tab) = it_std_pr_key.
out->write( data = it_std_pr_key name = `it_std_pr_key` ).
out->write( |\n| ).
*&---------------------------------------------------------------------*
*& COMPARING addition
*&---------------------------------------------------------------------*
out->write( `2) COMPARING ALL FIELDS` ).
DELETE DUPLICATES FROM it_std_pr_key COMPARING ALL FIELDS.
out->write( data = it_std_pr_key name = `it_std_pr_key` ).
out->write( |\n| ).
out->write( `3) COMPARING comp ...` ).
it_std_pr_key = copy_tab.
DELETE DUPLICATES FROM it_std_pr_key COMPARING b.
out->write( data = it_std_pr_key name = `it_std_pr_key` ).
out->write( |\n| ).
*&---------------------------------------------------------------------*
*& USING KEY addition
*&---------------------------------------------------------------------*
out->write( `4) USING KEY (secondary table key)` ).
DELETE DUPLICATES FROM it_std_sec_key USING KEY sk.
out->write( data = it_std_sec_key name = `it_std_sec_key` ).
out->write( |\n| ).
out->write( `5) USING KEY (secondary table key; plus COMPARING)` ).
it_std_sec_key = copy_tab.
DELETE DUPLICATES FROM it_std_sec_key USING KEY sk COMPARING b.
out->write( data = it_std_sec_key name = `it_std_sec_key` ).
out->write( |\n| ).
out->write( `6) USING KEY (primary table key)` ).
it_std_pr_key = copy_tab.
DELETE DUPLICATES FROM it_std_pr_key USING KEY primary_key.
out->write( data = it_std_pr_key name = `it_std_pr_key` ).
out->write( |\n| ).
IF veri_tab = it_std_pr_key.
out->write( `The compared tables have identical content.` ).
ELSE.
out->write( `The compared tables have different content.` ).
ENDIF.
ENDMETHOD.
ENDCLASS.DELETE ADJACENT DUPLICATES statements allow you to remove all adjacent duplicate lines except for the first line in groups with identical content in specific components. DELETE DUPLICATES FROM statements. Syntax patterns:
"Sorting before using DELETE ADJACENT DUPLICATES FROM
SORT it ...
"Implicitly using the primary table key
DELETE ADJACENT DUPLICATES FROM it.
"Deletion respecting the values of the entire line
DELETE ADJACENT DUPLICATES FROM it COMPARING ALL FIELDS.
"Only lines are deleted with matching content in specific fields
DELETE ADJACENT DUPLICATES FROM it COMPARING a c.
"Deletion respecting a specified table key
"Using a secondary table key
DELETE ADJACENT DUPLICATES FROM it USING KEY sec_key.
"Using the primary table key by specifying its default name;
"the example statement corresponds to the first statement above
"as it uses the primary table key implicitly
DELETE ADJACENT DUPLICATES FROM it USING KEY primary_key.
"Combining COMPARING and USING KEY
DELETE ADJACENT DUPLICATES FROM it USING KEY primary_key COMPARING a.To try out the following executable example, create a demo class named zcl_demo_abap, or reuse it if it already exists. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example is set up to display output in the console.
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
*&---------------------------------------------------------------------*
*& DELETE ADJACENT DUPLICATES statements
*&
*& Note: As the statements usually require a suitable sorting, the demo
*& DELETE ADJACENT DUPLICATES statements are preceded by SORT
*& statements.
*&---------------------------------------------------------------------*
"Demo structured type
TYPES:
BEGIN OF demo_struc,
a TYPE i,
b TYPE c LENGTH 3,
END OF demo_struc.
"Demo internal tables with different key definitions
DATA it_std_pr_key TYPE TABLE OF demo_struc WITH NON-UNIQUE KEY a.
DATA it_std_sec_key TYPE TABLE OF demo_struc WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY sk COMPONENTS a.
DATA copy_tab TYPE TABLE OF demo_struc WITH EMPTY KEY.
"Populating internal tables with demo data
"Note that the duplicates are intentionally scattered.
copy_tab = VALUE #( ( a = 1 b = 'aaa' )
( a = 3 b = 'ccc' )
( a = 2 b = 'bbb' )
( a = 1 b = 'aaa' )
( a = 4 b = 'eee' )
( a = 1 b = 'ddd' )
( a = 2 b = 'bbb' )
( a = 3 b = 'ccc' )
( a = 2 b = 'aaa' )
( a = 3 b = 'ccc' )
( a = 3 b = 'ddd' ) ).
it_std_pr_key = copy_tab.
it_std_sec_key = copy_tab.
*&---------------------------------------------------------------------*
*& DELETE ADJACENT DUPLICATES statements without further addition
*&---------------------------------------------------------------------*
out->write( `1) No addition (standard table with defined primary table key)` ).
SORT it_std_pr_key.
out->write( data = it_std_pr_key name = `it_std_pr_key after sorting` ).
out->write( |\n| ).
DELETE ADJACENT DUPLICATES FROM it_std_pr_key.
DATA(veri_tab) = it_std_pr_key.
out->write( data = it_std_pr_key name = `it_std_pr_key after deleting` ).
out->write( |\n| ).
*&---------------------------------------------------------------------*
*& COMPARING addition
*&---------------------------------------------------------------------*
out->write( `2) COMPARING ALL FIELDS` ).
it_std_pr_key = copy_tab.
SORT it_std_pr_key BY table_line.
out->write( data = it_std_pr_key name = `it_std_pr_key after sorting` ).
out->write( |\n| ).
DELETE ADJACENT DUPLICATES FROM it_std_pr_key COMPARING ALL FIELDS.
out->write( data = it_std_pr_key name = `it_std_pr_key after deleting` ).
out->write( |\n| ).
out->write( `4) COMPARING comp ...` ).
it_std_pr_key = copy_tab.
SORT it_std_pr_key BY b.
out->write( data = it_std_pr_key name = `it_std_pr_key after sorting` ).
out->write( |\n| ).
DELETE ADJACENT DUPLICATES FROM it_std_pr_key COMPARING b.
out->write( data = it_std_pr_key name = `it_std_pr_key after deleting` ).
out->write( |\n| ).
*&---------------------------------------------------------------------*
*& USING KEY addition
*&---------------------------------------------------------------------*
out->write( `5) USING KEY (secondary table key)` ).
SORT it_std_sec_key BY a.
out->write( data = it_std_sec_key name = `it_std_sec_key after sorting` ).
out->write( |\n| ).
DELETE ADJACENT DUPLICATES FROM it_std_sec_key USING KEY sk.
out->write( data = it_std_sec_key name = `it_std_sec_key after deleting` ).
out->write( |\n| ).
out->write( `6) USING KEY (secondary table key; plus COMPARING)` ).
it_std_sec_key = copy_tab.
SORT it_std_sec_key BY b.
out->write( data = it_std_sec_key name = `it_std_sec_key after sorting` ).
out->write( |\n| ).
DELETE ADJACENT DUPLICATES FROM it_std_sec_key USING KEY sk COMPARING b.
out->write( data = it_std_sec_key name = `it_std_sec_key after deleting` ).
out->write( |\n| ).
out->write( `7) USING KEY (primary table key)` ).
it_std_pr_key = copy_tab.
SORT it_std_pr_key BY a.
out->write( data = it_std_pr_key name = `it_std_pr_key after sorting` ).
out->write( |\n| ).
DELETE ADJACENT DUPLICATES FROM it_std_pr_key USING KEY primary_key.
out->write( data = it_std_pr_key name = `it_std_pr_key after deleting` ).
out->write( |\n| ).
IF veri_tab = it_std_pr_key.
out->write( `The compared tables have identical content.` ).
ELSE.
out->write( `The compared tables have different content.` ).
ENDIF.
ENDMETHOD.
ENDCLASS.The CLEAR and FREE statements allow you to delete the entire table content.
The difference between the two is in the handling of the memory space originally allocated to the table. When a table is cleared with CLEAR, the content is removed, but the memory space initially requested remains allocated. If the table is populated again later, the memory space is still available, which is a performance advantage over clearing an internal table with FREE. Such a statement also deletes the table content, but it also releases the memory space. Note that an assignment using the VALUE operator without entries in the parentheses clears the internal table.
CLEAR it.
"This statement additionally releases memory space.
FREE it.
"Assignment using the VALUE operator without entries in the parentheses
it = VALUE #( ).
"The same applies to data reference variables pointing to internal tables.
it_ref = NEW #( ).order, hence, they cannot be sorted explicitly with SORT.
SORT statements, the sort order is derived eitherby the primary table key (Note: Secondary keys cannot be used for the sorting.) or by explicitly specifying the fields to be sorted by.
easier to understand and can prevent unwanted sorting results, especially with tables with standard key.
| Subject | Details/Code Snippet |
| Sorting by primary table key |
The effect of sorting can have an unexpected result if you use the simple form of the statement and do not explicitly specify the keys. If an internal table has a structured line type and (perhaps inadvertently) the standard key as the primary table key, that is, all character-like and byte-like components make up the primary table key, all these components are taken into account when the table is sorted. Plus: Suppose there are only elementary numeric components in an internal table with a structured line type. In this case, sorting has no effect because the primary table key is considered empty. This is also applies to tables declared with |
| Sorting by explicitly specifying components |
You can sort by any component of the internal table. It is also possible to specify the sort order (even component-wise). Explicitly specifying the components has the advantage that your code is easier to understand and you can avoid unexpected results if you accidentally use |
| Stable sorting |
By default, the sorting is unstable concerning preserving the order of table lines with identical sort keys. To ensure a stable sorting with preserving the order of table lines with identical sort keys, you can use the |
To group internal tables, there are additions for LOOP AT statements.
LOOP AT ... GROUP BY
LOOP AT GROUP statements.ASCENDING/DESCENDING additions to specify the sort order in the group loop. You can also sort beforehand with SORT statements. WITHOUT MEMBERS: This creates groups (requiring group key binding) without assigning actual component values. Access to group lines is not possible. Use this when access is not necessary and you want to enhance read performance. GROUP BY GROUP SIZE to count group members, e.g., gs = GROUP SIZE. GROUP INDEX to index group members, e.g., gi = GROUP INDEX. gs and gi in the previous example) freely. INTO and a data object. You can also use data references (REFERENCE INTO) or field symbols (ASSIGNING ...). LOOP AT GROUP. dobj in LOOP AT itab INTO dobj), but the component values are initial when the group key binding is specified. You can address the group and its component values in nested loops with LOOP AT GROUP. LOOP AT GROUP. With representative binding, the sy-tabix value is set as if the loop was specified without grouping.LOOP AT GROUP:
LOOP ... GROUP BY statements (provided WITHOUT MEMBERS is not specified) LOOP AT GROUP, you can specify ... LOOP AT it INTO DATA(wa) GROUP BY ... LOOP AT GROUP wa .... LOOP AT it INTO DATA(wa) GROUP BY ... INTO DATA(gkb). ... LOOP AT GROUP gkb .... WHERE condition and further grouping are also available.More information:
FOR ... IN GROUP). For example, see the Constructor Expressions cheat sheet.The example class below demonstrates internal table grouping options. To try the example out, create a demo class named zcl_demo_abap. If it already exists, reuse it. Otherwise, create a new class with a different name. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example is designed to display results in the console.
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
TYPES: BEGIN OF demo_struct,
comp1 TYPE c LENGTH 1,
comp2 TYPE i,
comp3 TYPE abap_boolean,
comp4 TYPE string,
END OF demo_struct,
tab_type TYPE TABLE OF demo_struct WITH EMPTY KEY.
DATA str_table TYPE string_table.
"Populating a demo internal table as the basis of the syntax example
"Note: The example loops only use data objects as targets, not data references
"or field symbols.
DATA(it) = VALUE tab_type( ( comp1 = 'd' comp2 = 0 comp3 = abap_false )
( comp1 = 'a' comp2 = 1 comp3 = abap_true )
( comp1 = 'a' comp2 = 2 comp3 = abap_false )
( comp1 = 'e' comp2 = 11 comp3 = abap_true )
( comp1 = 'b' comp2 = 5 comp3 = abap_true )
( comp1 = 'b' comp2 = 6 comp3 = abap_false )
( comp1 = 'a' comp2 = 3 comp3 = abap_false )
( comp1 = 'b' comp2 = 4 comp3 = abap_true )
( comp1 = 'c' comp2 = 10 comp3 = abap_true )
( comp1 = 'e' comp2 = 1 comp3 = abap_false )
( comp1 = 'd' comp2 = 7 comp3 = abap_true )
( comp1 = 'a' comp2 = 4 comp3 = abap_true )
( comp1 = 'e' comp2 = 111 comp3 = abap_true ) ).
"The following example (and several others below) does not specify a nested loop.
"It does not specify a group key binding either. This means that the work area
"contains the first line of each group, representing the group in the loop
"(representative binding). The comp4 component is assigned the sy-tabix value,
"which is the number of the line in the table without the grouping.
DATA ita LIKE it.
LOOP AT it INTO DATA(waa) GROUP BY waa-comp1.
waa-comp4 = sy-tabix.
APPEND waa TO ita.
ENDLOOP.
out->write( data = ita name = `ita` ).
"Specifying sort order
DATA itb LIKE it.
LOOP AT it INTO DATA(wab) GROUP BY wab-comp1 ASCENDING.
wab-comp4 = sy-tabix.
APPEND wab TO itb.
ENDLOOP.
out->write( data = itb name = `itb` ).
"WITHOUT MEMBERS addition; a group key binding is required
"after WITHOUT MEMBERS
"The group key binding is added to a string table for visualizing its
"content.
"Note: The component values are initial when the group key binding is
"specified.
LOOP AT it INTO DATA(wac) GROUP BY wac-comp1 WITHOUT MEMBERS INTO DATA(keyc).
ASSERT wac IS INITIAL.
APPEND keyc TO str_table.
ENDLOOP.
out->write( data = str_table name = `str_table` ).
"Using a structured group key
"The following example just assigns component values to the group key. In this case,
"the grouping is performed with more than just one criterion as in the previous examples.
"As a result, table lines are added to the other table in descending order based on the
"two component values.
DATA itd LIKE it.
LOOP AT it INTO DATA(wad) GROUP BY ( key1 = wad-comp1 key2 = wad-comp2 ) DESCENDING.
APPEND wad TO itd.
ENDLOOP.
out->write( data = itd name = `itd` ).
"In the following example, the group is sorted in ascending order. Note that the
"group index value uses the original position in the group index. The group key
"binding information is added to a string table for visualizing its content.
CLEAR str_table.
LOOP AT it INTO DATA(wae) GROUP BY ( key = wae-comp1 gi = GROUP INDEX gs = GROUP SIZE ) ASCENDING INTO DATA(keye).
APPEND |Key component: '{ keye-key }', group index: '{ keye-gi }', group size: '{ keye-gs }'| TO str_table.
ENDLOOP.
out->write( data = str_table name = `str_table` ).
"LOOP AT GROUP: Nested loop across group members
"Unlike the previous example, the example uses a nested loop across the groups (the group key binding is
"specified after LOOP AT GROUP). There, the component values of the members can be accessed.
DATA itf LIKE it.
LOOP AT it INTO DATA(waf) GROUP BY ( key = waf-comp1 gi = GROUP INDEX gs = GROUP SIZE ) ASCENDING INTO DATA(keyf).
LOOP AT GROUP keyf INTO DATA(memberf).
APPEND VALUE #( comp1 = memberf-comp1 comp2 = memberf-comp2 comp3 = memberf-comp3
comp4 = |Key component: '{ keyf-key }', group index: '{ keyf-gi }', group size: '{ keyf-gs }'|
) TO itf.
ENDLOOP.
ENDLOOP.
out->write( data = itf name = `itf` ).
"The objective of this example is to extract the line with the highest value in a particular
"column within a group from the original table to another.
"The example uses representative binding, i.e. the representative of the group is specified
"in the work area, not in a group key binding.
DATA itg LIKE it.
LOOP AT it INTO DATA(wag) GROUP BY wag-comp1 ASCENDING.
LOOP AT GROUP wag INTO DATA(memberg) GROUP BY memberg-comp2 DESCENDING.
APPEND memberg TO itg.
EXIT.
ENDLOOP.
ENDLOOP.
out->write( data = itg name = `itg` ).
"The following example is similar to the previous example, and yields the same result.
"Here, the group key binding is specified after LOOP AT GROUP.
DATA ith LIKE it.
LOOP AT it INTO DATA(wah) GROUP BY wah-comp1 ASCENDING.
LOOP AT GROUP wah INTO DATA(memberh) GROUP BY memberh-comp2 DESCENDING.
APPEND memberh TO ith.
EXIT.
ENDLOOP.
ENDLOOP.
ASSERT itg = ith.
out->write( data = ith name = `ith` ).
"Additional syntax options, like specifying a WHERE condition in both nested and outer
"loops, are possible. The example below shows that the LOOP AT GROUP statement assigns
"the value of sy-tabix to the value that would be set for the current line in the LOOP
"without grouping.
DATA iti LIKE it.
LOOP AT it INTO DATA(wai) GROUP BY wai-comp1 ASCENDING.
LOOP AT GROUP wai INTO DATA(memberi) WHERE comp3 = abap_true.
APPEND VALUE #( comp1 = memberi-comp1 comp2 = memberi-comp2 comp3 = memberi-comp3
comp4 = |sy-tabix: '{ sy-tabix }'|
) TO iti.
ENDLOOP.
ENDLOOP.
out->write( data = iti name = `iti` ).
ENDMETHOD.
ENDCLASS.The following example compares a LOOP AT statement with a FOR loop in a constructor expression. The goal is to highlight their syntactical similarities and differences, such as the greater flexibility of LOOP AT statements in terms of additional specifications within the loop and the common use of FOR loops to create an internal table.
SELECT *
FROM zdemo_abap_flsch
INTO TABLE @DATA(fl_tab).
DATA memb LIKE fl_tab.
TYPES tab_type LIKE memb.
DATA member_tab TYPE TABLE OF tab_type WITH EMPTY KEY.
DATA tab TYPE string_table.
"-------------------------- LOOP AT statement --------------------------
LOOP AT fl_tab INTO DATA(flight)
GROUP BY ( carrier = flight-carrid cityfr = flight-cityfrom
size = GROUP SIZE index = GROUP INDEX )
ASCENDING REFERENCE INTO DATA(group_ref).
APPEND |carrier = "{ group_ref->carrier }", cityfr = "{ group_ref->cityfr }", size = "{ group_ref->size }", index = "{ group_ref->index }" | TO tab.
CLEAR memb.
LOOP AT GROUP group_ref ASSIGNING FIELD-SYMBOL(<flight>).
memb = VALUE #( BASE memb ( <flight> ) ).
ENDLOOP.
APPEND memb TO member_tab.
ENDLOOP.
"-------------------------- FOR loop --------------------------
TYPES: BEGIN OF demo_struc,
member LIKE memb,
info TYPE string,
END OF demo_struc.
TYPES ty_itab_constr TYPE TABLE OF demo_struc WITH EMPTY KEY.
DATA(itab_constr) = VALUE ty_itab_constr( FOR GROUPS g OF fl IN fl_tab
GROUP BY ( c = fl-carrid cifr = fl-cityfrom
size = GROUP SIZE index = GROUP INDEX ) ASCENDING
LET m = VALUE tab_type( FOR <fl> IN GROUP g ( <fl> ) ) IN
( member = m info = |carrid = "{ g-c }", cityfrom = "{ g-cifr }", size = "{ g-size }", index = "{ g-index }" | ) ).COLLECT statements, for example, to add the values of numeric components to the corresponding values in an internal table. "This example demonstrates how to insert data from a database table
"into an internal table in a compressed way. Within a SELECT loop,
"a COLLECT statement is used to consolidate lines with identical
"primary key components (carrid and connid) by summing the number
"of occupied seats in the numeric component (seatsocc).
"Additionally, an internal table is filled by adding all read lines.
"This table is looped across to simulate the effect of the COLLECT
"statement.
DATA: BEGIN OF seats,
carrid TYPE zdemo_abap_fli-carrid,
connid TYPE zdemo_abap_fli-connid,
seatsocc TYPE zdemo_abap_fli-seatsocc,
END OF seats,
seats_tab_col LIKE HASHED TABLE OF seats WITH UNIQUE KEY carrid connid,
seats_tab_all LIKE TABLE OF seats WITH EMPTY KEY,
seats_tab_loop_grp LIKE seats_tab_col.
SELECT carrid, connid, seatsocc
FROM zdemo_abap_fli
INTO @seats.
COLLECT seats INTO seats_tab_col.
APPEND seats TO seats_tab_all.
ENDSELECT.
LOOP AT seats_tab_all INTO DATA(wa) GROUP BY ( key1 = wa-carrid key2 = wa-connid ).
INSERT VALUE #( carrid = wa-carrid connid = wa-connid ) INTO TABLE seats_tab_loop_grp ASSIGNING FIELD-SYMBOL(<fs>).
LOOP AT GROUP wa INTO DATA(member).
<fs>-seatsocc = <fs>-seatsocc + member-seatsocc.
ENDLOOP.
ENDLOOP.
ASSERT seats_tab_loop_grp = seats_tab_col.This is relevant if you are not interested in the content of a table line, but only want to find out whether a line exists that matches to the index or key specifications. To do this, use a READ TABLE statement with the TRANSPORTING NO FIELDS addition. The addition indicates that no actual content is to be read. If the search was successful and an entry exists, the system field sy-subrc is set to 0.
A newer way to check the existence of a line is the predicate function line_exists( ). This function expects a table expression as an argument. See below for more on table expressions. Note that table expressions do not set system fields.
"Read using a key
READ TABLE it WITH KEY b = 2 TRANSPORTING NO FIELDS.
IF sy-subrc = 0.
...
ENDIF.
"Read using the index
READ TABLE it INDEX 1 TRANSPORTING NO FIELDS.
IF sy-subrc = 0.
...
ENDIF.
"Read using the key
IF line_exists( it[ b = 2 ] ).
...
ENDIF.
"Read using the index
IF line_exists( it[ 1 ] ).
...
ENDIF.If you want to find out about the index of a line in an internal table, you can also make use of the READ TABLE statement above. If the line is found, the system field sy-tabix is set to the number of the index. Otherwise, the built-in function line_index( ) can be used. It returns the index of the found line or 0 if the line does not exist.
DATA(itab) = VALUE string_table( ( `aaa` ) ( `bbb` ) ).
READ TABLE itab WITH KEY table_line = `bbb` TRANSPORTING NO FIELDS.
"2
DATA(tabix) = sy-tabix.
"1
DATA(idx) = line_index( itab[ table_line = `aaa` ] ).
"Note: No primary table index with hashed tables
DATA(hashed_tab) = VALUE string_hashed_table( ( `a` ) ( `b` ) ( `c` ) ).
"-1
DATA(hashed_primary_idx) = line_index( hashed_tab[ table_line = `c` ] ).
"Index access in hashed tables only using a secondary table index
TYPES: BEGIN OF s,
comp1 TYPE i,
comp2 TYPE i,
END OF s,
ttype TYPE HASHED TABLE OF s WITH UNIQUE KEY comp1 WITH NON-UNIQUE SORTED KEY sk COMPONENTS comp2.
DATA(hashed_tab2) = VALUE ttype( ( comp1 = 1 comp2 = 10 )
( comp1 = 2 comp2 = 8 )
( comp1 = 3 comp2 = 9 ) ).
"3
DATA(hashed_secondary_idx) = line_index( hashed_tab2[ KEY sk comp2 = 10 ] ).
"1
hashed_secondary_idx = line_index( hashed_tab2[ KEY sk comp2 = 8 ] ).lines( ) is another built-in function that you can use to check how many lines exist in an internal table. It returns an integer value.
DATA(itab) = VALUE string_table( ( `a` ) ( `b` ) ( `c` ) ( `d` ) ( `e` ) ).
"5
DATA(number_of_lines) = lines( itab ).
"Excursion: Finding out the number of lines in a table by specifying concrete
"component values, e.g. you want to find out how many lines exist in the table
"that have the value 1 for comp2
TYPES: BEGIN OF struct,
comp1 TYPE c LENGTH 3,
comp2 TYPE i,
END OF struct,
tab_type TYPE TABLE OF struct WITH EMPTY KEY.
DATA(it) = VALUE tab_type( ( comp1 = 'a' comp2 = 1 )
( comp1 = 'b' comp2 = 1 )
( comp1 = 'c' comp2 = 1 )
( comp1 = 'd' comp2 = 2 )
( comp1 = 'e' comp2 = 3 )
( comp1 = 'f' comp2 = 4 )
( comp1 = 'g' comp2 = 5 ) ).
"7
DATA(line_num) = lines( it ).
"Finding out the number of lines in a table by component value, e.g.
"using constructor expressions and specifying a WHERE clause.
"The example creates an new internal table inline using VALUE and a FOR loop,
"specified with a WHERE clause. The lines function is applied to the
"table created inline.
"3
DATA(line_num_filtered1) = lines( VALUE tab_type( FOR wa IN it WHERE ( comp2 = 1 ) ( wa ) ) ).
"Using the REDUCE operator
"The example adds 1 to the resulting integer if the comp2 value of the iterated line is greater than 1.
"The lines function is not relevant in the example.
"4
DATA(line_num_filtered2) = REDUCE i( INIT var = 0
FOR <line> IN it
WHERE ( comp2 > 1 )
NEXT var += 1 ).
"Using the FILTER operator
"Note: The source table must have at least one sorted key or a hash key for accessing.
"If the table does not have such a primary table key, a secondary table key must be available.
TYPES: tab_type_sorted TYPE TABLE OF struct with NON-UNIQUE SORTED KEY sec_key COMPONENTS comp2.
DATA it_sorted type tab_type_sorted.
it_sorted = it.
"The example creates an new internal table inline using FILTER,
"specified with a WHERE clause. The lines function is applied to the
"table created inline.
"3
DATA(line_num_filtered3) = lines( FILTER #( it_sorted USING KEY sec_key WHERE comp2 = 1 ) ).
"4
DATA(line_num_filtered4) = lines( FILTER #( it_sorted USING KEY sec_key WHERE comp2 > 1 ) ).
"Using LOOP statements
CLEAR number_of_lines.
"No WHERE condition as all lines shall be processed
"7
LOOP AT it REFERENCE INTO DATA(line).
number_of_lines += 1.
ENDLOOP.
CLEAR number_of_lines.
"3
LOOP AT it transporting no fields where comp2 = 1.
number_of_lines += 1.
ENDLOOP.Using Runtime Type Identification (RTTI), you can get type information on internal tables and table types at runtime.
For more information, see the Dynamic Programming ABAP cheat sheet.
RTTI example:
TYPES tab_type TYPE SORTED TABLE OF zdemo_abap_flsch
WITH UNIQUE KEY carrid connid
WITH NON-UNIQUE SORTED KEY sec_key ALIAS sk COMPONENTS countryfr cityto.
DATA itab TYPE tab_type.
DATA(tdo_d) = cl_abap_typedescr=>describe_by_data( itab ).
"DATA(tdo_d) = cl_abap_typedescr=>describe_by_name( 'TAB_TYPE' ).
"Cast to get more specific information
DATA(tdo_itab) = CAST cl_abap_tabledescr( cl_abap_typedescr=>describe_by_data( itab ) ).
"DATA(tdo_itab) = CAST cl_abap_tabledescr( tdo_d ).
DATA(type_category_itab) = tdo_itab->kind.
DATA(relative_name_itab) = tdo_itab->get_relative_name( ).
... "Explore more options by positioning the cursor behind -> and choosing CTRL + Space
DATA(table_kind_itab) = tdo_itab->table_kind.
DATA(table_keys_itab) = tdo_itab->key.
DATA(table_keys_more_details_itab) = tdo_itab->get_keys( ).
DATA(table_has_unique_key_itab) = tdo_itab->has_unique_key.
DATA(table_key_alias_itab) = tdo_itab->get_key_aliases( ).
DATA(line_type_itab) = tdo_itab->get_table_line_type( ).
DATA(table_component_info_itab) = CAST cl_abap_structdescr( tdo_itab->get_table_line_type( ) ).
DATA(table_components_itab) = CAST cl_abap_structdescr( tdo_itab->get_table_line_type( ) )->components.
DATA(table_comps_more_info_itab) = CAST cl_abap_structdescr( tdo_itab->get_table_line_type( ) )->get_components( ).
DATA(applies_to_data_itab) = tdo_itab->applies_to_data( VALUE tab_type( ) ).Adding multiple lines from a database table to an internal table using SELECT, for example, based on a condition. In the case below, the internal table is created inline.
SELECT FROM dbtab
FIELDS comp1, comp2 ...
WHERE ...
INTO TABLE @DATA(itab_sel).Adding multiple lines from a database table using SELECT, for example, based on a condition when the database table has a line type that is incompatible with the internal table. The * character means that all fields are selected. The other examples define specific fields. The APPENDING CORRESPONDING FIELDS INTO TABLE addition appends the selected data to the end of the table without deleting existing table entries. The INTO CORRESPONDING FIELDS OF TABLE addition adds lines and deletes existing table entries.
SELECT FROM dbtab2
FIELDS *
WHERE ...
APPENDING CORRESPONDING FIELDS OF TABLE @itab.
SELECT FROM dbtab2
FIELDS *
WHERE ...
INTO CORRESPONDING FIELDS OF TABLE @itab.Combining data from multiple database tables into one internal table using an inner join. The following example uses the INNER JOIN addition. Note that the field list includes fields from both tables. The fields are referred to using ~.
SELECT db1~comp1, db1~comp2, db2~comp_abc, db2~comp_xyz ...
FROM db1
INNER JOIN db2 ON db1~comp1 = db2~comp1
INTO TABLE @DATA(it_join_result).Populating an internal table from a database table using subqueries. The following two examples populate an internal table from a database table. In the first example, a subquery is specified in the WHERE clause with the NOT IN addition. It checks whether a value matches a value in a set of values specified in parentheses. The second example populates an internal table depending on data in another table. A subquery with the EXISTS addition is specified in the WHERE clause. In this case, the result of the subquery, which is another SELECT statement, is checked to see if an entry exists in a table based on the specified conditions.
SELECT comp1, comp2, ...
FROM dbtab
WHERE comp1 NOT IN ( a, b, c ... )
INTO TABLE @DATA(it_subquery_result1).
SELECT comp1, comp2, ...
FROM db1
WHERE EXISTS ( SELECT 'X' FROM db2
WHERE comp1 = db1~comp1 )
INTO TABLE @DATA(it_subquery_result2).Populating an internal table from a table based on the existence of data in another table using the FOR ALL ENTRIES addition.
[!NOTE] Make sure that the internal table you are reading from is not initial. Therefore, it is recommended that you use a subquery as shown above: ... ( SELECT ... FROM ... WHERE ... ) ....
IF itab IS NOT INITIAL.
SELECT dbtab~comp1, dbtab~comp2, ...
FROM dbtab
FOR ALL ENTRIES IN @itab
WHERE comp1 = @itab-comp1
INTO TABLE @DATA(it_select_result).
ENDIF.General rule: Use SELECT with internal tables as a data source only when SQL functionality, such as joins, exceeds ABAP statements.
Technical considerations:
SELECT statements for tabular data within the memory of an Application Server ABAP (AS ABAP). The tabular data can include:SELECT statement using internal tables as data sources includes elements beyond the engine's capability (for example, most subqueries aren't supported; see detailed restrictions here), the internal table content is transferred to a temporary database table before the query executes. Only components involved in the read access are transferred.##itab_db_select.Using internal tables as data sources in ABAP SQL SELECT statements:
@ prefix and give them an alias. SELECT comp1, comp2, ...
FROM @itab AS it_alias
WHERE ...
INTO TABLE @DATA(itab_sel).DECLARE CLIENT addition. For example, if the internal table specifies a key that is not specified at the beginning of the line type, you cannot use SELECT to retrieve data from the table. TYPES: BEGIN OF s,
comp1 TYPE i,
comp2 TYPE i,
comp3 TYPE i,
END OF s.
DATA itab TYPE TABLE OF s WITH KEY comp3.
"SELECT SINGLE * FROM @itab AS tab INTO @DATA(line).SELECT statements.sstring.SELECT statements with internal tables as data sources is the access to ABAP SQL's extensive functionalities, like aggregate expressions.READ TABLE or LOOP AT statements if the ABAP SQL in-memory engine can process the data without requiring a database transfer.SELECT with internal tables as a data source only when SQL functionality exceeds that of ABAP statements, such as in joins. For tasks achievable with ABAP statements, it is preferable to use them as they are optimized for internal tables and offer better performance.The following example finds duplicate entries in internal tables. It uses a SELECT statement with the internal table as the data source. However, since the statement executes on the database, a syntax warning appears unless suppressed by the pragma. An advisable alternative is the internal-table-specific LOOP AT statement, which is optimized for operations on internal tables.
TYPES: BEGIN OF s_demo,
comp1 TYPE i,
comp2 TYPE c LENGTH 3,
END OF s_demo,
ty_tab_demo TYPE TABLE OF s_demo WITH EMPTY KEY.
DATA(it) = VALUE ty_tab_demo(
( comp1 = 1 comp2 = 'A' )
( comp1 = 1 comp2 = 'B' )
( comp1 = 1 comp2 = 'C' )
( comp1 = 2 comp2 = 'A' )
( comp1 = 2 comp2 = 'E' )
( comp1 = 1 comp2 = 'D' )
( comp1 = 3 comp2 = 'D' )
( comp1 = 3 comp2 = 'G' )
( comp1 = 4 comp2 = 'C' )
( comp1 = 5 comp2 = 'A' ) ).
"------------------ SELECT ------------------
"Note: The SELECT command is executed on the database. Therefore, the pseudo
"comment is include to suppress a syntax warning.
SELECT comp1, COUNT(*) AS count
FROM @it AS it
GROUP BY comp1
HAVING COUNT(*) > 1
INTO TABLE @DATA(duplicates_sql) ##ITAB_DB_SELECT.
"------------------ LOOP AT ------------------
TYPES: BEGIN OF duplicates_struc,
comp1 TYPE s_demo-comp1,
count TYPE i,
END OF duplicates_struc,
ty_tab_duplicates TYPE TABLE OF duplicates_struc WITH EMPTY KEY.
DATA duplicates_loop_at TYPE ty_tab_duplicates.
LOOP AT it INTO DATA(wa) GROUP BY ( comp = wa-comp1 size = GROUP SIZE ) ASCENDING REFERENCE INTO DATA(group_ref).
IF group_ref->size > 1.
APPEND VALUE #( comp1 = group_ref->comp count = group_ref->size ) TO duplicates_loop_at.
ENDIF.
ENDLOOP.More information:
The following example explores various SELECT queries with internal tables as data sources. To try it out, create a demo class named zcl_demo_abap. If it already exists, reuse it. Otherwise, create a new class with a different name. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example uses objects of the ABAP cheat sheets repository and is set up to display output in the console.
Example:
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
"----------- Exploiting ABAP SQL functionality with internal tables -----------
TYPES int_tab_type TYPE TABLE OF i WITH EMPTY KEY.
DATA(itab_a) = VALUE int_tab_type( ( 1 ) ( 32 ) ( 100 ) ( -24 ) ( 17 ) ( 99 ) ).
"SELECT query with an internal table as data source
"The example uses an aggregate expression. It is statically
"detected that the query cannot be processed by the ABAP SQL
"engine. The data must be passed to the database. Consequently,
"a syntax warning is displayed. It can be suppressed by a pragma.
SELECT MAX( table_line ) AS max_val
FROM @itab_a AS it
INTO @DATA(max_a).
"100
SELECT MAX( table_line ) AS max_val ##itab_db_select
FROM @itab_a AS it
INTO @DATA(max_b).
out->write( max_a ).
out->write( max_b ).
"Using the LIKE addition in the WHERE clause to extract internal table
"entries matching a specific pattern.
TYPES: BEGIN OF s1,
a TYPE c LENGTH 3,
b TYPE i,
END OF s1,
it_type_1 TYPE TABLE OF s1 WITH EMPTY KEY.
DATA(itab_b) = VALUE it_type_1( ( a = 'abc' b = 1 )
( a = 'zbc' b = 2 )
( a = 'bde' b = 3 )
( a = 'yde' b = 4 ) ).
SELECT a, b
FROM @itab_b AS it_alias
WHERE a LIKE '%bc'
INTO TABLE @DATA(select_like_result).
*A B
*abc 1
*zbc 2
out->write( select_like_result ).
"----------- Using a SELECT loop with an internal table as data source -----------
TYPES: BEGIN OF s2,
comp1 TYPE c LENGTH 2,
comp2 TYPE i,
END OF s2,
it_type_2 TYPE TABLE OF s2 WITH EMPTY KEY.
DATA(itab_c) = VALUE it_type_2( ( comp1 = 'aa' comp2 = 2 )
( comp1 = 'zz' comp2 = 9 )
( comp1 = 'dd' comp2 = 1 )
( comp1 = 'rr' comp2 = 7 )
( comp1 = 'tt' comp2 = 5 )
( comp1 = 'bb' comp2 = 6 ) ).
DATA itab_d TYPE int_tab_type.
"The following SELECT loop specifies an internal table as data source.
"The loop sequence is defined by a sort order. Such a functionality is
"not available with LOOP AT.
SELECT comp2
FROM @itab_c AS it
ORDER BY comp2 DESCENDING
INTO @DATA(wa).
INSERT wa INTO TABLE itab_d.
ENDSELECT.
*9
*7
*6
*5
*2
*1
out->write( itab_d ).
"------------------- Joins with internal tables -------------------
TYPES: BEGIN OF s3,
a TYPE c LENGTH 3,
b TYPE c LENGTH 3,
c TYPE i,
END OF s3,
it_type_3 TYPE TABLE OF s3 WITH EMPTY KEY.
DATA(itab_e) = VALUE it_type_3( ( a = 'aaa' b = 'bbb' c = 1 )
( a = 'ccc' b = 'ddd' c = 1 )
( a = 'eee' b = 'fff' c = 2 ) ).
DATA(itab_f) = VALUE it_type_3( ( a = 'ggg' b = 'hhh' c = 1 )
( a = 'iii' b = 'jjj' c = 1 )
( a = 'kkk' b = 'lll' c = 3 ) ).
"No syntax warning. The internal tables can be processed by the
"ABAP SQL engine.
SELECT it_alias1~a, it_alias2~b
FROM @itab_e AS it_alias1
INNER JOIN @itab_f AS it_alias2 ON it_alias1~c = it_alias2~c
INTO TABLE @DATA(itab_g).
*A B
*aaa hhh
*aaa jjj
*ccc hhh
*ccc jjj
out->write( itab_g ).
"Join with a database table and an internal table
"Preparing a demo database table and an internal table
DELETE FROM zdemo_abap_tab1.
INSERT zdemo_abap_tab1 FROM TABLE @( VALUE #( ( key_field = 1 char1 = 'aaa' )
( key_field = 2 char1 = 'bbb' )
( key_field = 3 char1 = 'ccc' ) ) ).
TYPES it_type_4 TYPE TABLE OF zdemo_abap_tab1 WITH EMPTY KEY.
DATA(itab_h) = VALUE it_type_4( ( key_field = 1 char2 = 'zzz' )
( key_field = 2 char2 = 'yyy' ) ).
SELECT db~key_field, db~char1, it~char2
FROM zdemo_abap_tab1 AS db
INNER JOIN @itab_h AS it ON it~key_field = db~key_field
INTO TABLE @DATA(itab_i).
*KEY_FIELD CHAR1 CHAR2
*1 aaa zzz
*2 bbb yyy
out->write( itab_i ).
ENDMETHOD.
ENDCLASS.SELECT list or WHERE clause. utclong.sstring (for example, a component typed with a data element or a CDS simple type that uses sstring).The following example demonstrates various SELECT statements. A demo internal table has a component that is typed with a CDS simple type, which can be created as follows:
zdemo_abap_string) and a description.The code of the CDS simple type may look as follows:
@EndUserText.label: 'String type'
define type zdemo_abap_string: abap.sstring( 1333 );Commented code example:
*&---------------------------------------------------------------------*
*& Internal table having a component of type string
*&---------------------------------------------------------------------*
"Creating and populating an internal table that includes a component that is
"typed with type string
TYPES: BEGIN OF s1,
comp1 TYPE i,
comp2 TYPE c LENGTH 3,
comp3 TYPE string,
END OF s1,
tab_type_1 TYPE TABLE OF s1 WITH EMPTY KEY.
DATA(itab1) = VALUE tab_type_1( ( comp1 = 1 comp2 = 'aaa' comp3 = `ABAP` ) ).
"Columns of type string cannot be used in SELECT statements that
"select from internal tables (* selects all fields). Therefore, the
"following statements are commented out.
"SELECT SINGLE * FROM @itab1 AS it WHERE comp1 = 1 INTO @DATA(res1).
"SELECT SINGLE comp1 FROM @itab1 AS it where comp3 = `ABAP` INTO @DATA(res2).
"SELECT SINGLE comp3 FROM @itab1 AS it where comp1 = 1 INTO @DATA(res3).
"However, the following statements work. No component of type string is involved.
SELECT SINGLE comp1 FROM @itab1 AS it WHERE comp2 = 'aaa' INTO @DATA(res4).
SELECT SINGLE comp1, comp2 FROM @itab1 AS it INTO @DATA(res5).
*&---------------------------------------------------------------------*
*& Internal table having a component typed with a CDS simple type (sstring)
*&---------------------------------------------------------------------*
"Creating and populating an internal table that includes a component that is typed
"with a CDS simple type (sstring). Note: Built-in DDIC types such as sstring cannot
"directly be used in ABAP statements, except for typed literals.
TYPES: BEGIN OF s2,
comp1 TYPE i,
comp2 TYPE c LENGTH 3,
comp3 TYPE zdemo_abap_string,
END OF s2,
tab_type_2 TYPE TABLE OF s2 WITH EMPTY KEY.
DATA(itab2) = VALUE tab_type_2( ( comp1 = 1 comp2 = 'aaa' comp3 = `ABAP` ) ).
"Unlike above, the following SELECT statements are possible
SELECT SINGLE * FROM @itab2 AS it WHERE comp1 = 1 INTO @DATA(res6).
SELECT SINGLE comp1 FROM @itab2 AS it WHERE comp3 = `ABAP` INTO @DATA(res7).
SELECT SINGLE comp1, comp3 FROM @itab2 AS it WHERE comp2 = 'aaa' AND comp3 = `ABAP` INTO @DATA(res8).
"Note: `ABAP` represents a literal of type string. When specifying it here
"like it is specified above, there is an implicit conversion. The following
"example uses a typed literal.
SELECT SINGLE comp1, comp2, comp3 FROM @itab2 AS it WHERE comp3 = sstring`ABAP` INTO @DATA(res9).The following code snippets demonstrate joining/merging the content of two simple internal tables into another table. There may be several ways to achieve this. Here, the intention is to give an idea and, in particular, to emphasize SQL functionalities also available for internal tables (note the restrictions mentioned above and in the documentation).
Assumptions:
The code snippet shows a selection of syntax options and includes the following statements:
SELECT (INNER JOIN, LEFT OUTER JOIN, CTE)LOOP AT statements, FOR loops using the VALUE and REDUCE operators"Creating two internal tables whose content will be joined. The shared
"value is represented by the key1 and key2 components.
"Sorted tables are used in the example (having key1/key2 as unique keys)
"to have unique values to perform joins.
TYPES: BEGIN OF s1,
key1 TYPE i,
a TYPE c LENGTH 1,
b TYPE c LENGTH 1,
c TYPE c LENGTH 1,
END OF s1,
tab_type1 TYPE SORTED TABLE OF s1 WITH UNIQUE KEY key1,
BEGIN OF s2,
key2 TYPE i,
d TYPE c LENGTH 1,
e TYPE c LENGTH 1,
END OF s2,
tab_type2 TYPE SORTED TABLE OF s2 WITH UNIQUE KEY key2.
"Populating demo internal tables
DATA(itab1) = VALUE tab_type1( ( key1 = 1 a = 'a' b = 'b' c = 'c' )
( key1 = 2 a = 'd' b = 'e' c = 'f' )
( key1 = 3 a = 'g' b = 'h' c = 'i' ) ).
DATA(itab2) = VALUE tab_type2( ( key2 = 1 d = 'j' e = 'k' )
( key2 = 2 d = 'l' e = 'm' ) ).
"SELECT statement, inner join
"Note: With the inner join, the target table contains all
"combinations of rows for whose columns the join condition
"is true.
SELECT a~key1, a~a, a~b, b~d, b~e
FROM @itab1 AS a
INNER JOIN @itab2 AS b ON a~key1 = b~key2
INTO TABLE @DATA(itab3).
*Result
*KEY1 A B D E
*1 a b j k
*2 d e l m
"SELECT statement, left outer join
"In contrast to the inner join above, the target table here
"also contains the table row of the first table for which
"no equivalent row exists in the second table.
SELECT a~key1, a~a, a~b, b~d, b~e
FROM @itab1 AS a
LEFT OUTER JOIN @itab2 AS b ON a~key1 = b~key2
INTO TABLE @DATA(itab4).
*Result
*KEY1 A B D E
*1 a b j k
*2 d e l m
*3 g h
*&---------------------------------------------------------------------*
*& Note: The following statements produce the same result as the
*& previous example (itab4).
*&---------------------------------------------------------------------*
"Common table expression
WITH +it1 AS ( SELECT a~key1, a~a, a~b FROM @itab1 AS a ),
+it2 AS ( SELECT b~key2, b~d, b~e FROM @itab2 AS b )
SELECT +it1~key1, +it1~a, +it1~b, +it2~d, +it2~e FROM +it1 LEFT JOIN +it2 ON +it1~key1 = +it2~key2
INTO TABLE @DATA(itab5).
"LOOP statements
"Using the CORRESPONDING operator to assign identically named components,
"BASE retains existing content
"The assignment with CORRESPONDING ... BASE ... includes a table expression
"in which table lines are read and inserted based on the key mapping. With the
"OPTIONAL addition, errors can be avoided if a line does not exist.
DATA itab6 LIKE itab4.
LOOP AT itab1 INTO DATA(wa1).
INSERT CORRESPONDING #( wa1 ) INTO TABLE itab6 REFERENCE INTO DATA(ref).
ref->* = CORRESPONDING #( BASE ( ref->* ) VALUE #( itab2[ key2 = ref->key1 ] OPTIONAL ) ).
ENDLOOP.
"Assume the second table's shared component was also key1. In the second CORRESPONDING
"you could then work with the EXCEPT addition to not overwrite the identically named
"component.
"Example similar to the previous one
"Also here, a table expression is used to read a line from
"the second internal table. The INSERT statement (without
"CORRESPONDING) includes the concrete value assignments
"with the VALUE operator.
DATA itab7 LIKE itab4.
LOOP AT itab1 INTO DATA(wa2).
DATA(line) = VALUE #( itab2[ key2 = wa2-key1 ] OPTIONAL ).
INSERT VALUE #( key1 = wa2-key1
a = wa2-a
b = wa2-b
d = line-d
e = line-e ) INTO TABLE itab7.
ENDLOOP.
"Example using a FOR loop with the VALUE operator
TYPES tt_type3 LIKE itab4.
DATA(itab8) = VALUE tt_type3( FOR wa3 IN itab1
( key1 = wa3-key1
a = wa3-a
b = wa3-b
d = VALUE #( itab2[ key2 = wa3-key1 ]-d OPTIONAL )
e = VALUE #( itab2[ key2 = wa3-key1 ]-e OPTIONAL ) ) ).
"Similar example that includes a LET expression
DATA(itab9) = VALUE tt_type3( FOR wa4 IN itab1
LET tab_line = VALUE #( itab2[ key2 = wa4-key1 ] OPTIONAL ) IN
( key1 = wa4-key1
a = wa4-a
b = wa4-b
d = tab_line-d
e = tab_line-e ) ).
"Example using a FOR loop with the REDUCE operator and LET
DATA(itab10) = REDUCE tt_type3( INIT tab = VALUE #( )
FOR wa5 IN itab1
LET tableline = VALUE #( itab2[ key2 = wa5-key1 ] OPTIONAL ) IN
NEXT tab = VALUE #( BASE tab
( key1 = wa5-key1
a = wa5-a
b = wa5-b
d = tableline-d
e = tableline-e ) ) ).This section serves as an overview of specific aspects of secondary table keys in internal tables. Some of these topics are also discussed in previous sections.
| Subject | Notes |
|---|---|
|
General aspects |
Secondary table keys:
|
|
How to declare |
Note that the following snippet demonstrates internal table declarations using |
|
When and why to use |
|
|
How to use |
|
|
Additional notes |
|
The code snippet shows internal table declarations, illustrating the specification of secondary table keys.
"Demo structured type
TYPES: BEGIN OF s,
comp1 TYPE i,
comp2 TYPE i,
comp3 TYPE c LENGTH 3,
END OF s.
*&---------------------------------------------------------------------*
*& Internal table declaration options with secondary table keys
*&---------------------------------------------------------------------*
"-------------------- Standard tables --------------------
"Standard table with non-unique sorted secondary table key
DATA it_std_non_uni_so_sk TYPE TABLE OF s WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY nu_so COMPONENTS comp1.
"Standard table with unique sorted secondary table key
DATA it_std_uni_so_sk TYPE TABLE OF s WITH EMPTY KEY
WITH UNIQUE SORTED KEY uni_so COMPONENTS comp1.
"Standard table with hashed secondary table key
DATA it_std_hash_sk TYPE TABLE OF s WITH EMPTY KEY
WITH UNIQUE HASHED KEY hak COMPONENTS comp1.
"-------------------- Sorted tables --------------------
"Sorted table with non-unique sorted secondary table key
DATA it_so_non_uni_so_sk TYPE SORTED TABLE OF s WITH NON-UNIQUE KEY comp1
WITH NON-UNIQUE SORTED KEY nu_so COMPONENTS comp2.
"Sorted table with non-unique sorted secondary table key
DATA it_so_uni_so_sk TYPE SORTED TABLE OF s WITH NON-UNIQUE KEY comp1
WITH UNIQUE SORTED KEY uni_so COMPONENTS comp2.
"Sorted table with hashed secondary table key
DATA it_so_hash_sk TYPE SORTED TABLE OF s WITH NON-UNIQUE KEY comp1
WITH UNIQUE HASHED KEY hak COMPONENTS comp2.
"-------------------- Hashed tables --------------------
"Hashed table with non-unique sorted secondary table key
DATA it_ha_non_uni_so_sk TYPE HASHED TABLE OF s WITH UNIQUE KEY comp1
WITH NON-UNIQUE SORTED KEY nu_so COMPONENTS comp2.
"Hashed table with non-unique sorted secondary table key
DATA it_ha_uni_so_sk TYPE HASHED TABLE OF s WITH UNIQUE KEY comp1
WITH UNIQUE SORTED KEY uni_so COMPONENTS comp2.
"Hashed table with hashed secondary table key
DATA it_ha_hash_sk TYPE HASHED TABLE OF s WITH UNIQUE KEY comp1
WITH UNIQUE HASHED KEY hak COMPONENTS comp2.
"-------------------- Secondary table keys with alias names --------------------
"Standard table with non-unique sorted secondary table key
DATA it_std_non_uni_so_sk_al TYPE TABLE OF s WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY nu_so ALIAS s1 COMPONENTS comp1.
"Sorted table with non-unique sorted secondary table key
DATA it_so_non_uni_so_sk_al TYPE SORTED TABLE OF s WITH NON-UNIQUE KEY comp1
WITH NON-UNIQUE SORTED KEY nu_so ALIAS s2 COMPONENTS comp2.
"Hashed table with hashed secondary table key
DATA it_ha_hash_sk_al TYPE HASHED TABLE OF s WITH UNIQUE KEY comp1
WITH UNIQUE HASHED KEY hak ALIAS s3 COMPONENTS comp2.
*&---------------------------------------------------------------------*
*& Table type declaration
*&---------------------------------------------------------------------*
"Table type: Hashed table with hashed secondary table key
TYPES ha_hash_sk_al TYPE HASHED TABLE OF s WITH UNIQUE KEY comp1
WITH UNIQUE HASHED KEY hak ALIAS s3 COMPONENTS comp2.
DATA it_ha_hash_sk_al_2 TYPE ha_hash_sk_al.The following example illustrates various internal table processing statements using secondary table keys. The snippets primarily use a demo table with a non-unique sorted secondary table key to demonstrate the statements without risking duplicate keys.
[!NOTE] When adding content to internal tables declared with non-unique sorted secondary table keys, entries with identical keys are placed above existing entries.
Expand the following collapsible section for example code. To try it out, create a demo class named zcl_demo_abap, or reuse it if it already exists. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example is set up to display output in the console.
🟢 Click to expand for example code
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
"Demo structured type
TYPES: BEGIN OF s,
comp1 TYPE i,
comp2 TYPE i,
comp3 TYPE c LENGTH 3,
END OF s.
"Internal table declarations to work with in the example
DATA it_std_empty_pr_key TYPE TABLE OF s WITH EMPTY KEY.
"Standard table with non-unique sorted secondary table key
DATA it_std_non_uni_so_sk TYPE TABLE OF s WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY nu_so COMPONENTS comp1.
"Standard table with unique sorted secondary table key
DATA it_std_uni_so_sk TYPE TABLE OF s WITH EMPTY KEY
WITH UNIQUE SORTED KEY uni_so COMPONENTS comp1.
"Standard table with hashed secondary table key
DATA it_std_hash_sk TYPE TABLE OF s WITH EMPTY KEY
WITH UNIQUE HASHED KEY hak COMPONENTS comp1.
"Dummy table to illustrate the secondary table index
DATA it_demo_table_key_index TYPE TABLE OF s WITH EMPTY KEY.
METHODS:
set_example_divider IMPORTING out TYPE REF TO if_oo_adt_classrun_out
text TYPE string,
m01_populate_itabs IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m02_loop_at IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m03_read_table IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m04_table_expressions IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m05_insert IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m06_append IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m07_modify IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string,
m08_delete IMPORTING out TYPE REF TO if_oo_adt_classrun_out text TYPE string.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
"Populating internal table with demo values
it_std_empty_pr_key = VALUE #( ( comp1 = 3 comp2 = 333 comp3 = 'i' )
( comp1 = 1 comp2 = 111 comp3 = 'c' )
( comp1 = 4 comp2 = 44 comp3 = 'j' )
( comp1 = 2 comp2 = 22 comp3 = 'e' )
( comp1 = 1 comp2 = 11 comp3 = 'b' )
( comp1 = 3 comp2 = 3 comp3 = 'g' )
( comp1 = 2 comp2 = 222 comp3 = 'f' )
( comp1 = 1 comp2 = 1 comp3 = 'a' )
( comp1 = 2 comp2 = 2 comp3 = 'd' )
( comp1 = 3 comp2 = 33 comp3 = 'h' )
( comp1 = 4 comp2 = 444 comp3 = 'k' )
( comp1 = 4 comp2 = 4 comp3 = 'l' ) ).
"Dynamically calling methods of the class
"The method names are retrieved using RTTI. For more information, refer to the
"Dynamic Programming ABAP cheat sheet.
"Only those methods should be called that follow the naming convention M + digit.
DATA(methods) = CAST cl_abap_classdescr( cl_abap_typedescr=>describe_by_object_ref( me ) )->methods.
SORT methods BY name ASCENDING.
LOOP AT methods INTO DATA(meth_wa).
TRY.
"The find function result indicates that the method name begins (offset = 0) with M and a digit.
IF find( val = meth_wa-name pcre = `^M\d` case = abap_false ) = 0.
CALL METHOD (meth_wa-name) EXPORTING out = out text = CONV string( meth_wa-name ).
ENDIF.
CATCH cx_root INTO DATA(error).
out->write( error->get_text( ) ).
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD m01_populate_itabs.
set_example_divider( out = out text = |{ text }: Populating the internal tables with secondary table keys| ).
"Assigning the source to the target table
"The following assignment works as the target table does not specify
"a unique secondary table key.
it_std_non_uni_so_sk = it_std_empty_pr_key.
out->write( `Assignment result (1):` ).
out->write( it_std_non_uni_so_sk ).
out->write( |\n| ).
"Due to unique keys in the target tables and duplicate keys in the
"source table, the following assignments would trigger a runtime error.
* it_std_uni_so_sk = it_std_empty_pr_key.
* it_std_hash_sk = it_std_empty_pr_key.
"Using an INSERT statement to populate the internal table with secondary table keys
"The source table is looped over, and individual table lines are added to the
"target table. The statement illustrates that the INSERT statement supports
"the raising of a catchable exception regarding duplicates and unique secondary
"table keys.
LOOP AT it_std_empty_pr_key INTO DATA(wa).
TRY.
INSERT wa INTO TABLE it_std_uni_so_sk.
CATCH cx_sy_itab_duplicate_key INTO DATA(error).
out->write( error->get_text( ) ).
out->write( |Affected row: { wa-comp1 }, { wa-comp2 }, { wa-comp3 }| ).
ENDTRY.
ENDLOOP.
out->write( |\n| ).
out->write( `Assignment result (2):` ).
out->write( it_std_uni_so_sk ).
out->write( |\n| ).
"Using the CORRESPONDING operator and the DISCARDING DUPLICATES addition
it_std_hash_sk = CORRESPONDING #( it_std_empty_pr_key DISCARDING DUPLICATES ).
out->write( `Assignment result (3):` ).
out->write( it_std_hash_sk ).
ENDMETHOD.
METHOD m02_loop_at.
set_example_divider( out = out text = |{ text }: LOOP AT statements using secondary table keys| ).
"The following examples illustrate the primary and secondary table index.
"Notes on the resulting table that illustrates the secondary table index of
"the source table that is looped over using the secondary table key:
"- Non-unique secondary keys: When assiging table lines, the entries having an
" identical key are put on top of existing entries with that key.
"- Unique secondary keys: The mechanism is similar to above, however, the
" entry with identical is discarded - if a statement with appropriate addition
" is used. Otherwise, a runtime error occurs since a duplicate key violation
" occurs regarding the secondary key.
CLEAR it_demo_table_key_index.
"Demonstrating primary table index (no USING KEY addition)
LOOP AT it_std_non_uni_so_sk INTO DATA(wa).
APPEND wa TO it_demo_table_key_index.
ENDLOOP.
out->write( `Illustrating the primary table index (1):` ).
out->write( it_demo_table_key_index ).
out->write( |\n| ).
DATA(copy) = it_demo_table_key_index.
CLEAR it_demo_table_key_index.
"Demonstrating primary table index (USING KEY addition)
LOOP AT it_std_non_uni_so_sk INTO wa USING KEY primary_key.
APPEND wa TO it_demo_table_key_index.
ENDLOOP.
out->write( `Illustrating the primary table index (2):` ).
out->write( it_demo_table_key_index ).
out->write( |\n| ).
ASSERT copy = it_demo_table_key_index.
CLEAR it_demo_table_key_index.
"Demonstrating secondary table index (USING KEY addition)
LOOP AT it_std_non_uni_so_sk INTO wa USING KEY nu_so.
APPEND wa TO it_demo_table_key_index.
ENDLOOP.
out->write( `Illustrating the secondary table index:` ).
out->write( it_demo_table_key_index ).
CLEAR it_demo_table_key_index.
ENDMETHOD.
METHOD m03_read_table.
set_example_divider( out = out text = |{ text }: READ TABLE statements using secondary table keys| ).
"WITH TABLE KEY addition
READ TABLE it_std_non_uni_so_sk INTO DATA(wa) WITH TABLE KEY nu_so COMPONENTS comp1 = 1.
IF sy-subrc = 0.
DATA(tabix) = sy-tabix.
out->write( wa ).
out->write( |\n| ).
out->write( |Secondary table index: { tabix }| ).
ELSE.
out->write( `Table entry not found` ).
ENDIF.
out->write( |\n| ).
out->write( |\n| ).
READ TABLE it_std_uni_so_sk INTO wa WITH TABLE KEY uni_so COMPONENTS comp1 = 1.
IF sy-subrc = 0.
tabix = sy-tabix.
out->write( wa ).
out->write( |\n| ).
out->write( |Secondary table index: { tabix }| ).
ELSE.
out->write( `Table entry not found` ).
ENDIF.
out->write( |\n| ).
out->write( |\n| ).
READ TABLE it_std_hash_sk INTO wa WITH TABLE KEY hak COMPONENTS comp1 = 1.
IF sy-subrc = 0.
tabix = sy-tabix.
out->write( wa ).
out->write( |\n| ).
"Hash key -> no sy-tabix value
out->write( |Secondary table index: { tabix }| ).
ELSE.
out->write( `Table entry not found` ).
ENDIF.
out->write( |\n| ).
out->write( |\n| ).
"WITH KEY addition
"Allows more components to be specified, not just those of the table key specified
READ TABLE it_std_non_uni_so_sk INTO wa WITH KEY nu_so COMPONENTS comp1 = 1 comp2 = 111.
IF sy-subrc = 0.
tabix = sy-tabix.
out->write( wa ).
out->write( |\n| ).
out->write( |Secondary table index: { tabix }| ).
ELSE.
out->write( `Table entry not found` ).
ENDIF.
out->write( |\n| ).
out->write( |\n| ).
"This entry will not be found.
READ TABLE it_std_uni_so_sk INTO wa WITH KEY uni_so COMPONENTS comp1 = 1 comp2 = 1.
IF sy-subrc = 0.
tabix = sy-tabix.
out->write( wa ).
out->write( |\n| ).
out->write( |Secondary table index: { tabix }| ).
ELSE.
out->write( `Table entry not found` ).
ENDIF.
ENDMETHOD.
METHOD m04_table_expressions.
set_example_divider( out = out text = |{ text }: Table expressions using secondary table keys| ).
"TABLE KEY addition
DATA(wa) = it_std_non_uni_so_sk[ TABLE KEY nu_so COMPONENTS comp1 = 1 ].
out->write( wa ).
out->write( |\n| ).
"KEY addition
wa = it_std_non_uni_so_sk[ KEY nu_so COMPONENTS comp1 = 1 comp2 = 111 ].
out->write( wa ).
out->write( |\n| ).
TRY.
wa = it_std_uni_so_sk[ KEY uni_so COMPONENTS comp1 = 1 comp2 = 1 ].
out->write( wa ).
CATCH cx_sy_itab_line_not_found INTO DATA(not_found).
out->write( not_found->get_text( ) ).
ENDTRY.
out->write( |\n| ).
"Getting the line index
DATA(tabix) = line_index( it_std_non_uni_so_sk[ KEY nu_so COMPONENTS comp1 = 1 comp2 = 111 ] ).
out->write( |Secondary table index: { tabix }| ).
ENDMETHOD.
METHOD m05_insert.
set_example_divider( out = out text = |{ text }: INSERT statements using secondary table keys| ).
"The example illustrates block-wise insertions into a target internal
"table with and without using the USING KEY specification. It uses the
"demo table with a non-unique secondary sorted key.
"Note that the order for the insertion is affected by the table key
"specification.
DATA tab LIKE it_std_non_uni_so_sk.
DATA copy1 LIKE it_std_non_uni_so_sk.
DATA copy2 LIKE it_std_non_uni_so_sk.
copy1 = it_std_non_uni_so_sk.
copy2 = it_std_non_uni_so_sk.
tab = VALUE #( ( comp1 = 5 comp2 = 55 comp3 = 'm' )
( comp1 = 4 comp2 = 4444 comp3 = 'n' )
( comp1 = 5 comp2 = 5 comp3 = 'o' ) ).
"Insert using a secondary table key
INSERT LINES OF tab USING KEY nu_so INTO TABLE copy1.
out->write( copy1 ).
out->write( |\n| ).
"Insert using the primary table key (implicitly)
INSERT LINES OF tab INTO TABLE copy2.
out->write( copy2 ).
out->write( |\n| ).
CLEAR it_demo_table_key_index.
LOOP AT copy1 INTO DATA(wa) USING KEY nu_so.
APPEND wa TO it_demo_table_key_index.
ENDLOOP.
DATA(copy) = it_demo_table_key_index.
CLEAR it_demo_table_key_index.
LOOP AT copy2 INTO wa USING KEY nu_so.
APPEND wa TO it_demo_table_key_index.
ENDLOOP.
IF copy = it_demo_table_key_index.
out->write( `The table indexes reflect the same line sequence.` ).
ELSE.
out->write( `The table indexes reflect a different line sequence.` ).
ENDIF.
CLEAR it_demo_table_key_index.
ENDMETHOD.
METHOD m06_append.
"See the notes for INSERT above.
set_example_divider( out = out text = |{ text }: APPEND statements using secondary table keys| ).
DATA tab LIKE it_std_non_uni_so_sk.
DATA copy3 LIKE it_std_non_uni_so_sk.
DATA copy4 LIKE it_std_non_uni_so_sk.
copy3 = it_std_non_uni_so_sk.
copy4 = it_std_non_uni_so_sk.
tab = VALUE #( ( comp1 = 5 comp2 = 55 comp3 = 'm' )
( comp1 = 4 comp2 = 4444 comp3 = 'n' )
( comp1 = 5 comp2 = 5 comp3 = 'o' ) ).
APPEND LINES OF tab USING KEY nu_so TO copy3.
out->write( copy3 ).
out->write( |\n| ).
APPEND LINES OF tab TO copy4.
out->write( copy4 ).
out->write( |\n| ).
CLEAR it_demo_table_key_index.
LOOP AT copy3 INTO DATA(wa) USING KEY nu_so.
APPEND wa TO it_demo_table_key_index.
ENDLOOP.
DATA(copy) = it_demo_table_key_index.
CLEAR it_demo_table_key_index.
LOOP AT copy4 INTO wa USING KEY nu_so.
APPEND wa TO it_demo_table_key_index.
ENDLOOP.
IF copy = it_demo_table_key_index.
out->write( `The table indexes reflect the same line sequence.` ).
ELSE.
out->write( `The table indexes reflect a different line sequence.` ).
ENDIF.
CLEAR it_demo_table_key_index.
ENDMETHOD.
METHOD m07_modify.
set_example_divider( out = out text = |{ text }: MODIFY statements using secondary table keys| ).
DATA line LIKE LINE OF it_std_non_uni_so_sk.
line = VALUE #( comp1 = 3 comp2 = 3333 comp3 = '###' ).
MODIFY TABLE it_std_non_uni_so_sk
USING KEY nu_so
FROM line.
out->write( it_std_non_uni_so_sk ).
out->write( |\n| ).
"The illustration of the secondary table index shows that
"the first entry with comp1 = 3 was changed.
CLEAR it_demo_table_key_index.
LOOP AT it_std_non_uni_so_sk INTO DATA(wa) USING KEY nu_so.
APPEND wa TO it_demo_table_key_index.
ENDLOOP.
out->write( it_demo_table_key_index ).
ENDMETHOD.
METHOD m08_delete.
set_example_divider( out = out text = |{ text }: DELETE statements using secondary table keys| ).
"Retrieving all comp1 = 3 values
"The line order reflects the order in the secondary table. The comp3
"value is assigned the table index value reflecting the position in the
"secondary table index.
CLEAR it_demo_table_key_index.
LOOP AT it_std_non_uni_so_sk INTO DATA(wa) USING KEY nu_so WHERE comp1 = 3.
wa-comp3 = sy-tabix.
APPEND wa TO it_demo_table_key_index.
ENDLOOP.
out->write( it_demo_table_key_index ).
out->write( |\n| ).
"This entry gets deleted with the following DELETE statement.
DATA(first_entry_in_sec_index) = it_demo_table_key_index[ 1 ].
DATA line LIKE LINE OF it_std_non_uni_so_sk.
line = VALUE #( comp1 = 3 ).
DELETE TABLE it_std_non_uni_so_sk FROM line USING KEY nu_so.
out->write( it_std_non_uni_so_sk ).
out->write( |\n| ).
IF line_exists( it_std_non_uni_so_sk[ KEY nu_so comp1 = first_entry_in_sec_index-comp1
comp2 = first_entry_in_sec_index-comp2 ] ).
out->write( |The first entry in the secondary table index with the value comp1 = { first_entry_in_sec_index-comp1 } was not deleted.| ).
ELSE.
out->write( |The first entry in the secondary table index with the value comp1 = { first_entry_in_sec_index-comp1 } was deleted.| ).
ENDIF.
ENDMETHOD.
METHOD set_example_divider.
out->write( |\n| ).
out->write( |*&{ repeat( val = `-` occ = strlen( text ) + 2 ) }*| ).
out->write( |*& { text }| ).
out->write( |*&{ repeat( val = `-` occ = strlen( text ) + 2 ) }*| ).
out->write( |\n| ).
out->write( |\n| ).
ENDMETHOD.
ENDCLASS.The following example class illustrates aspects of update behavior and performance concerning the use of secondary table keys in internal tables. Among them:
[!NOTE] This example is for exploration, experimentation, and demonstration. It is not intended for accurate runtime or performance testing and is not a suitable method for such purposes. Due to its simplified nature, results may vary and not be entirely accurate, even across multiple runs. It is similarly set up as the example in section Exploring Read Access Performance with Internal Tables. Check more information and, particularly, the disclaimer regarding the runtime measurement there.
The example includes runtime performance evaluations using these comparisons:
Notes on the example implementation:
DO loop. One entry provides a benchmark score of 100, while the other average runtimes are compared to this score to demonstrate the percentage difference in average runtime.Notes on the expected outcome:
Expand the following collapsible section for example code. To try it out, create a demo class named zcl_demo_abap, or reuse it if it already exists. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example is set up to display output in the console.
🟢 Click to expand for example code
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
comparison1_nuk_primary_std,
comparison1_nuk_primary_so,
comparison1_nuk_sec_key_std_a,
comparison1_nuk_sec_key_std_b,
comparison2_nuk_sec_insert,
comparison2_uk_sec_insert,
comparison3_nu_so_sec_key_rd_a,
comparison3_nu_so_sec_key_rd_b,
comparison3_uni_so_sec_key_rd,
comparison3_uni_ha_sec_key_rd,
store_runtime IMPORTING high TYPE utclong low TYPE utclong,
get_avg_runtime RETURNING VALUE(avg_runtime) TYPE decfloat34,
calculate_score.
"Demo structured type
TYPES: BEGIN OF s,
comp1 TYPE c LENGTH 10,
comp2 TYPE c LENGTH 10,
comp3 TYPE c LENGTH 10,
comp4 TYPE c LENGTH 10,
comp5 TYPE c LENGTH 10,
comp6 TYPE c LENGTH 10,
comp7 TYPE c LENGTH 10,
comp8 TYPE c LENGTH 10,
comp9 TYPE c LENGTH 10,
comp10 TYPE c LENGTH 10,
END OF s.
"Internal table declarations to work with in the example
DATA itab_content TYPE TABLE OF s WITH EMPTY KEY.
"Standard table with non-unique primary table key
DATA it_std_nu_pr_key TYPE TABLE OF s WITH NON-UNIQUE KEY primary_key COMPONENTS comp1 comp2 comp3 comp4 comp5.
"Standard table with empty primary table key and non-unique, sorted secondary table key
DATA it_std_nu_so_sk TYPE TABLE OF s WITH EMPTY KEY
WITH NON-UNIQUE SORTED KEY nu_so COMPONENTS comp1 comp2 comp3 comp4 comp5.
"Sorted table with non-unique, sorted primary table key
DATA it_so_nu_pr_key TYPE SORTED TABLE OF s WITH NON-UNIQUE KEY primary_key COMPONENTS comp1 comp2 comp3 comp4 comp5.
"Standard table with empty primary table key and unique, sorted secondary table key
DATA it_std_uni_so_sk TYPE TABLE OF s WITH EMPTY KEY
WITH UNIQUE SORTED KEY uni_so COMPONENTS comp1 comp2 comp3 comp4 comp5.
"Standard table with empty primary table key and unique, hashed secondary table key
DATA it_std_hash_sk TYPE TABLE OF s WITH EMPTY KEY
WITH UNIQUE HASHED KEY hak COMPONENTS comp1 comp2 comp3 comp4 comp5.
DATA: ts1 TYPE utclong,
runtime_tab TYPE TABLE OF decfloat34 WITH EMPTY KEY,
idx_char TYPE c LENGTH 10.
CONSTANTS: table_lines_10k TYPE i VALUE 10000,
loop_count_1k TYPE i VALUE 1000,
benchmark100 TYPE decfloat34 VALUE '100'.
"Internal tables for illustrating comparisons and display purposes
TYPES: BEGIN OF comparison_struc,
method TYPE string,
avg_runtime TYPE string,
score TYPE string,
description TYPE string,
count TYPE i,
END OF comparison_struc.
DATA comparison1 TYPE TABLE OF comparison_struc WITH EMPTY KEY.
DATA comparison2 TYPE TABLE OF comparison_struc WITH EMPTY KEY.
DATA comparison3 TYPE TABLE OF comparison_struc WITH EMPTY KEY.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
"Populating internal table as the basis for other internal tables
DO table_lines_10k TIMES.
INSERT VALUE #( comp1 = sy-index comp2 = sy-index comp3 = sy-index comp4 = sy-index comp5 = sy-index
comp6 = sy-index comp7 = sy-index comp8 = sy-index comp9 = sy-index comp10 = sy-index
) INTO TABLE itab_content.
ENDDO.
"Comparison 1
comparison1_nuk_primary_std( ).
comparison1_nuk_primary_so( ).
comparison1_nuk_sec_key_std_a( ).
comparison1_nuk_sec_key_std_b( ).
"Comparison 2
comparison2_nuk_sec_insert( ).
comparison2_uk_sec_insert( ).
"Comparison 3
comparison3_nu_so_sec_key_rd_a( ).
comparison3_nu_so_sec_key_rd_b( ).
comparison3_uni_so_sec_key_rd( ).
comparison3_uni_ha_sec_key_rd( ).
"Calculating the score for the comparisons
calculate_score( ).
"Displaying the internal tables illustrating the comparisons
out->write( `----- Comparison 1 -----` ).
out->write( comparison1 ).
out->write( |\n| ).
out->write( |\n| ).
out->write( `----- Comparison 2 -----` ).
out->write( comparison2 ).
out->write( |\n| ).
out->write( |\n| ).
out->write( `----- Comparison 3 -----` ).
out->write( comparison3 ).
ENDMETHOD.
METHOD store_runtime.
cl_abap_utclong=>diff( EXPORTING high = high
low = low
IMPORTING seconds = DATA(seconds) ).
APPEND seconds TO runtime_tab.
ENDMETHOD.
METHOD get_avg_runtime.
DATA(sum) = REDUCE decfloat34( INIT s = VALUE #( )
FOR wa IN runtime_tab
NEXT s += wa ).
TRY.
avg_runtime = sum / lines( runtime_tab ).
CATCH cx_sy_zerodivide.
ENDTRY.
CLEAR runtime_tab.
ENDMETHOD.
METHOD calculate_score.
FIELD-SYMBOLS <tab> TYPE ANY TABLE.
FIELD-SYMBOLS <avg> TYPE string.
DO 3 TIMES.
CASE sy-index.
WHEN 1.
ASSIGN comparison1 TO <tab>.
WHEN 2.
ASSIGN comparison2 TO <tab>.
WHEN 3.
ASSIGN comparison3 TO <tab>.
ENDCASE.
READ TABLE <tab> WITH KEY ('SCORE') = benchmark100 ASSIGNING FIELD-SYMBOL(<entry>).
IF sy-subrc = 0.
ASSIGN <entry>-('AVG_RUNTIME') TO <avg>.
IF sy-subrc = 0.
LOOP AT <tab> ASSIGNING FIELD-SYMBOL(<line>) WHERE ('SCORE IS INITIAL').
TRY.
<line>-('SCORE') = |{ 100 / <avg> * <line>-('AVG_RUNTIME') STYLE = SIMPLE DECIMALS = 5 }|.
CATCH cx_sy_zerodivide.
ENDTRY.
ENDLOOP.
ENDIF.
ENDIF.
ENDDO.
ENDMETHOD.
METHOD comparison1_nuk_primary_std.
FREE it_std_nu_pr_key.
it_std_nu_pr_key = itab_content.
DO loop_count_1k TIMES.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE it_std_nu_pr_key TRANSPORTING NO FIELDS
WITH TABLE KEY primary_key COMPONENTS comp1 = idx_char comp2 = idx_char comp3 = idx_char comp4 = idx_char comp5 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison1_nuk_primary_std`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
score = |{ benchmark100 STYLE = SIMPLE }|
description = `Reading from a standard table using the non-unique primary table key`
count = CONV #( idx_char )
) TO comparison1.
ENDMETHOD.
METHOD comparison1_nuk_primary_so.
FREE it_so_nu_pr_key.
it_so_nu_pr_key = itab_content.
DO loop_count_1k TIMES.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE it_so_nu_pr_key TRANSPORTING NO FIELDS
WITH TABLE KEY primary_key COMPONENTS comp1 = idx_char comp2 = idx_char comp3 = idx_char comp4 = idx_char comp5 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison1_nuk_primary_so`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
description = `Reading from a sorted table using the non-unique sorted primary table key`
count = CONV #( idx_char )
) TO comparison1.
ENDMETHOD.
METHOD comparison1_nuk_sec_key_std_a.
FREE it_std_nu_so_sk.
it_std_nu_so_sk = itab_content.
DO loop_count_1k TIMES.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE it_std_nu_so_sk TRANSPORTING NO FIELDS
WITH TABLE KEY nu_so COMPONENTS comp1 = idx_char comp2 = idx_char comp3 = idx_char comp4 = idx_char comp5 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison1_nuk_sec_key_std_a`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
description = `Reading from a standard table using the secondary, non-unique sorted table key`
count = CONV #( idx_char )
) TO comparison1.
ENDMETHOD.
METHOD comparison1_nuk_sec_key_std_b.
FREE it_std_nu_so_sk.
it_std_nu_so_sk = itab_content.
READ TABLE it_std_nu_so_sk TRANSPORTING NO FIELDS
WITH TABLE KEY nu_so COMPONENTS comp1 = '1' comp2 = '1' comp3 = '1' comp4 = '1' comp5 = '1'.
DO loop_count_1k TIMES.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE it_std_nu_so_sk TRANSPORTING NO FIELDS
WITH TABLE KEY nu_so COMPONENTS comp1 = idx_char comp2 = idx_char comp3 = idx_char comp4 = idx_char comp5 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison1_nuk_sec_key_std_b`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
description = `Same as comparison1_nuk_sec_key_std_a but without first read after population`
count = CONV #( idx_char )
) TO comparison1.
ENDMETHOD.
METHOD comparison2_nuk_sec_insert.
FREE it_std_nu_so_sk.
DATA from TYPE i VALUE 1.
DATA to TYPE i VALUE 5.
DO loop_count_1k TIMES.
idx_char = sy-index.
IF sy-index <> 1.
from += 5.
to += 5.
ENDIF.
ts1 = utclong_current( ).
**********************************************************************
INSERT LINES OF itab_content FROM from TO to INTO TABLE it_std_nu_so_sk.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison2_nuk_sec_insert`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
score = |{ benchmark100 STYLE = SIMPLE }|
description = `Insertion into an internal with a non-unique, sorted secondary table key`
count = CONV #( idx_char )
) TO comparison2.
ENDMETHOD.
METHOD comparison2_uk_sec_insert.
FREE it_std_uni_so_sk.
DATA from TYPE i VALUE 1.
DATA to TYPE i VALUE 5.
DO loop_count_1k TIMES.
idx_char = sy-index.
IF sy-index <> 1.
from += 5.
to += 5.
ENDIF.
ts1 = utclong_current( ).
**********************************************************************
INSERT LINES OF itab_content FROM from TO to INTO TABLE it_std_uni_so_sk.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison2_uk_sec_insert`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
description = `Insertion into an internal with a unique, sorted secondary table key`
count = CONV #( idx_char )
) TO comparison2.
ENDMETHOD.
METHOD comparison3_nu_so_sec_key_rd_a.
FREE it_std_nu_so_sk.
it_std_nu_so_sk = itab_content.
DO loop_count_1k TIMES.
idx_char = sy-index.
READ TABLE it_std_nu_so_sk TRANSPORTING NO FIELDS
WITH TABLE KEY nu_so COMPONENTS comp1 = idx_char comp2 = idx_char comp3 = idx_char comp4 = idx_char comp5 = idx_char.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE it_std_nu_so_sk TRANSPORTING NO FIELDS
WITH TABLE KEY nu_so COMPONENTS comp1 = idx_char comp2 = idx_char comp3 = idx_char comp4 = idx_char comp5 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison3_nu_so_sec_key_rd_a`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
score = |{ benchmark100 STYLE = SIMPLE }|
description = `Reading from a standard table using the secondary, non-unique sorted table key (without first read after population)`
count = CONV #( idx_char )
) TO comparison3.
ENDMETHOD.
METHOD comparison3_nu_so_sec_key_rd_b.
FREE it_std_nu_so_sk.
it_std_nu_so_sk = itab_content.
DO loop_count_1k TIMES.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE it_std_nu_so_sk TRANSPORTING NO FIELDS
WITH TABLE KEY nu_so COMPONENTS comp1 = idx_char comp2 = idx_char comp3 = idx_char comp4 = idx_char comp5 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison3_nu_so_sec_key_rd_b`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
description = `Reading from a standard table using the secondary, non-unique sorted table key (with first read after population)`
count = CONV #( idx_char )
) TO comparison3.
ENDMETHOD.
METHOD comparison3_uni_ha_sec_key_rd.
FREE it_std_hash_sk.
it_std_hash_sk = itab_content.
DO loop_count_1k TIMES.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE it_std_hash_sk TRANSPORTING NO FIELDS
WITH TABLE KEY hak COMPONENTS comp1 = idx_char comp2 = idx_char comp3 = idx_char comp4 = idx_char comp5 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison3_uni_ha_sec_key_rd`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
description = `Reading from a standard table using the secondary, unique hashed table key`
count = CONV #( idx_char )
) TO comparison3.
ENDMETHOD.
METHOD comparison3_uni_so_sec_key_rd.
FREE it_std_uni_so_sk.
it_std_uni_so_sk = itab_content.
DO loop_count_1k TIMES.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE it_std_uni_so_sk TRANSPORTING NO FIELDS
WITH TABLE KEY uni_so COMPONENTS comp1 = idx_char comp2 = idx_char comp3 = idx_char comp4 = idx_char comp5 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
APPEND VALUE #( method = `comparison3_uni_so_sec_key_rd`
avg_runtime = |{ get_avg_runtime( ) STYLE = SIMPLE }|
description = `Reading from a standard table using the secondary, unique sorted table key`
count = CONV #( idx_char )
) TO comparison3.
ENDMETHOD.
ENDCLASS.DO loop, multiple reads are performed on the internal tables. Before and after the reads, the current timestamp is retrieved to evaluate runtime. Find additional details in the similarly structured example in the next section. The results should demonstrate a significant performance improvement for reads on an internal table using the secondary key.zcl_demo_abap. If it already exists, reuse it. Otherwise, create a new class with a different name. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example is set up to display the results in the console.[!NOTE] This example is for exploration, experimentation, and demonstration. It is not intended for accurate runtime or performance testing and is not a suitable method for such purposes. Due to its simplified nature, results may vary and not be entirely accurate, even across multiple runs. It is similarly set up as the example in section Exploring Read Access Performance with Internal Tables. Check more information and, particularly, the disclaimer regarding the runtime measurement there.
🟢 Click to expand for example code
"! <p class="shorttext"><strong>Improving Read Performance with Secondary Table Keys</strong><br/>
"! ABAP cheat sheet example class</p>
"!
"! <p>The example class includes several methods that explore both inefficient and potentially more
"! efficient code examples affecting performance.</p>
"! <p>In ADT, choose F9 in ADT to run the class.</p>
"!
"! <h2>Note</h2>
"! <ul><li>The example is for exploration, experimentation, and demonstration. It is not intended for accurate
"! runtime or performance testing and is not a suitable method for such purposes. Due to its simplified nature,
"! results may vary and not be entirely accurate, even across multiple runs.</li>
"! <li>Note the disclaimer in the ABAP cheat sheet repository.</li></ul>
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS:
primary_table_key RETURNING VALUE(result) TYPE decfloat34,
free_key RETURNING VALUE(result) TYPE decfloat34,
secondary_table_key RETURNING VALUE(result) TYPE decfloat34,
store_runtime IMPORTING high TYPE utclong low TYPE utclong,
get_avg_runtime RETURNING VALUE(avg_runtime) TYPE decfloat34.
"Demo internal table
TYPES: BEGIN OF demo_struc,
comp1 TYPE i,
comp2 TYPE c LENGTH 20,
comp3 TYPE c LENGTH 20,
comp4 TYPE c LENGTH 20,
comp5 TYPE c LENGTH 20,
comp6 TYPE c LENGTH 20,
comp7 TYPE c LENGTH 20,
END OF demo_struc.
DATA: itab_empty_key TYPE TABLE OF demo_struc WITH EMPTY KEY,
itab_w_primary_key TYPE TABLE OF demo_struc WITH NON-UNIQUE KEY comp1 comp2 comp3,
itab_w_secondary_key TYPE TABLE OF demo_struc WITH EMPTY KEY WITH NON-UNIQUE SORTED KEY sk COMPONENTS comp1 comp2 comp3.
DATA: ts1 TYPE utclong,
runtime_tab TYPE TABLE OF decfloat34 WITH EMPTY KEY,
description TYPE string,
idx TYPE i,
idx_char TYPE c LENGTH 20.
CONSTANTS: loop_count TYPE i VALUE 5000.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
"Populating demo tables
DO loop_count TIMES.
INSERT VALUE #( comp1 = sy-index comp2 = sy-index comp3 = sy-index comp4 = sy-index
comp5 = sy-index comp6 = sy-index comp7 = sy-index ) INTO TABLE itab_empty_key.
ENDDO.
itab_w_primary_key = itab_empty_key.
itab_w_secondary_key = itab_empty_key.
DATA(res1) = primary_table_key( ).
out->write( `Average runtime for reading lines of a standard table using the primary table key:` ).
out->write( |{ res1 STYLE = SIMPLE }| ).
DATA(res2) = free_key( ).
out->write( `Average runtime for reading lines of a standard table using a free key:` ).
out->write( |{ res2 STYLE = SIMPLE }| ).
DATA(res3) = secondary_table_key( ).
out->write( `Average runtime for reading lines of a standard table using the secondary table key:` ).
out->write( |{ res3 STYLE = SIMPLE }| ).
ENDMETHOD.
METHOD primary_table_key.
"Reading lines from an internal table using the primary table key
DO loop_count TIMES.
idx = sy-index.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE itab_w_primary_key TRANSPORTING NO FIELDS WITH TABLE KEY comp1 = idx comp2 = idx_char comp3 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
result = get_avg_runtime( ).
ENDMETHOD.
METHOD free_key.
"Reading lines from an internal table using a free key
"The free key corresponds to the primary and secondary table key components
"of the other demo internal tables.
DO loop_count TIMES.
idx = sy-index.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE itab_empty_key TRANSPORTING NO FIELDS WITH KEY comp1 = idx comp2 = idx_char comp3 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
result = get_avg_runtime( ).
ENDMETHOD.
METHOD secondary_table_key.
"Reading lines from an internal table using the secondary table key
DO loop_count TIMES.
idx = sy-index.
idx_char = sy-index.
ts1 = utclong_current( ).
**********************************************************************
READ TABLE itab_w_secondary_key TRANSPORTING NO FIELDS WITH TABLE KEY sk COMPONENTS comp1 = idx comp2 = idx_char comp3 = idx_char.
**********************************************************************
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
result = get_avg_runtime( ).
ENDMETHOD.
METHOD store_runtime.
cl_abap_utclong=>diff( EXPORTING high = high
low = low
IMPORTING seconds = DATA(seconds) ).
APPEND seconds TO runtime_tab.
ENDMETHOD.
METHOD get_avg_runtime.
"Calculating the average runtime value
DATA(sum) = REDUCE decfloat34( INIT s = VALUE #( )
FOR wa IN runtime_tab
NEXT s += wa ).
TRY.
avg_runtime = sum / lines( runtime_tab ).
CATCH cx_sy_zerodivide.
ENDTRY.
CLEAR runtime_tab.
ENDMETHOD.
ENDCLASS.The following code snippet illustrates the additions WITH FURTHER SECONDARY KEYS and WITHOUT FURTHER SECONDARY KEYS to table type definitions. In the first case, the table type may include additional secondary keys. In the second case, it cannot. Find more information here.
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
TYPES ty_tab_w_sk TYPE SORTED TABLE OF zdemo_abap_carr
WITH UNIQUE KEY carrid
WITH FURTHER SECONDARY KEYS.
METHODS meth_w_sk_tab IMPORTING it TYPE ty_tab_w_sk.
TYPES ty_tab_wo_sk TYPE SORTED TABLE OF zdemo_abap_carr
WITH UNIQUE KEY carrid
WITHOUT FURTHER SECONDARY KEYS.
METHODS meth_wo_sk_tab IMPORTING it TYPE ty_tab_wo_sk.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA itab TYPE SORTED TABLE OF zdemo_abap_carr
WITH UNIQUE KEY carrid
WITH NON-UNIQUE SORTED KEY sk COMPONENTS carrname.
"Method whose importing parameter is specified with
"WITH FURTHER SECONDARY KEYS
meth_w_sk_tab( itab ).
"Method whose importing parameter is specified with
"WITHOUT FURTHER SECONDARY KEYS
"The method call will show a syntax error. The internal table
"defined with a secondary table key cannot be passed.
"meth_wo_sk_tab( itab ).
"Assigning the internal table with secondary table key
"to a field symbol and passing it to the method.
FIELD-SYMBOLS <tab> TYPE ANY TABLE.
ASSIGN itab TO <tab>.
meth_w_sk_tab( <tab> ).
TRY.
"No syntax error shown for the generic type of the
"field symbol. However, an exception will be raised
"because of the wrong parameter type.
meth_wo_sk_tab( <tab> ).
CATCH cx_sy_dyn_call_illegal_type INTO DATA(error).
out->write( error->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD meth_w_sk_tab.
...
ENDMETHOD.
METHOD meth_wo_sk_tab.
...
ENDMETHOD.
ENDCLASS.READ TABLE statements (including the TRANSPORTING NO FIELDS addition, as the table content and its storage or further processing are not relevant to this example) to access the tables by: zcl_demo_abap. If it already exists, reuse it. Otherwise, create a new class with a different name. Paste the code into it. If you choose a different class name, update the class name in the code snippet accordingly. After activation, choose F9 in ADT to execute the class. The example is set up to display the results in the console. It may take some time to finish and display the output. See the following note on the expected results.[!IMPORTANT] This example: - Is for exploration, experimentation, and demonstration purposes only. It is not suitable for accurate runtime or performance testing (see the notes in the ABAP Performance Notes cheat sheet). Due to its simplified nature (for example, the table line type is flat and includes fairly simple components of type i) and various factors that can influence the runtime of an ABAP program, results may vary and may not be entirely accurate, even across multiple runs. The displayed results may not always accurately reflect the performance-related notes from the cheat sheet (see also the general notes in the first expandable below). - Is intended to underscore the significance of choosing the right table categories and specifications for your internal tables, tailored to your specific use case and the frequency of table access.
[!NOTE] - The runtime analysis in this example is as follows: The code snippets (i.e.,READ TABLEstatements) to measure runtime are included in method implementations. The methods are called, with each call surrounded by two timestamp retrievals usingutclong_current( ). The runtime is analysed by calculating the delta between these two timestamp values, indicating the runtime for the code snippet. To provide a more meaningful analysis, the methods are called multiple times, which is achieved by enclosing them in aDOloop. The runtime delta is stored in an internal table that collects all delta values for the specific code snippet across all iterations. Finally, the average runtime is calculated based on these collected delta values and added to an internal table along with other relevant information. This approach of using multiple iterations and computing an average runtime aims to offer a balanced view of performance. Note that this implementation is for exploratory and demonstration purposes only. - The following points should be reflected by the result: - Index access (both primary and secondary table index) is generally very fast. - Hash key access (both full primary table keys of hashed tables and hashed secondary table keys) is fast. - Table access using secondary table keys is optimized. - Specifying secondary table keys for standard tables means enabling optimized access and fast key access also for this table category. - For internal tables with non-unique, sorted secondary table keys, the secondary table index does not update immediately. It updates only upon the first access of the table using the secondary table key. The example includes two methods that attempt to illustrate the behavior: Read access by secondary table key with and without counting in the first read access by key. The runtime measurement for the example without the first key access should (on multiple example executions) reflect the extra administrational effort. - Both full and partial, left-aligned key accesses in sorted tables are optimized. The example access using a not left-aligned key should reflect a slower, non-optimized access. - Free key searches are generally slower. -READ TABLEstatements with standard tables can include theBINARY SEARCHaddition (which is no longer recommended; see the section onBINARY SEARCH). This method optimizes read access but generally requires sorting beforehand. The example includes two methods to demonstrate reading usingBINARY SEARCH: one where the runtime measurement includes and does not include a prior, single sorting. - Primary table key access in standard tables is relatively slow. Secondary table key access is optimized. - Dynamic key specifications require effort for evaluation. The example includes methods that illustrate static key access and a comparable dynamic key access.
🟢 Click to expand for general notes
Notes on ...
... primary table key and primary table index:
txt in the example below, while the remaining components are of type i.a, b, and c, optimized access is valid if only a and b are specified. If the keys are not left-aligned, such as in the case where b and c are specified without a, optimized access is not applicable, and a linear search will be used.... secondary table key and secondary table index:
... the use of table categories:
SORT statements, especially after table population.READ TABLE ... BINARY SEARCH statements (Note: It is recommended to use secondary table keys for an optimized access.)🟢 Click to expand for example code
"! <p class="shorttext"><strong>Exploring Read Access Performance with Internal Tables</strong><br/>
"! ABAP cheat sheet example class</p>
"!
"! <p>The example class includes several methods that explore the performance of read accesses to
"! internal tables of various kinds.</p>
"! <p>In ADT, choose F9 in ADT to run the class.</p>
"!
"! <h2>Note</h2>
"! <ul><li>The example is for exploration, experimentation, and demonstration. It is not intended for accurate
"! runtime or performance testing and is not a suitable method for such purposes. Due to its simplified nature,
"! results may vary and not be entirely accurate, even across multiple runs.</li>
"! <li>Note the disclaimer in the ABAP cheat sheet repository.</li></ul>
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
CLASS-METHODS class_constructor.
PROTECTED SECTION.
PRIVATE SECTION.
"Types and data object used for providing and
"storing information such as method name, internal
"table kind, runtime
TYPES: BEGIN OF ENUM enum_access,
index,
primary_table_key,
secondary_table_key,
free_key,
secondary_table_index,
END OF ENUM enum_access.
TYPES: BEGIN OF ENUM enum_category,
standard,
sorted,
hashed,
END OF ENUM enum_category.
TYPES: BEGIN OF s_table_info,
method_name TYPE string,
avg_runtime TYPE string,
table_category TYPE enum_category,
access TYPE enum_access,
description TYPE string,
iteration_count TYPE i,
END OF s_table_info.
CLASS-DATA t_table_info TYPE TABLE OF s_table_info WITH EMPTY KEY.
"Demo internal tables used by the examples
TYPES: BEGIN OF ty_tab,
comp1 TYPE i,
comp2 TYPE i,
comp3 TYPE i,
comp4 TYPE i,
comp5 TYPE i,
comp6 TYPE i,
comp7 TYPE i,
END OF ty_tab.
CLASS-DATA:
base_tab TYPE TABLE OF ty_tab WITH EMPTY KEY,
"Standard tables
standard_tab TYPE TABLE OF ty_tab WITH NON-UNIQUE KEY comp1,
standard_tab_mult_key_comp TYPE TABLE OF ty_tab WITH NON-UNIQUE KEY comp1 comp2 comp3,
standard_tab_non_uni_so_sec_k TYPE TABLE OF ty_tab WITH EMPTY KEY WITH NON-UNIQUE SORTED KEY non_uni_so_key COMPONENTS comp1,
standard_tab_uni_so_sec_k TYPE TABLE OF ty_tab WITH EMPTY KEY WITH UNIQUE SORTED KEY uni_so_key COMPONENTS comp1,
standard_tab_hashed_sec_k TYPE TABLE OF ty_tab WITH EMPTY KEY WITH UNIQUE HASHED KEY hashed_key COMPONENTS comp1,
"Sorted tables
sorted_tab TYPE SORTED TABLE OF ty_tab WITH UNIQUE KEY comp1,
sorted_tab_mult_key_comp TYPE SORTED TABLE OF ty_tab WITH UNIQUE KEY comp1 comp2 comp3,
sorted_tab_w_sec_key TYPE SORTED TABLE OF ty_tab WITH UNIQUE KEY comp1 WITH NON-UNIQUE SORTED KEY sec_key COMPONENTS comp2,
"Hashed tables
hashed_tab TYPE HASHED TABLE OF ty_tab WITH UNIQUE KEY comp1,
hashed_tab_mult_key_comp TYPE HASHED TABLE OF ty_tab WITH UNIQUE KEY comp1 comp2 comp3,
hashed_tab_w_sec_key TYPE HASHED TABLE OF ty_tab WITH UNIQUE KEY comp1 WITH NON-UNIQUE SORTED KEY non_uni_so_key COMPONENTS comp2.
"Constant determining the number of internal table lines and
"read accesses
CONSTANTS loop_count TYPE i VALUE 10000.
"Data objects used for calculating the average runtime
DATA: ts1 TYPE utclong,
runtime_tab TYPE TABLE OF decfloat34 WITH EMPTY KEY,
runtime TYPE decfloat34,
description TYPE string,
idx TYPE i.
"Exemplary methods containing READ TABLE statements
"The method names reflect the illustration purpose.
METHODS:
"Standard tables
std_01_index,
std_02_pr_key_single_comp,
std_03_mult_key_full,
std_04_mult_key_partial_la,
std_05_mult_key_partial_non,
std_06_mult_key_dynamic,
std_07_free_key,
std_08a_free_key_binary_search,
std_08b_free_key_binary_search,
std_09a_sec_key_nonuni_sorted,
std_09b_sec_key_nonuni_sorted,
std_10_sec_key_nonuni_sort_idx,
std_10_sec_key_unique_sorted,
std_11_sec_key_unique_hashed,
"Sorted tables
sorted_01_index,
sorted_02_pr_key_single_comp,
sorted_03_mult_key_full,
sorted_04_mult_key_partial_la,
sorted_05_mult_key_partial_non,
sorted_06_mult_key_dynamic,
sorted_07_free_key,
"Hashed tables
hashed_01_pr_key_single_comp,
hashed_02_mult_key_full,
hashed_03_mult_key_partial_la,
hashed_04_mult_key_partial_non,
hashed_05_mult_key_dynamic,
hashed_06_free_key,
hashed_07_sec_key_nonun_so_idx.
METHODS:
"Preparation methods that populate respective tables anew
prep_std_tabs,
prep_sorted_tabs,
prep_hashed_tabs,
"Collects timestamps to calculate the average runtime
store_runtime IMPORTING high TYPE utclong
low TYPE utclong,
"Calculates the average runtime
get_avg_runtime RETURNING VALUE(avg_runtime) TYPE decfloat34.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD class_constructor.
"Providing demo content for the base table that is used to populate all
"demo tables that are accessed throughout the method calls.
DO loop_count TIMES.
INSERT VALUE #( comp1 = sy-index comp2 = sy-index comp3 = sy-index comp4 = sy-index
comp5 = sy-index comp6 = sy-index comp7 = sy-index ) INTO TABLE base_tab.
ENDDO.
"Initially populating an info table, which as output in the console after calling all
"methods and assigning the average runtime value.
t_table_info = VALUE #(
( method_name = `std_01_index`
table_category = standard
access = index
description = `Read standard table lines by index` )
( method_name = `std_02_pr_key_single_comp`
table_category = standard
access = primary_table_key
description = `Read standard table lines by primary table key (single key component)` )
( method_name = `std_03_mult_key_full`
table_category = standard
access = primary_table_key
description = `Read standard table lines by the full primary table key (multiple key components)` )
( method_name = `std_04_mult_key_partial_la`
table_category = standard
access = primary_table_key
description = `Read standard table lines by the partial, left-aligned primary table key` )
( method_name = `std_05_mult_key_partial_non`
table_category = standard
access = primary_table_key
description = `Read standard table lines by the partial, not left-aligned primary table key` )
( method_name = `std_06_mult_key_dynamic`
table_category = standard
access = primary_table_key
description = `Read standard table lines by primary table key, dynamic key specifications` )
( method_name = `std_07_free_key`
table_category = standard
access = free_key
description = `Read standard table lines by free key` )
( method_name = `std_08a_free_key_binary_search`
table_category = standard
access = free_key
description = `Read standard table lines with free key and binary search (SORT included in measurement)` )
( method_name = `std_08b_free_key_binary_search`
table_category = standard
access = free_key
description = `Read standard table lines with free key and binary search (SORT not included in measurement)` )
( method_name = `std_09a_sec_key_nonuni_sorted`
table_category = standard
access = secondary_table_key
description = `Read standard table lines by secondary table key (non-unique sorted key; measurement includes first read after population)` )
( method_name = `std_09b_sec_key_nonuni_sorted`
table_category = standard
access = secondary_table_key
description = `Read standard table lines by secondary table key (non-unique sorted key; measurement does not include first read after population)` )
( method_name = `std_10_sec_key_nonuni_sort_idx`
table_category = standard
access = secondary_table_index
description = `Read standard table lines by secondary table index (non-unique sorted key)` )
( method_name = `std_10_sec_key_unique_sorted`
table_category = standard
access = secondary_table_key
description = `Read standard table lines by secondary table key (unique sorted key)` )
( method_name = `std_11_sec_key_unique_hashed`
table_category = standard
access = secondary_table_key
description = `Read standard table lines by secondary table key (unique hashed key)` )
( method_name = `sorted_01_index`
table_category = sorted
access = index
description = `Read sorted table lines by index` )
( method_name = `sorted_02_pr_key_single_comp`
table_category = sorted
access = primary_table_key
description = `Read sorted table lines by primary table key (single key component)` )
( method_name = `sorted_03_mult_key_full`
table_category = sorted
access = primary_table_key
description = `Read sorted table lines by the full primary table key (multiple key components)` )
( method_name = `sorted_04_mult_key_partial_la`
table_category = sorted
access = primary_table_key
description = `Read sorted table lines by partial, left-aligned primary table key` )
( method_name = `sorted_05_mult_key_partial_non`
table_category = sorted
access = primary_table_key
description = `Read sorted table lines by partial, not left-aligned primary table key` )
( method_name = `sorted_06_mult_key_dynamic`
table_category = sorted
access = primary_table_key
description = `Read sorted table lines by primary table key, dynamic key specification` )
( method_name = `sorted_07_free_key`
table_category = sorted
access = free_key
description = `Read sorted table lines by free key` )
( method_name = `hashed_01_pr_key_single_comp`
table_category = hashed
access = primary_table_key
description = `Read hashed table lines by primary table key (single key component)` )
( method_name = `hashed_02_mult_key_full`
table_category = hashed
access = primary_table_key
description = `Read hashed table lines by the full primary table key (multiple key components)` )
( method_name = `hashed_03_mult_key_partial_la`
table_category = hashed
access = primary_table_key
description = `Read hashed table lines by partial, left-aligned primary table key` )
( method_name = `hashed_04_mult_key_partial_non`
table_category = hashed
access = primary_table_key
description = `Read hashed table lines by partial, not left-aligned primary table key` )
( method_name = `hashed_05_mult_key_dynamic`
table_category = hashed
access = primary_table_key
description = `Read hashed table lines by primary table key, dynamic key specification` )
( method_name = `hashed_06_free_key`
table_category = hashed
access = free_key
description = `Read hashed table lines by free key` )
( method_name = `hashed_07_sec_key_nonun_so_idx`
table_category = hashed
access = secondary_table_index
description = `Read hashed table lines by secondary table index (non-unique sorted key)` )
).
ENDMETHOD.
METHOD if_oo_adt_classrun~main.
"Dynamically calling all methods that were inserted into the info table,
"and which include READ TABLE statements
LOOP AT t_table_info REFERENCE INTO DATA(t).
t->method_name = to_upper( condense( val = t->method_name to = `` ) ).
"Populating relevant demo tables anew with every method call procedure
CASE t->table_category.
WHEN standard.
prep_std_tabs( ).
WHEN sorted.
prep_sorted_tabs( ).
WHEN hashed.
prep_hashed_tabs( ).
ENDCASE.
"Preparation steps for selected methods to illustrate specific behavior
CASE t->method_name.
WHEN `STD_08B_FREE_KEY_BINARY_SEARCH`.
"Excluding a prior sorting from the runtime measurement for an example that uses BINARY SEARCH
SORT standard_tab BY comp7 ASCENDING.
WHEN `STD_09B_SEC_KEY_NONUNI_SORTED`.
"For internal tables with non-unique, sorted secondary table keys, the secondary table index does not update immediately.
"It updates only upon the first access of the table using the secondary table key. For this example run, the first read
"is not considered for the runtime measurement.
READ TABLE standard_tab_non_uni_so_sec_k TRANSPORTING NO FIELDS WITH TABLE KEY non_uni_so_key COMPONENTS comp1 = 1.
ENDCASE.
TRY.
DO loop_count TIMES.
idx = sy-index.
ts1 = utclong_current( ).
"Dynamically calling methods using the method names stored
"in the info internal table
CALL METHOD (t->method_name).
store_runtime( high = utclong_current( ) low = ts1 ).
ENDDO.
runtime = get_avg_runtime( ).
t->avg_runtime = |{ runtime STYLE = SIMPLE }|.
t->iteration_count = idx.
CATCH cx_root INTO DATA(error).
RAISE SHORTDUMP error.
ENDTRY.
ENDLOOP.
SORT t_table_info BY avg_runtime ASCENDING.
out->write( t_table_info ).
ENDMETHOD.
METHOD prep_hashed_tabs.
FREE: hashed_tab, hashed_tab_mult_key_comp, hashed_tab_w_sec_key.
INSERT LINES OF base_tab INTO TABLE hashed_tab.
INSERT LINES OF base_tab INTO TABLE hashed_tab_mult_key_comp.
INSERT LINES OF base_tab INTO TABLE hashed_tab_w_sec_key.
ENDMETHOD.
METHOD prep_sorted_tabs.
FREE: sorted_tab, sorted_tab_mult_key_comp, sorted_tab_w_sec_key.
INSERT LINES OF base_tab INTO TABLE sorted_tab.
INSERT LINES OF base_tab INTO TABLE sorted_tab_mult_key_comp.
INSERT LINES OF base_tab INTO TABLE sorted_tab_w_sec_key.
ENDMETHOD.
METHOD prep_std_tabs.
FREE: standard_tab, standard_tab_mult_key_comp, standard_tab_non_uni_so_sec_k, standard_tab_uni_so_sec_k, standard_tab_hashed_sec_k.
INSERT LINES OF base_tab INTO TABLE standard_tab.
INSERT LINES OF base_tab INTO TABLE standard_tab_mult_key_comp.
INSERT LINES OF base_tab INTO TABLE standard_tab_non_uni_so_sec_k.
INSERT LINES OF base_tab INTO TABLE standard_tab_uni_so_sec_k.
INSERT LINES OF base_tab INTO TABLE standard_tab_hashed_sec_k.
ENDMETHOD.
METHOD get_avg_runtime.
"Calculating the average runtime value
DATA(sum) = REDUCE decfloat34( INIT s = VALUE #( )
FOR wa IN runtime_tab
NEXT s += wa ).
TRY.
avg_runtime = sum / lines( runtime_tab ).
CATCH cx_sy_zerodivide INTO DATA(error).
RAISE SHORTDUMP error.
ENDTRY.
CLEAR runtime_tab.
ENDMETHOD.
METHOD store_runtime.
cl_abap_utclong=>diff( EXPORTING high = high
low = low
IMPORTING seconds = DATA(seconds) ).
APPEND seconds TO runtime_tab.
ENDMETHOD.
METHOD sorted_01_index.
READ TABLE sorted_tab TRANSPORTING NO FIELDS INDEX idx.
ENDMETHOD.
METHOD sorted_02_pr_key_single_comp.
READ TABLE sorted_tab TRANSPORTING NO FIELDS WITH TABLE KEY comp1 = idx.
ENDMETHOD.
METHOD sorted_03_mult_key_full.
READ TABLE sorted_tab_mult_key_comp TRANSPORTING NO FIELDS WITH TABLE KEY comp1 = idx comp2 = idx comp3 = idx.
ENDMETHOD.
METHOD sorted_04_mult_key_partial_la.
READ TABLE sorted_tab_mult_key_comp TRANSPORTING NO FIELDS WITH KEY comp1 = idx comp2 = idx.
ENDMETHOD.
METHOD sorted_05_mult_key_partial_non.
READ TABLE sorted_tab_mult_key_comp TRANSPORTING NO FIELDS WITH KEY comp2 = idx comp3 = idx.
ENDMETHOD.
METHOD sorted_06_mult_key_dynamic.
READ TABLE sorted_tab_mult_key_comp TRANSPORTING NO FIELDS WITH TABLE KEY ('COMP1') = idx ('COMP2') = idx ('COMP3') = idx.
ENDMETHOD.
METHOD sorted_07_free_key.
READ TABLE sorted_tab TRANSPORTING NO FIELDS WITH KEY comp7 = idx.
ENDMETHOD.
METHOD std_01_index.
READ TABLE standard_tab TRANSPORTING NO FIELDS INDEX idx.
ENDMETHOD.
METHOD std_02_pr_key_single_comp.
READ TABLE standard_tab TRANSPORTING NO FIELDS WITH TABLE KEY comp1 = idx.
ENDMETHOD.
METHOD std_03_mult_key_full.
READ TABLE standard_tab_mult_key_comp TRANSPORTING NO FIELDS WITH TABLE KEY comp1 = idx comp2 = idx comp3 = idx.
ENDMETHOD.
METHOD std_04_mult_key_partial_la.
READ TABLE standard_tab_mult_key_comp TRANSPORTING NO FIELDS WITH KEY comp1 = idx comp2 = idx.
ENDMETHOD.
METHOD std_05_mult_key_partial_non.
READ TABLE standard_tab_mult_key_comp TRANSPORTING NO FIELDS WITH KEY comp2 = idx comp3 = idx.
ENDMETHOD.
METHOD std_06_mult_key_dynamic.
READ TABLE standard_tab_mult_key_comp TRANSPORTING NO FIELDS WITH TABLE KEY ('COMP1') = idx ('COMP2') = idx ('COMP3') = idx.
ENDMETHOD.
METHOD std_07_free_key.
READ TABLE standard_tab TRANSPORTING NO FIELDS WITH KEY comp7 = idx.
ENDMETHOD.
METHOD std_08a_free_key_binary_search.
IF idx = 1.
SORT standard_tab BY comp7 ASCENDING.
ENDIF.
READ TABLE standard_tab TRANSPORTING NO FIELDS WITH KEY comp7 = idx BINARY SEARCH.
ENDMETHOD.
METHOD std_08b_free_key_binary_search.
READ TABLE standard_tab TRANSPORTING NO FIELDS WITH KEY comp7 = idx BINARY SEARCH.
ENDMETHOD.
METHOD std_09a_sec_key_nonuni_sorted.
READ TABLE standard_tab_non_uni_so_sec_k TRANSPORTING NO FIELDS WITH TABLE KEY non_uni_so_key COMPONENTS comp1 = idx.
ENDMETHOD.
METHOD std_09b_sec_key_nonuni_sorted.
READ TABLE standard_tab_non_uni_so_sec_k TRANSPORTING NO FIELDS WITH TABLE KEY non_uni_so_key COMPONENTS comp1 = idx.
ENDMETHOD.
METHOD std_10_sec_key_nonuni_sort_idx.
READ TABLE standard_tab_non_uni_so_sec_k TRANSPORTING NO FIELDS USING KEY non_uni_so_key INDEX idx.
ENDMETHOD.
METHOD std_10_sec_key_unique_sorted.
READ TABLE standard_tab_uni_so_sec_k TRANSPORTING NO FIELDS WITH TABLE KEY uni_so_key COMPONENTS comp1 = idx.
ENDMETHOD.
METHOD std_11_sec_key_unique_hashed.
READ TABLE standard_tab_hashed_sec_k TRANSPORTING NO FIELDS WITH TABLE KEY hashed_key COMPONENTS comp1 = idx.
ENDMETHOD.
METHOD hashed_01_pr_key_single_comp.
READ TABLE hashed_tab TRANSPORTING NO FIELDS WITH TABLE KEY comp1 = idx.
ENDMETHOD.
METHOD hashed_02_mult_key_full.
READ TABLE hashed_tab_mult_key_comp TRANSPORTING NO FIELDS WITH TABLE KEY comp1 = idx comp2 = idx comp3 = idx.
ENDMETHOD.
METHOD hashed_03_mult_key_partial_la.
READ TABLE hashed_tab_mult_key_comp TRANSPORTING NO FIELDS WITH KEY comp1 = idx comp2 = idx.
ENDMETHOD.
METHOD hashed_04_mult_key_partial_non.
READ TABLE hashed_tab_mult_key_comp TRANSPORTING NO FIELDS WITH KEY comp2 = idx comp3 = idx.
ENDMETHOD.
METHOD hashed_05_mult_key_dynamic.
READ TABLE hashed_tab_mult_key_comp TRANSPORTING NO FIELDS WITH TABLE KEY ('COMP1') = idx ('COMP2') = idx ('COMP3') = idx.
ENDMETHOD.
METHOD hashed_06_free_key.
READ TABLE hashed_tab TRANSPORTING NO FIELDS WITH KEY comp7 = idx.
ENDMETHOD.
METHOD hashed_07_sec_key_nonun_so_idx.
READ TABLE hashed_tab_w_sec_key TRANSPORTING NO FIELDS USING KEY non_uni_so_key INDEX idx.
ENDMETHOD.
ENDCLASS.INDEX TABLE. Invalid method calls and table passing are commented out.CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
CLASS-METHODS process_any_table IMPORTING itab TYPE ANY TABLE.
CLASS-METHODS process_standard_tables1 IMPORTING itab TYPE STANDARD TABLE.
CLASS-METHODS process_standard_tables2 IMPORTING itab TYPE TABLE.
CLASS-METHODS process_sorted_tables IMPORTING itab TYPE SORTED TABLE.
CLASS-METHODS process_hashed_tables IMPORTING itab TYPE HASHED TABLE.
CLASS-METHODS process_index_tables IMPORTING itab TYPE INDEX TABLE.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA standard_tab TYPE TABLE OF string WITH EMPTY KEY.
DATA sorted_tab TYPE SORTED TABLE OF string WITH NON-UNIQUE KEY table_line.
DATA hashed_tab TYPE HASHED TABLE OF string WITH UNIQUE KEY table_line.
"ANY TABLE
process_any_table( standard_tab ).
process_any_table( sorted_tab ).
process_any_table( hashed_tab ).
"(STANDARD) TABLE
process_standard_tables1( standard_tab ).
"process_standard_tables1( sorted_tab ).
"process_standard_tables1( hashed_tab ).
process_standard_tables2( standard_tab ).
"process_standard_tables2( sorted_tab ).
"process_standard_tables2( hashed_tab ).
"SORTED TABLE
"process_sorted_tables( standard_tab ).
process_sorted_tables( sorted_tab ).
"process_sorted_tables( hashed_tab ).
"HASHED TABLE
"process_hashed_tables( standard_tab ).
"process_hashed_tables( sorted_tab ).
process_hashed_tables( hashed_tab ).
"INDEX TABLE
process_index_tables( standard_tab ).
process_index_tables( sorted_tab ).
"process_index_tables( hashed_tab ).
"Note: Field symbols can also be typed with generic types.
FIELD-SYMBOLS <fs_std_table> TYPE table.
ASSIGN standard_tab TO <fs_std_table>.
"ASSIGN sorted_tab TO <fs_std_table>.
"ASSIGN hashed_tab TO <fs_std_table>.
FIELD-SYMBOLS <fs_index_table> TYPE INDEX TABLE.
ASSIGN standard_tab TO <fs_index_table>.
ASSIGN sorted_tab TO <fs_index_table>.
"ASSIGN hashed_tab TO <fs_index_table>.
ENDMETHOD.
METHOD process_any_table.
ENDMETHOD.
METHOD process_hashed_tables.
ENDMETHOD.
METHOD process_index_tables.
ENDMETHOD.
METHOD process_sorted_tables.
ENDMETHOD.
METHOD process_standard_tables1.
ENDMETHOD.
METHOD process_standard_tables2.
ENDMETHOD.
ENDCLASS.You can use FIND ... IN TABLE statements to search for substrings in internal tables (standard tables without secondary table keys; with character-like line type) line by line.
DATA(str_table) = VALUE string_table( ( `aZbzZ` ) ( `cdZze` ) ( `Zzzf` ) ( `ghz` ) ).
"Finding all occurrences in a table
"Note: res_tab is of type match_result_tab
"You can also restrict the search range in an internal table; see an example in REPLACE ... IN TABLE
FIND ALL OCCURRENCES OF `Z`
IN TABLE str_table
RESULTS DATA(res_tab)
RESPECTING CASE.
"4 entries in table res_tab (tables in SUBMATCHES are initial since no regular expression is used)
"1. line: 1, offset: 1, length: 1, submatches: (initial)
"2. line: 1, offset: 4, length: 1, ...
"3. line: 2, offset: 2, length: 1, ...
"4. line: 3, offset: 0, length: 1, ...
"Finding the first occurrence in a table
"Note: res_struc, which is declared inline here, is of type match_result
FIND FIRST OCCURRENCE OF `Z`
IN TABLE str_table
RESULTS DATA(res_struc)
RESPECTING CASE.
"Entries in structure res_struc
"line: 1, offset: 1, length: 1, submatches: (initial)
"Alternative to the statement above (storing the information in individual data objects)
FIND FIRST OCCURRENCE OF `Z`
IN TABLE str_table
MATCH LINE DATA(line) "1
MATCH OFFSET DATA(off) "1
MATCH LENGTH DATA(len) "1
RESPECTING CASE.Replacements in internal tables with REPLACE ... IN TABLE:
DATA(str_table_original) = VALUE string_table( ( `aZbzZ` ) ( `cdZze` ) ( `Zzzf` ) ( `ghz` ) ).
DATA(str_table) = str_table_original.
"Replacing all occurrences in a table
"RESULTS addition: Storing information in an internal table of type repl_result_tab
REPLACE ALL OCCURRENCES OF `Z`
IN TABLE str_table
WITH `#`
RESULTS DATA(res_table)
RESPECTING CASE.
"str_table: a#bz# / cd#ze / #zzf / ghz
"res_table:
"LINE OFFSET LENGTH
"1 1 1
"1 4 1
"2 2 1
"3 0 1
str_table = str_table_original.
"Replacing the first occurrence in a table
"RESULTS addition: Storing information in a structure of type repl_result
REPLACE FIRST OCCURRENCE OF `Z`
IN TABLE str_table
WITH `#`
RESULTS DATA(res_structure)
RESPECTING CASE.
"str_table: a#bzZ / cdZze / Zzzf / ghz
"res_structure:
"LINE OFFSET LENGTH
"1 1 1
str_table = str_table_original.
"Restricting the search range in an internal table
REPLACE ALL OCCURRENCES OF `Z`
IN TABLE str_table
FROM 1 TO 2
WITH `#`
RESPECTING CASE.
"str_table: a#bz# / cd#ze / Zzzf / ghz
str_table = str_table_original.
"Offsets can be optionally specified (also only the offset of start or end line possible)
REPLACE ALL OCCURRENCES OF `Z`
IN TABLE str_table
FROM 1 OFFSET 3 TO 2 OFFSET 2
WITH `#`
RESPECTING CASE.
"str_table: aZbz# / cdZze / Zzzf / ghzSIGN, OPTION, LOW, and HIGH SIGN: type c with length 1; indicates whether each line is included (I) or excluded (E) from the result set.OPTION: type c with length 2; specifies the selection option for the condition. It uses comparison operators such as EQ, NE, GE, GT, LE, LT, CP, NP, BT, NB. Special rules apply for certain operators such as when using CP or NP, the LOW and HIGH columns must be character values.LOW: The lower comparison value.HIGH: The higher comparison value.TYPE RANGE OF addition in DATA and TYPES statements IN operator (each row in the table represents a separate comparison)"Populating an integer table with values from 1 to 20
TYPES int_tab_type TYPE TABLE OF i WITH EMPTY KEY.
DATA(inttab) = VALUE int_tab_type( FOR x = 1 WHILE x <= 20 ( x ) ).
"Declaring a ranges table
DATA rangestab TYPE RANGE OF i.
"Populating a ranges table using VALUE
rangestab = VALUE #( sign = 'I'
option = 'BT' ( low = 1 high = 3 )
( low = 6 high = 8 )
( low = 12 high = 15 )
option = 'GE' ( low = 18 ) ).
"Using a SELECT statement and the IN addition to retrieve internal table
"content based on the ranges table specifications
SELECT * FROM @inttab AS tab
WHERE table_line IN @rangestab
INTO TABLE @DATA(result).
"result: 1, 2, 3, 6, 7, 8, 12, 13, 14, 15, 18, 19, 20You can compare internal tables:
CL_ABAP_DIFF class to compare the content of two compatible index tables programmatically. In the context of ABAP RAP, the operands of ABAP EML statements and parameters of RAP handler methods and RAP saver methods are mainly special messenger tables for passing data and receiving results or messages: BDEF derived types - special ABAP types (internal tables and structures) that are tailor-made for RAP purposes.
"Example declarations
"For an EML create operation
DATA create_tab TYPE TABLE FOR CREATE entity.
"For an update operation
DATA update_tab TYPE TABLE FOR UPDATE entity.
"Type declaration using a BDEF derived type
TYPES der_typ TYPE TABLE FOR DELETE entity.Find more information in the ABAP for RAP: Entity Manipulation Language (ABAP EML) ABAP cheat sheet.
Find more information in the Dynamic Programming cheat sheet.
DATA(some_type) = 'STRING'.
DATA dataref TYPE REF TO data.
"Creating an internal table using a CREATE DATA statement
"by specifying the type name dynamically.
"In the example, a standard table with elementary line type
"and standard key is created.
CREATE DATA dataref TYPE TABLE OF (some_type).
TYPES: BEGIN OF demo_struc,
comp1 TYPE c LENGTH 10,
comp2 TYPE i,
comp3 TYPE i,
END OF demo_struc.
"Internal table with structured line type and empty key.
CREATE DATA dataref TYPE TABLE OF ('DEMO_STRUC') WITH EMPTY KEY.
"Using a globally available table type
CREATE DATA dataref TYPE ('STRING_TABLE').The system field (i.e. a built-in data object that is filled depending on the context by the ABAP runtime framework) sy-tabix is used in various contexts and examples of the cheat sheet. It is of type i. The value of sy-tabix represents the line number in the table index of an internal table. sy-tabix contains the last line addressed using a primary or secondary table index. Note that it is set to 0 when accessed using a hash algorithm.
The following code snippet illustrates sy-tabix with READ TABLE statements.
DATA(it_std) = VALUE string_table( ( `A` ) ( `B` ) ( `C` ) ).
DATA it_so TYPE TABLE OF string WITH NON-UNIQUE KEY table_line.
it_so = VALUE #( ( `A` ) ( `B` ) ( `C` ) ).
DATA(it_hashed) = VALUE string_hashed_table( ( `A` ) ( `B` ) ( `C` ) ).
DATA it_std_sec_hashed_key TYPE TABLE OF string WITH EMPTY KEY WITH UNIQUE HASHED KEY sk COMPONENTS table_line.
it_std_sec_hashed_key = VALUE #( ( `A` ) ( `B` ) ( `C` ) ).
READ TABLE it_std INTO DATA(tline_std) WITH KEY table_line = `A`.
ASSERT sy-tabix = 1.
READ TABLE it_so INTO DATA(tline_so) WITH KEY table_line = `B`.
ASSERT sy-tabix = 2.
READ TABLE it_hashed INTO DATA(tline_hashed) WITH KEY table_line = `C`.
ASSERT sy-tabix = 0.
READ TABLE it_std_sec_hashed_key INTO DATA(tline_std_w_sk) WITH TABLE KEY sk COMPONENTS table_line = `C`.
ASSERT sy-tabix = 0.The example in the expandable section highlights a potential pitfall when using the sy-tabix value. An internal table is looped over, checking for the presence of a specific value. If the value is found, the current table line is deleted. This process occurs twice: first, by using the sy-tabix value directly and second, by storing the sy-tabix value in a variable for later use. The issue arises when other statements intervene before you actually use the value. In this case, the sy-tabix might change due to those statements, leading to unintended results in the first loop.
🟢 Click to expand for example code
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(str_tab) = VALUE string_table( ( `a` ) ( `#` ) ( `c` ) ( `d` ) ( `e` )
( `f` ) ( `#` ) ( `h` ) ( `i` ) ( `#` ) ).
DATA(str_tab_copy) = str_tab.
DATA subrc TYPE sy-subrc.
DATA(num) = 0.
LOOP AT str_tab REFERENCE INTO DATA(dref).
num += 1.
out->write( |Loop pass: { num }| ).
out->write( |sy-tabix: { sy-tabix }| ).
IF dref->* CS `#`.
DELETE str_tab INDEX sy-tabix.
subrc = sy-subrc.
out->write( |sy-subrc: { subrc }| ).
ENDIF.
out->write( `-------` ).
ENDLOOP.
out->write( str_tab ).
out->write( |{ repeat( val = `-` occ = 50 ) }| ).
str_tab = str_tab_copy.
CLEAR num.
DATA tabix TYPE sy-tabix.
LOOP AT str_tab REFERENCE INTO dref.
tabix = sy-tabix.
num += 1.
out->write( |Loop pass: { num }| ).
out->write( |sy-tabix: { tabix }| ).
IF dref->* CS `#`.
DELETE str_tab INDEX tabix.
subrc = sy-subrc.
out->write( |sy-subrc: { subrc }| ).
ENDIF.
out->write( `-------` ).
ENDLOOP.
out->write( str_tab ).
ENDMETHOD.
ENDCLASS.STEP addition determines the step size and loop order for processing internal tables.LOOP ATFOR loops with constructor expressions using VALUE and NEWAPPENDINSERTDELETESTEP addition expects a numeric expression, which must be an integer. It determines the step size. For example, a value of 2 means every second line. The step size must not be 0.STEP is not specified, the implicit step size is 1, and the table category or the USING KEY addition determine the loop order.FROM and TO: Define index range conditions to process only a subset of table lines.WHERE: When WHERE is used after STEP, the numeric value can only be 1 or -1.USING KEY addition): Processes the secondary table index backwards starting with the highest index.USING KEY addition): The behavior is the same as for hashed tables.sy-tabix value reflects the current line number.The following code snippets illustrate the STEP addition in various contexts. Note that most of the snippets use the string table itab and the string text from the first example for demonstration purposes. Comments outline the effect of using the STEP addition with the content of the text variable.
| Context | Code Snippet |
|---|---|
|
|
|
|
Combining |
Note that these combination options are also possible for constructor expressions. |
|
Backward loops with hashed table |
|
|
Backward loops with tables defining secondary table keys |
|
|
Using |
|
|
|
|
|
|
|
|
|
|
DATA itab TYPE TABLE OF zdemo_abap_flsch., if an explicit table key is not specified, the standard table key is used by default. SORT and READ TABLE statements are applied to an internal table with an empty primary table key. To avoid syntax warnings - since it is statically determined that the table has an empty primary table key - dynamic programming is employed. The effects are as follows:FROM clause specifies a component value not present in that line.To try the example out, create a demo class named zcl_demo_abap and paste the code into it. After activation, choose F9 in ADT to execute the class. The example is set up to display output in the console.
CLASS zcl_demo_abap DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
METHODS has_empty_primary_key IMPORTING tab TYPE ANY TABLE
RETURNING VALUE(has_empty_key) TYPE abap_boolean.
METHODS get_key_components IMPORTING tab TYPE ANY TABLE
RETURNING VALUE(keys) TYPE abap_keydescr_tab.
ENDCLASS.
CLASS zcl_demo_abap IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
out->write( `------ Example 1: Finding out about empty table key ------` ).
out->write( |\n| ).
"Structured type that includes several components of numeric types only.
TYPES:
BEGIN OF demo_struc1,
comp1 TYPE i,
comp2 TYPE i,
comp3 TYPE decfloat34,
comp4 TYPE decfloat16,
comp5 TYPE p LENGTH 8 DECIMALS 2,
END OF demo_struc1.
"Structured type that includes several components of numeric types and one component with character-like type.
TYPES:
BEGIN OF demo_struc2,
comp1 TYPE i,
comp2 TYPE i,
comp3 TYPE decfloat34,
comp4 TYPE decfloat16,
comp5 TYPE p LENGTH 8 DECIMALS 2,
comp6 TYPE c LENGTH 3,
END OF demo_struc2.
"Internal tables that do not include an explicit table key specification.
"The primary table key is implicitly the standard table key.
DATA itab1 TYPE TABLE OF demo_struc1.
DATA itab2 TYPE TABLE OF demo_struc2.
"Note: The standard table key can also be specified explicitly.
"The table is not used in the example.
DATA itab_expl_std_key TYPE TABLE OF demo_struc1 WITH DEFAULT KEY.
"Explicitly specifying an empty table key.
DATA itab3 TYPE TABLE OF demo_struc1 WITH EMPTY KEY.
"Using RTTI to determine if the tables' primary table key is empty and
"retrieving the keys.
DATA has_empty_key TYPE abap_boolean.
DATA key_table TYPE abap_keydescr_tab.
DO 3 TIMES.
CASE sy-index.
WHEN 1.
has_empty_key = has_empty_primary_key( itab1 ).
key_table = get_key_components( itab1 ).
WHEN 2.
has_empty_key = has_empty_primary_key( itab2 ).
key_table = get_key_components( itab2 ).
WHEN 3.
has_empty_key = has_empty_primary_key( itab3 ).
key_table = get_key_components( itab3 ).
ENDCASE.
out->write( |itab{ sy-index }:| ).
out->write( |has_empty_key: "{ has_empty_key }"| ).
IF key_table IS INITIAL.
out->write( `key_table is initial` ).
ELSE.
out->write( data = key_table name = `key_table` ).
ENDIF.
out->write( |\n| ).
ENDDO.
**********************************************************************
out->write( |\n| ).
out->write( `------ Example 2: Sorting and reading from tables based on the primary table key although the tables have an empty key ------` ).
out->write( |\n| ).
itab1 = VALUE #(
( comp1 = 1 comp2 = 10 comp3 = '1.2' comp4 = '2.34' comp5 = '1234.56' )
( comp1 = 2 comp2 = 20 comp3 = '2.3' comp4 = '3.45' comp5 = '2345.67' )
( comp1 = 5 comp2 = 50 comp3 = '5.6' comp4 = '6.78' comp5 = '5678.90' )
( comp1 = 6 comp2 = 60 comp3 = '6.7' comp4 = '7.89' comp5 = '6789.01' )
( comp1 = 7 comp2 = 70 comp3 = '7.8' comp4 = '8.90' comp5 = '7890.12' )
( comp1 = 3 comp2 = 30 comp3 = '3.4' comp4 = '4.56' comp5 = '3456.78' )
( comp1 = 4 comp2 = 40 comp3 = '4.5' comp4 = '5.67' comp5 = '4567.89' )
).
DATA(itab1_copy) = itab1.
"The following statement raises a syntax warning.
"SORT itab1 DESCENDING.
"Using dynamic programming to circumvent the syntax warning.
FIELD-SYMBOLS <tab> TYPE ANY TABLE.
ASSIGN itab1 TO <tab>.
SORT <tab> DESCENDING.
IF <tab> = itab1_copy.
out->write( `The table content is identical` ).
ELSE.
out->write( `The table content is not identical` ).
ENDIF.
out->write( |\n| ).
out->write( |\n| ).
DATA(k) = VALUE demo_struc1( comp1 = 4 ).
"The following statement raises a syntax warning.
"READ TABLE itab1 FROM k USING KEY primary_key INTO DATA(wa).
"Using dynamic programming to circumvent the syntax warning.
READ TABLE itab1 FROM k USING KEY ('primary_key') INTO DATA(wa).
out->write( data = wa name = `wa` ).
out->write( |\n| ).
itab2 = VALUE #(
( comp1 = 1 comp2 = 10 comp3 = '1.2' comp4 = '2.34' comp5 = '1234.56' comp6 = 'a' )
( comp1 = 2 comp2 = 20 comp3 = '2.3' comp4 = '3.45' comp5 = '2345.67' comp6 = 'e' )
( comp1 = 5 comp2 = 50 comp3 = '5.6' comp4 = '6.78' comp5 = '5678.90' comp6 = 'f' )
( comp1 = 6 comp2 = 60 comp3 = '6.7' comp4 = '7.89' comp5 = '6789.01' comp6 = 'b' )
( comp1 = 7 comp2 = 70 comp3 = '7.8' comp4 = '8.90' comp5 = '7890.12' comp6 = 'd' )
( comp1 = 3 comp2 = 30 comp3 = '3.4' comp4 = '4.56' comp5 = '3456.78' comp6 = 'g' )
( comp1 = 4 comp2 = 40 comp3 = '4.5' comp4 = '5.67' comp5 = '4567.89' comp6 = 'c' )
).
SORT itab2 DESCENDING.
out->write( data = itab2 name = `Sorting result:` ).
out->write( |\n| ).
DATA(k2) = VALUE demo_struc2( comp6 = 'd' ).
READ TABLE itab2 FROM k2 USING KEY primary_key INTO DATA(wa2).
out->write( data = wa2 name = `wa2` ).
ENDMETHOD.
METHOD has_empty_primary_key.
DATA(table_descr) = CAST cl_abap_tabledescr( cl_abap_typedescr=>describe_by_data( tab ) ).
DATA(keys) = table_descr->get_keys( ).
DATA(primary_key) = keys[ 1 ].
IF primary_key-key_kind = cl_abap_tabledescr=>keydefkind_empty OR primary_key-components IS INITIAL.
RETURN abap_true.
ENDIF.
ENDMETHOD.
METHOD get_key_components.
keys = CAST cl_abap_tabledescr( cl_abap_typedescr=>describe_by_data( tab ) )->key.
ENDMETHOD.
ENDCLASS.Topic Internal Tables in the ABAP Keyword Documentation.
[!NOTE] - The executable example covers the following topics, among others: Creating, populating, reading from, sorting, modifying internal tables - The steps to import and run the code are outlined here. - Disclaimer
01_Internal_Tables.md。版权归原作者所有。