HP Guideline ABAP Performance Tuning
HP Guideline ABAP Performance Tuning
Table of Contents
1. Processing Internal Tables..................................................................................................................... 4
To fill an internal table without creating duplicate entries and add up the Packed, Integer, and Floating Point
fields at the same time, use:
COLLECT itab.
Use this for tables for which you expect approximately 50 entries or less. The COLLECT statement scans the
table sequentially for a match on all fields that do not have a data type of Packed, Integer, or Floating Point.
Hence it is can be resource expensive for larger tables.
The second method performs a sequential read from the first record until if finds a match.
The first method performs a binary search to find a matching record, but the table must be sorted first.
1.3. Sorting
Wherever possible, identify the fields to be sorted. The format:
SORT itab.
SORT itab without fields specified attempts to sort the table by all fields other than Packed, Integer, and Floating
Point fields.
Rather than:
LOOP AT itab.
W_COUNT = W_COUNT + 1.
ENDLOOP.
Statement:
If you only want to verify the existence of a record but don’t need any of the fields from the record then use the
addition
TRANSPORTING NO FIELDS
If you only need a few fields from the internal table for processing then use the addition
In order to determine if an internal table contains any records, always use the DESCRIBE statement.
ITAB2[] = ITAB1[].
The same applies if you only want to verify that at least one record exists that satisfies a certain condition:
IF SY-SUBRC = 0.
* Record found …
ENDIF
When reading through a set of related tables, use FOR ALL ENTRIES rather than a logical database. The
combination of using FOR ALL ENTRIES and using the internal tables are more efficient than a set of nested
SELECT ... ENDSELECT statements.
In this case, a CHECK statement that returns a negative result terminates the processing of GET events for
subordinate database tables and the associated data is not read. For efficiency reasons, you should therefore
always perform the CHECK at the highest possible level of the database hierarchy.
For example:
Rather than:
• UP TO 1 ROW can be used to retrieve one record when the full key is not known.
• Whenever the full key is not known you will need to use the SELECT * ... ENDSELECT version of the
SELECT statement.
In this case, specifying values for as many of the table’s key fields in a WHERE clause will make the SELECT
statement more efficient than checking values after the select.
For example:
For example:
ENDSELECT.
Example: if you only need the fields order number, order type and customer from the sales document table, code
as follows:
See the editor help for all the variants of the INTO clause.
This is much faster than issuing a SELECT without the UP TO clause and then checking for the system variable SY -
DBCNT.
Instead of repeatedly accessing data on the database it is better to keep the information in an internal table and
check first if the data has been retrieved already before the ‘expensive’ call to the database is made.
Code sample:
ENDIF.
…
ENDSELECT.
Instead of processing a
statement it is far more efficient to select the fields straight into the internal table and process the data from the
internal table:
This example will select the cumulative sales quantities grouped by material number and quantity unit.
2.11. UPDATE
Instead of updating records within a SELECT … ENDSELECT construct
2.12. DELETE
The same consideration as for the UPDATE is true for the DELETE:
Instead of deleting records within a SELECT … ENDSELECT construct
2.13. COMMIT
ABAP reports that issue INSERT, UPDATE or DELETE commands have to issue COMMIT WORK statements after a
logical unit of work is completed. Missing COMMITs can lead to bottlenecks on the database side because the
system has to keep track of the table changes via rollback segments (in order to enable a rollback of all changes
since the last commit). Rollback segments are kept in memory and missing COMMITs can lead to overflows of
the rollback segment areas. Also the database system holds locks on the changed records that are not released
until COMMIT time.
Example:
The SAP Database Interface transforms this query and submits it to Oracle in the following form:
Since MANDT is the first field of the Primary key index, the Optimizer performs an index range scan using the
Primary key index to access the data, if no other appropriate indexes are found. But in some cases, specially in
the Production environment, where there is only one client, a full table scan could provide better performance
than accessing the table using the index, therefore the "Client Specified" clause is used to bypass the automatic
client handling feature of SAP Open SQL.
Example:
SELECT *
FROM YABC CLIENT SPECIFIED
WHERE column = value
In the above query, the SAP Database Interface will not insert the current client in the WHERE clause. Since the
Production enviro nment has only one client, the query will still return the same rows as the query with a client in
the WHERE clause, the only difference is that the index will be ignored and a full table scan done on YABC.
NOTE: In the development environments, you may need to add the following "WHERE" clause to the query to
exclude the unwanted clients:
To retrieve data from two (or more) related tables is normally done in ABAP/4 by using Nested Selects. For
example: To retrieve all Customer Orders whose names begin with "T" and who are in the "AES1" Sales
Organization one would join the Customer and Order tables using a Nested Select as shown below:
IF SY-SUBRC NE 0.
EXIT.
ENDIF.
ENDSELECT.
The above statement is very inefficient because a new SQL statement has to be executed for every single row
found in the outer select.
A better way to achieve the same result is to use the "FOR ALL ENTRIES" statement in the inner SELECT statement
as show below:
The following two approaches (as applicable) may be used to solve the above problem and force Oracle to use
the appropriate index:
The FIRST approach uses SAP Open SQL and should be used on relatively small tables with less than 100,000
rows. In this approach, the query forces the usage of the appropriate index containing MANDT and the Field (F1)
on which the MAX function is applied because of the enhanced WHERE clause.
Example:
SELECT MAX(F1)
FROM T1
WHERE F1 > least_value AND F1 <= highest_value
The SECOND approach requires the use of Native SQL and should be used on large tables greater than
100,000 rows. In this approach, the data is selected directly from the index rather than the table by retrieving
the record from the bottom of the index. This does not require a MAX function call or an 'actual' sort on the
retrieved records.
The example below gets the Maximum value for the field FCBSPDDT from table YTPNUBD whose Primary Key is
MANDT+FCBSPDDT+FCVRNO. It first checks whether the index exists or not. If it does, then it retrieves the
Maximum value from the index, else it uses the unoptimized approach. The primary drawback with this
approach is that the index name has to be hard coded in the query as a hint to Oracle. If the index name is
changed, then either the query has to be changed to maintain optimal performance, else the performance may
deteriorate.
Example:
Are there SELECTs without WHERE conditions Re-consider your program design!
against large tables or tables which grow
constantly (BSEG, MKPF, VBAK)?
Are CHECK statements for table fields Incorporate the CHECK statements into the
embedded in a SELECT ... ENDSELECT loop ? WHERE clause of the SELECT statement and
control the access path with SDBE after the
checks are imbedded.
Are there SELECT statements for which the If you have to read less than 20% of the table's
execution path is INDEX RANGE SCAN of an records or the table is small, try to apply WHERE
index, which has MANDT as the first field and criteria that are better supported by the index or
the other index fields are not (or only with gaps) discuss the creation of an index with DDIC
specified in the WHERE clause? (Use transaction support. If you have to read more than 20 % of
SDBE to check) the table, consider forcing a full table scan (s
below).
Does the program use non obligatory SELECT- Make sure the select options fields that are used
OPTIONS or PARAMETERS without default in the WHERE clause and support index access
values in the WHERE clause (SELECT … WHERE always have a value. Define them as
field IN s_field)? OBLIGATORY, set a DEFAULT value and check
what users enter (event AT-SELECTION-SCREEN).
If they don't enter any criteria select only UP TO
NN ROWS. NN can also be a parameter with a
default value
Is there a SELECT that forces Oracle to touch or Force a full table scan using CLIENT SPECIFIED,
look at more than 20% of the rows? dropping the WHERE clause for MANDT (but not
other where criteria) and read into an internal
table
Is there a SELECT with 'CLIENT SPECIFIED'? If this is used to force a full table scan (reading
more than 20% of the table rows), make sure
there is no MANDT in the WHERE clause. Other
WHERE criteria should be provided to avoid
network traffic)
Are there duplicate SQL statements to a table Read the data into an internal table and use
using the same WHERE criteria? binary search methods to retrieve it. Beware of
internal table sizes (you might have to read with
PACKAGE SIZE.. If the table is too large) and try
to limit the width if possible.
Do SELECTS on non-key fields use an Check with DDIC support, whether an index for
appropriate DB index or is the table buffered ? the table or buffering would make sense
Is the program using nested SELECTs to retrieve Convert nested selects to SELECT xxx FOR ALL
data? (SELECT ….FROM MASTER. SELECT ENTRIES IN ITAB or into SELECT …. WHERE field
FROM DETAIL. ENDSELECT. ENDSELECT.) IN range_table if the driving range table only
has up to 200 entries. Beware of internal table
sizes (you might have to read with PACKAGE
SIZE.. If the table is too large) and try to limit the
width if possible.
Does the program read data into an internal Convert nested selects to SELECT xxx FOR ALL
table itab, loops at itab and executes selects to ENTRIES IN ITAB. Make sure the WHERE clause
retrieve detail information? is supported by an index. (S. also 'Efficient use of
IN clause' on Enterprise Database Services
website)
Is the program using FOR ALL ENTRIES IN itab 1. Make sure it doesn't run against an empty
WHERE? internal table itab - it would return ALL records of
the table!
Is the program using SELECT... APPEND Change the processing to read the data
ITAB ..ENDSELECT techniques to fill internal immediately into an internal table (SELECT
tables? VBELN AUART... INTO TABLE IVBAK...) Beware
of internal table sizes (you might have to read
with PACKAGE SIZE... If the table is too large)
and try to limit the width if possible.
Are there SELECT… INTO CORRESPONDING If the structure of the fields you select and the
FIELDS OF TABLE ITAB statements? internal table is the same, skip the
'CORRESPONDING FIELDS' clause
Are there SELECT… INTO TABLE.. Statements, To avoid memory allocation problems, read in
which read HUGE tables into memory? packages (SELECT.. PACKAGE SIZE) and use
FREE itab to release allocated memory as soon
as possible
Is the program using SELECT ORDER BY Data should be read into an internal table first
statements? and then sorted, unless there is an appropriate
index on the order by fields
Are there SELECT… ENDSELECT statements to Use SELECT... FROM...UP TO 1 ROWS to stop
verify, whether there is at least one record, the query after the first match is found. Support
which matches a criteria? the query with a proper WHERE clause so that
an index can be used.
Are there SELECT MAX (field) … statements If there is an index available for 'field', add
without WHERE clause? WHERE criteria for 'field' ("field BETWEEN 0
AND 999999") to support index access
Are there statements like LOOP AT itab. MOVE Replace this by UPDATE (INSERT) DBTAB FROM
ITAB TO DBTAB. UPDATE (INSERT) DBTAB. itab or use UPDATE DBTAB WHERE… SET...
ENDLOOP?
Are there INSERTS/UPDATES insight Open a cursor with hold and use function module
SELECT …ENDSELECT loop, which accesses a DB_COMMIT to save database changes after a
huge table? certain number of records to avoid SNAPSHOT
TO OLD errors
Is there a DELETE FROM dbtab WHERE field IN Make sure range_tab is never empty - otherwise
range_tab statement? all the rows of dbtab will be deleted!!
3. Miscellaneous
Moving data from one table work area/structure to another one with an identical structure.
Use:
MOVE structure 1 TO structure2
Rather than:
Rather than:
IF field CP ‘#’.
ENDIF.
fieldlength = SY-FDPOS.
For example:
This is because the search for the field (in this case KNA1-NAME1) is carried out only in the Data Dictionary and
not in the symbol table. The field must then be a component field of a database table declared with the TABLES
statement. This improves the performance of this statement considerably. In contrast to the second method
above, the performance does not depend on the number of fields used within the program.
For example:
IF MARA-IDNRA = SPACE.
....
ENDIF.
IF MARA-IDNRA IS INITIAL.
....
ENDIF.
But only for the first time the field is tested. This is because the IS INITIAL test, SAP must determine what data
type the field being tested is and then determine what value it is checking for (e.g. space(s) for character fields,
zero for numeric fields, etc.). After a field has been tested once, SAP remembers its data type and subsequent
‘IS INITIAL’ tests are equivalent in efficiency to testing against a field (or constant) of the same data type.
IF field = value1.
....
ELSEIF field = value2.
....
ENDIF.
Or:
CASE field.
WHEN value1.
....
WHEN value2.
....
WHEN value3.
....
WHEN valuen.
....
ENDCASE.
The first method is more efficient when checking a field for up to about five values. But the improved readability
of the program code associated with the CASE statement dictates that its use should be applied for levels of three
or greater.
For example, fieldx can have values ‘A’, ‘B’, or ‘C’. A value of ‘B’ is the most likely value to occur, followed by
‘C’, then ‘A’; to optimize a CASE statement for fieldx, code the CASE statement as follows:
CASE fieldx.
WHEN ‘B’. “Most likely value
....
WHEN ‘C’. “Next most likely value
....
WHEN ‘A’. “Least likely value
....
ENDCASE.
Here, if fieldx has a value of ‘B’, only one test is performed, if it has a value of ‘C’, two tests must be performed,
and so on.
Coding in this manner reduces the average number of tests performed by the program.
Use:
IF field NE 0.
PERFORM SUB1.
ENDIF.
FORM SUB1.
....
ENDFORM.
Rather than:
PERFORM SUB1.
FORM SUB1.
IF field NE 0.
....
ENDIF.
ENDFORM.
In very simple terms, Integers (type I) is the fastest, Floating Point (type F) requires more time, and Packed (type P)
is the most expensive.
Normally, packed number arithmetic is used to evaluate arithmetic expressions. If, however, the expression
contains a floating point function, or there is at least one type F operand, or the result field is type F, floating
point arithmetic is used instead for the entire expression. On the other hand, if only type I fields or date and time
fields occur, the calculation involves integer operations.
Since floating point arithmetic is fast on SAP hardware platforms, you should use it when you need a greater
value range and you are able to tolerate rounding errors. Rounding errors may occur when converting the
external (decimal) format to the corresponding internal format (base 2 or 16) or vice versa.
All Packed fields are treated as whole numbers. Calculations involving decimal places require additional
programming to include multiplication or division by 10, 100, 1000, etc... The DECIMALS specification with the
DATA declaration is effective only for output with the WRITE statement. If, however, fixed point arithmetic active
(program attributes) is active, the DECIMALS specification is also taken into account. In this case, intermediate
results are calculated with maximum accuracy (31 decimal places). This applies particularly to division. For this
reason, you should always set the program attribute "Fixed point arithmetic".
The return code should always be checked after any database table read/update statements.
Be aware that many ABAP/4 statements will set the value of the system return code. It is advisable, therefore, to
test the return code immediately after the statement whose outcome is important, rather than further along in the
code.
Refer to Appendix A for a list of ABAP/4 statements/keywords that set the system return code and possible
values as a result of these.
For SELECT ... ENDSELECT processing loops, the return code should be checked after the ENDSELECT statement
to check for the success of the SELECT statement.
For LOOP AT ... ENDLOOP processing loops, the return code should be checked after the ENDLOOP statement
to check for the success of the LOOP statement.
To increase ABAP/4 code readability, it may be useful to identify what text is being output / written by including
a comment in the program code, or by specifying the Numbered Text as follows:
1. Prepare
a. Know your table and the data within the table.
What indexes exist for the table? Check with your supervisor or functional analyst.
What indexed attributes identify small result sets? In contrast, are there certain fields for which
the data in the entire table is roughly the same (e.g. a country code field for a table
could very well be USA for every row in the table...this attribute would "not" help identify
small result sets, instead it would bring back everything from the table)?
How large is the table, i.e. number of rows, width of rows, and so on?
Is this a critical processing period, i.e. month-end close? If so, wait until the system is less busy.
If possible, run your SE(NN) transactions in the evenings or weekends when the system is less
busy.
Always enter in as many fields of data as possible. Especially important are date related fields. The
more data you supply, the less work the database will have to do "behind the scenes", and the faster
your results will be returned.
Never run an SE16 type query against a database view. If you're not sure if something's a "view", ask
your supervisor.
As a general rule, if a field allows for single or multiple entries, try to stick to using the single entry.
Multiple value fields can eliminate the effectiveness of an index.
Only retrieve data that you absolutely need (e.g. if you only need data from one Organization, supply
that field in the SE16 entry screen, versus using an '*' to pull back all the possible entries for that field)
Most importantly, if you have any doubts on how the query will perform, run the query in a large test
environment to test performance before invoking it on production data.
b. SE38 / SA38
Do not execute a program that may run for more than a few minutes
Consider running programs in background mode. Basically there are two ways to run programs in SAP--
interactively, in what's known as "dialog" mode, or via "background" mode. When programs are run in
dialog mode, the program will run and tie up a user's SAP gui session. This will continue until either the
program finishes or is cancelled. From the user's perspective, the SAP session will be busy and unusable
until it finishes.
Programs run in dialog mode are intended to be quick ones. Period. Therefore, certain restrictions are in
place, including an automatic "time out" for programs running more than 30 minutes. If that time limit is
exceeded, the user's session will be terminated, and no results will be returned. Starting additional
programs, in this case, one after another will not help. They will time out as well and place an
unnecessary load on the system.
Longer running programs need to be run via background mode. There are no automatic "time out's" for
programs run this way. Not all users have access to run jobs in this manner. Please work with your
supervisor to request this access if it is deemed applicable.
4. Helpful Hints
When running these transactions, be aware that it is possible for you to cancel, close, ctl-alt-delete, or
even power off your computer, and the program can still be running, behind the scenes, on the
database! If your "end" of the SAP GUI has been closed in this manner, then SAP will never be able to
return the results back to you. Nonetheless that transaction will still be consuming resources on the
database, affecting the performance of everything else running in the system.
Be aware that an SE16 transaction can be killed at the SAP level and still be running in the database.
Run the transaction that will return only the information you want.
5. If a query ever gets away from you, make sure it's cleared from the system! If the transaction is still
running after 10 minutes, you've likely issued an inefficient query and are adversely affecting production
applications. Use SQL Trace and see where the problem is and redesign the SQL.
Don't try the query again until you understand what happened and how to prevent it in the future.
2. GIVE THE DATABASE A BREAK WHENEVER POSSIBLE. The fastest query is the one that was never
issued
4. UNDERSTAND HOW THE DATABASE SELECTS AN ACCESS PATH AND EXECUTES A SQL
STATEMENT.
6. CREATE SEPARATE MODULES FOR SQL STATEMENTS OR USE VIEWS FOR FREQUENTLY USED
SELECT STATEMENTS.
Promotes execution of SQL statements that look exactly the same
9. PERFORM A FULL TABLE SCAN IF THE TABLE IS SMALL OR MORE THAN 20% OF THE ROWS ARE
RETRIEVED.
Delete the index, change the "WHERE" clause, or add "CLIENT SPECIFIED" and omit the "WHERE"
clause to suppress the index.
11. FOR COMPARISONS USE "=" COMBINED WITH ANDs, IN THE "WHERE" CLAUSE
14. CHECK WITH THE DB DESIGN GROUP IF TABLE BUFFERING CAN BE USED FOR CODE/DECODE
TABLES.
15. USING AN AGGREGATE TABLE IS BETTER THAN USING A "GROUP BY" STATEMENT.
19. USE CURSORS TO COMMIT TABLE CHANGES DURING LONG RUNNING "SELECTS" (FUNCTION -
DB_COMMIT).
20. ALWAYS CREATE INDEXES FOR MATCHCODE IDs AND APPLY SELECT CRITERIA OR SET/GET
PARAMETERS TO LIMIT THE NUMBER OF ROWS RETURNED.
21. AVOID LOGICAL DATABASES IF AT ALL POSSIBLE BECAUSE THEY ARE NOT USUALLY TUNED; IF YOU
HAVE TO USE THEM, WRITE PROPER STATEMENTS TO ACCESS THEM.
22. TO LOCK SEVERAL TABLE ROWS DO NOT CALL THE "ENQUEUE" FUNCTION FOR EACH ROW, BUT
USE GENERIC LOCKS INSTEAD.