When writing programs with ABAP, there are many ways to significantly improve performance. Generally, the performance in the SAP environment usually drops when reading/processing masses of data. Exactly at these points there are some approaches to accelerate these operations.
Reading data from the database
When (re-)reading data from the database, a distinction is usually made between single/record processing and mass/bulk processing. With record processing, there is always the problem that a connection must be established from the application server to the database server for each record read - even if this only takes a few milliseconds, it accumulates when thousands of records are read.
For the following examples, we always measure with the following number of records:
- MARA: 83813
- MARC: 190384
Sequential reading of all data
The classical way to read data from the database was record processing (loop). However, this is very poor in terms of performance due to the above reasons:
1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT matnr, matkl
FROM mara
INTO CORRESPONTING FIELDS OF @ls_mara
WHERE matkl IN @s_matkl.
SELECT *
FROM marc
INTO CORRESPONDING FIELDS OF @ls_marc
WHERE matnr EQ @ls_mara-matnr.
ENDSELECT.
ENDSELECT.
Runtime: 41 seconds
Sequential reading of missing data
1
2
3
4
5
6
7
8
9
10
11
12
SELECT matnr, matkl
FROM mara
INTO CORRESPONDING FIELDS OF TABLE @lt_mara
WHERE matkl IN @s_matkl.
LOOP AT lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>).
SELECT *
FROM marc
APPENDING CORRESPONDING FIELDS OF TABLE @lt_marc
WHERE matnr EQ @<s_mara>-matnr.
ENDLOOP.
Runtime: 38 seconds
Reading via “FOR ALL ENTRIES IN”
If you have an internal table with data, where you can select for each data set to another table, then you can use the addition FOR ALL ENTRIES IN.
It must be ensured that there are data sets in the existing internal table, otherwise the selection is made without restriction!
1
2
3
4
5
6
7
8
9
10
11
SELECT matnr, matkl
FROM mara
INTO CORRESPONDING FIELDS OF TABLE @lt_mara
WHERE matkl IN @s_matkl.
SELECT *
FROM marc
INTO CORRESPONDING FIELDS OF TABLE @lt_marc
FOR ALL ENTRIES IN @lt_mara
WHERE matnr EQ @lt_mara-matnr.
Runtime: 0.7 seconds
Processing internal tables
If one has read once all necessary data from the data base into internal tables, these must be read during the processing from the internal table. If there are many entries in the internal table, an inperformant access (despite the fact that everything is already in memory) can result in a long runtime.
For the following examples we always measure with the following number of records. The internal tables correspond to the examples from above.
- MARA: 83813
- MARC: 190384
Sequential access
If you have a “standard table” (e.g. if no separate type was specified when declaring the internal table), then access to data in the table is sequential - this means that one record is processed after the other.
1
2
3
4
5
6
7
8
9
10
DATA: lt_mara TYPE TABLE OF t_mara,
lt_marc TYPE TABLE OF t_marc.
... (data selection)
LOOP AT lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>).
LOOP AT lt_marc ASSIGNING FIELD-SYMBOL(<s_marc>) WHERE matnr EQ <s_mara>-matnr.
ENDLOOP.
ENDLOOP.
Runtime: 931 seconds
Binary search
A special case is the binary search, which can be used as an addition for READ TABLE.
The only requirement is that the data to be selected is unique and sorted.
1
2
3
4
5
6
7
8
9
10
11
DATA: lt_mara TYPE TABLE OF t_mara,
lt_marc TYPE TABLE OF t_marc.
... (data selection)
SORT lt_mara BY matnr ASCENDING.
LOOP AT lt_marc ASSIGNING FIELD-SYMBOL(<s_marc>).
READ TABLE lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>) WITH KEY matnr = <s_marc>-matnr BINARY SEARCH.
ENDLOOP.
Runtime: 0.6 seconds
Secondary key
Since ABAP 7.0 EhP2 there are Secondary keys for internal tables. These basically function like an index in the database to additional fields. With these secondary keys you can define individual fields of the internal table for an optiomized access without having to change the type of the table itself:
1
2
3
4
5
6
7
8
9
10
11
12
DATA: lt_mara TYPE TABLE OF t_mara,
lt_marc TYPE TABLE OF t_marc
WITH NON-UNIQUE SORTED KEY k1 COMPONENTS matnr. " nicht eindeutig
... (data selection)
LOOP AT lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>).
LOOP AT lt_marc ASSIGNING FIELD-SYMBOL(<s_marc>) USING KEY k1 WHERE matnr EQ <s_mara>-matnr.
ENDLOOP.
ENDLOOP.
Runtime: 0.6 seconds
1
2
3
4
5
6
7
8
9
10
DATA: lt_mara TYPE TABLE OF t_mara
WITH UNIQUE HASHED KEY k1 COMPONENTS matnr, " eindeutig
lt_marc TYPE TABLE OF t_marc.
... (data selection)
LOOP AT lt_marc ASSIGNING FIELD-SYMBOL(<s_marc>).
READ TABLE lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>) WITH KEY k1 COMPONENTS matnr = <s_marc>-matnr.
ENDLOOP.
Runtime: 0.5 seconds
Table type SORTED/HASHED
If you have the option, you should specifically use the correct table type for processing a large number of data records (especially > 10 000). Here you can distinguish between a SORTED TABLE and a HASHED TABLE. As the SAP blog post shows, the access time for the two types of tables behaves as follows:
READ … WITH KEY |
Standard | Sorted | Hashed |
---|---|---|---|
O(1) | Access via index | Access via index | KEY contains the whole table key |
O(logn) | KEY nedds to be sorted and BINARY SEARCH has been added |
KEY contains the whole or the beginning part of the table key |
- |
O(n) | KEY not sorted or no BINARY SEARCH |
KEY does not contains the first field of the table key |
KEY does not contain the whole table key |
LOOP AT … WHERE |
Standard | Sorted | Hashed |
---|---|---|---|
O(1) | - | - | WHERE contains the whole key |
O(logn) | The table need to be sorted according the WHERE clause + workaround: 1. First find the starting index with READ … BINARY SEARCH 2. LOOP AT … WHERE … FROM INDEX |
WHERE contatins the whole or the beginning part of the table key |
- |
O(n) | Every other LOOP AT … WHERE | WHERE does not contain the first field of the table key |
WHERE does not contain the whole table key |
Here it is especially important to note that a wrong use of the type can even lead to a worse result (see examples O(n) for hashed tables.
1
2
3
4
5
6
7
8
9
10
11
12
13
DATA: lt_mara TYPE TABLE OF t_mara,
lt_marc TYPE SORTED TABLE OF t_marc
WITH UNIQUE KEY matnr werks.
... (data selection)
LOOP AT lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>).
LOOP AT lt_marc ASSIGNING FIELD-SYMBOL(<s_marc>) WHERE matnr EQ <s_mara>-matnr.
ENDLOOP.
ENDLOOP.
Runtime: 0.5 seconds
1
2
3
4
5
6
7
8
9
10
11
DATA: lt_mara TYPE HASHED TABLE OF t_mara
WITH UNIQUE KEY matnr,
lt_marc TYPE TABLE OF t_marc.
... (data selection)
LOOP AT lt_marc ASSIGNING FIELD-SYMBOL(<s_marc>).
READ TABLE lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>) WITH KEY matnr = <s_marc>-matnr.
ENDLOOP.
Runtime: 0.5 seconds
Changing data of an internal table
Basically, it does not make much difference whether you use a target structure with LOOP AT INTO
or a field symbol LOOP AT ASSIGNING
when iterating over a table. The LOOP AT INTO
is meanwhile so well optimized in the ABAP kernel that you can measure almost no difference to LOOP AT ASSIGNING
regarding runtime.
As soon as you want to change data in an internal table, however, it makes a big difference whether you work with a structure or a field symbol. In the following examples the amount of data of the table MARA was increased to 804,019, so that you can measure the difference better:
1
2
3
4
5
6
7
8
9
DATA: lt_mara TYPE TABLE OF t_mara.
... (data selection)
LOOP AT lt_mara INTO DATA(ls_mara).
ls_mara-aenam = sy-uname.
MODIFY lt_mara FROM ls_mara TRANSPORTING aenam.
ENDLOOP.
Runtime: 1.9 seconds
1
2
3
4
5
6
7
8
DATA: lt_mara TYPE TABLE OF t_mara.
... (data selection)
LOOP AT lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>).
<s_mara>-aenam = sy-uname.
ENDLOOP.
Runtime: 0.3 seconds
Caching of frequently read data
If there are recurring accesses to a table from different parts of the program, it is worth thinking about a suitable caching method.
For the following examples the data from the first example is used again:
- MARA: 83813
It should be noted here that all materials from the MARA table are in the same material group (MATKL),
Re-reading without cache
The classic approach: just do a select on the database in the loop. It may be that a cache in the database then takes effect here and speeds everything up a bit, but reading the same data over and over again is counterproductive.
1
2
3
4
5
6
7
8
9
10
11
DATA: lt_mara TYPE TABLE OF t_mara.
... (data selection)
LOOP AT lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>).
SELECT SINGLE pernr
FROM pa0105
INTO @DATA(lv_pernr)
WHERE usrid EQ @<s_mara>-aenam.
ENDLOOP.
Runtime: 34 seconds
Reading via local cache
A local cache works on the principle of “I look to see if the record is already in the internal table - if not, I read it from the database”. For this purpose, a “proxy class” with optimized access to the database or internal cache can be used:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
DATA: gt_pa0105 TYPE HASHED TABLE OF pa0105
WITH UNIQUE KEY usrid.
METHOD get_pernr.
READ TABLE gt_pa0105 ASSIGNING FIELD-SYMBOL(<s_pa0105>) WITH KEY usrid = iv_uname.
IF sy-subrc NE 0.
SELECT SINGLE *
FROM pa0105
INTO @DATA(ls_pa0105)
WHERE usrid EQ @iv_uname.
IF sy-subrc NE 0.
" fake entry
ls_pa0105-usrid = iv_uname.
ENDIF.
INSERT ls_pa0105
INTO TABLE gt_pa0105 ASSIGNING <s_pa0105>.
ENDIF.
rv_pernr = <s_pa0105>-pernr.
ENDMETHOD.
1
2
3
4
5
6
7
8
DATA: lt_mara TYPE TABLE OF t_mara.
... (data selection)
LOOP AT lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>).
DATA(lv_pernr) = get_pernr( <s_mara>-aenam ).
ENDLOOP.
Runtime: 0.7 seconds
Buffered table
Tables can also be marked for active buffering in the technical settings for the table (SE11 or SE13). If a table (or record) is buffered, it is kept in its own table buffer on the application server - thus the select on the database is omitted (if the result is in the buffer).
Simply marking the table for buffering does not guarantee that the application server can also store this data in the buffer. Depending on the buffer size or the number of active records in the buffer, buffered records can also be displaced from the buffer again.
1
2
3
4
5
6
7
8
9
10
11
12
DATA: lt_mara TYPE TABLE OF t_mara.
... (data selection)
LOOP AT lt_mara ASSIGNING FIELD-SYMBOL(<s_mara>).
SELECT SINGLE name1 " USR03 ist gebuffert
FROM usr03
INTO @DATA(lv_name1)
WHERE bname EQ @<s_mara>-aenam.
ENDLOOP.
Runtime: 0.8 seconds