Idioms in ABAP/4: Local table caching
Introduction and motivation
Sometimes you’re writing a report and you need to access a configuration table several times (like company codes) or you need to access some master data across your program.
In this blog I’d like to give you a structure, how you can safely enable local caching of table entries within your programs, to improve performance and readability, as the caching algorithms guarantee, that a SELECT statement for a particular table (entry) is done only once for the whole duration of the program.
The usage of static variables or global variables should be avoided, wherever possible, as it leads to a bad programming style. However we use the STATICS statement here, that encapsulates a program-wide variable into a single form routine. That kind of usage isn’t allowed in the ABAP OO context, anymore. If you’d like to have the caching algorithm within the ABAP OO context, your static variable will become class instance, and the selecting form routine a static method. That way, you could even share the cache across different programs within one execution unit.
But let’s start more simple. And this is with ABAP/4 form routines.
How to use it
The caching mechanism will only work, if you access your table through a single method only. As a matter of fact, you’ll breach this deal, when youstart duplicate SELECT statements again, not reading from the cache.
By encapsulating the table accesses, you’ll also be able to track down the access points of the table(s) more simply, by using the where-used-list of the form routine. Your program implementation will become more readable and more easy to maintain.
The example usage will always have such a format, like the one for example table T001, that holds the company codes. Keep in mind, that there’re already some caching or access routines in place by the SAP Standard, so you may also switch the actual select statement into another function call.
Idiom for full table cache
Create a form routine (when using classical ABAP/4) for read access to your table.
Within the form routine:
FORM select_single_<table to cache>
STATICS: lt_cache TYPE STANDARD TABLE
OF <table to cache>.
IF lt_cache IS INITIAL.
“Fill cache
ENDIF.
READ TABLE lt_cache INTO <return structure>
WITH KEY key(s) = <given key field(s)>
ENDFORM.
Idiom for single line table cache
FORM select_single_<table to cache>
STATICS: lt_cache TYPE STANDARD TABLE
OF <table to cache>.
READ TABLE lt_cache INTO <return structure>
WITH KEY key(s) = <given key field(s)>
IF sy-subrc <> 0. “missed
“Fill cache line
SELECT SINGLE * FROM <table to cache>
INTO <return structure> WHERE …
<when found, insert entry into cache>
ENDIF.
ENDFORM.
Full table cache
REPORT ZADV_TABLE_CACHING_FULL.
PARAMETERS: pv_bukrs TYPE BUKRS. “t001-bukrs
START-OF-SELECTION.
PERFORM start_of_selection.
FORM start_of_selection.
“Lookup on a full table cache
DATA: ls_t001 TYPE T001.
PERFORM select_t001 USING pv_bukrs
CHANGING ls_t001.
IF sy–subrc <> 0.
WRITE:/ ‘Entry not found for BUKRS=’, pv_bukrs.
ELSE.
WRITE:/ ‘Entry T001=’, ls_t001–bukrs, ls_t001–butxt. “,…
ENDIF.
ENDFORM.
Here you can see, that the call for the table lookup should be made very simple, at best, you put it all into one single line of coding. In my examples I have put it into several lines to make it easier for you to read.
Now let’s see, how the caching will work, and we start with the algorithm that just reads the complete content of table into the cache. You can use this technique when you expect a limited low number of table entries.
**********************************************************************
* Local static cache, full table
**********************************************************************
FORM select_t001 USING iv_bukrs TYPE BUKRS
CHANGING cs_t001 TYPE T001.
STATICS: lt_t001 TYPE STANDARD TABLE
OF T001
INITIAL SIZE 50.
“Full cache
IF lt_t001[] IS INITIAL.
“Pre-fill the cache now
SELECT * FROM t001 INTO TABLE lt_t001 ORDER BY PRIMARY KEY. “#EC CI_NOWHERE
ENDIF.
READ TABLE lt_t001 INTO cs_t001
WITH KEY bukrs = iv_bukrs
BINARY SEARCH.
“At the end of the FORM-call, the sy-subrc <> 0 will indicate
“the not-found state, otherwise the changing parameter has valid data
ENDFORM.
First of all you’ll notice the definition of the cache, in this case, a STANDARD TABLE. A sorted table might not work in any situation when reading the full table, but feel free to change it as you need it.
The second section will pre-fill the cache, whenever it has not been initialized yet. The statement is very fast, so it is okay to execute it for every table access on the cache later on. The trade-off to make a pre-fill is better than an unsuccessful READ TABLE before, and then filling the cache, and then reading from the cache again.
The cache lookup is performed by a simple READ TABLE, and we add a search direction, which you can omit, if you like.
The result is now either
sy-subrc = 0 and the table entry of the changing parameter structure has valid data
or it is
sy-subrc <> 0 and there’s no valid data.
You may also CLEAR the result data, when the read access wasn’t successful, but I’d advice to stick to one strategy only.
Caching of single table entries
Caching single table lines will be much the same, but now we put the cache lookup upfront, and maintain the cache afterwards on any missing entry. This way the cache will grow for every different selection on demand.
**********************************************************************
* Local static cache, single table entries
**********************************************************************
FORM select_lfa1 USING iv_lifnr TYPE LIFNR
CHANGING cs_lfa1 TYPE LFA1.
STATICS: lt_lfa1 TYPE SORTED TABLE
OF LFA1
WITH UNIQUE KEY LIFNR “Adapt accordingly
INITIAL SIZE 50.
“First cache lookup
READ TABLE lt_lfa1 INTO cs_lfa1
WITH TABLE KEY lifnr = iv_lifnr.
IF sy–subrc <> 0.
“Not found in cache
SELECT SINGLE * FROM LFA1
INTO cs_lfa1
WHERE lifnr = iv_lifnr.
IF sy–subrc = 0.
“The entry exists and is now already put into the changing/returning structure
“Then add it to the cache now
INSERT cs_lfa1 INTO TABLE lt_lfa1.
ENDIF.
ENDIF.
“At the end of the FORM-call, the sy-subrc <> 0 will indicate
“the not-found state, otherwise the changing parameter has valid data
ENDFORM.
So, here we start with the cache lookup. You can adapt the read table with a BINARY SEARCH extension, when you’re using a standard table (which is sorted).
If the cache was missed, we maintain it by re-using the existing changing variable (that fills the returning element with the same statement).
The generic versions
If you have to access a lot of different cached tables in your program, you might think of using a form generator in the form of macro definition.
**********************************************************************
* Generic table cache on any table with one primary key field
* in addition to MANDT field (if present)
*
* Caches full table
*
**********************************************************************
DEFINE cached_table_full. “&1 = table name
“&2 = fieldname of key
“&3 = data element of key field
*** Generic local static cache, single table entries
FORM select_&1 USING iv_&2 TYPE &3
CHANGING cs_&1 TYPE &1.
STATICS: lt_&1 TYPE STANDARD TABLE
OF &1
INITIAL SIZE 50.
“Full cache
IF lt_&1[] IS INITIAL.
“Pre-fill the cache now
SELECT * FROM &1 INTO TABLE lt_&1 ORDER BY PRIMARY KEY. “#EC CI_NOWHERE
ENDIF.
READ TABLE lt_&1 INTO cs_&1
WITH KEY &2 = iv_&2
BINARY SEARCH.
“At the end of the FORM-call, the sy-subrc <> 0 will indicate
“the not-found state, otherwise the changing parameter has valid data
ENDFORM.
END-OF-DEFINITION.
and you define your form routine like this:
cached_table_full TCURC WAERS WAERS_CURC. “Define form for full cache on TCURC table
And the single-entry version is like this:
**********************************************************************
* Generic table cache on any table with one primary key field
* in addition to MANDT field (if present).
*
* Caches single entries
*
**********************************************************************
DEFINE cached_table_single. “&1 = table name
“&2 = fieldname of key
“&3 = data element of key field
*** Generic local static cache, single table entries
FORM select_&1 USING iv_&2 TYPE &3
CHANGING cs_&1 TYPE &1.
STATICS: lt_&1 TYPE SORTED TABLE
OF &1
WITH UNIQUE KEY &2 “Adapt accordingly
INITIAL SIZE 50.
“First cache lookup
READ TABLE lt_&1 INTO cs_&1
WITH TABLE KEY &2 = iv_&2.
IF sy–subrc <> 0.
“Not found in cache
SELECT SINGLE * FROM &1
INTO cs_&1
WHERE &2 = iv_&2.
IF sy–subrc = 0.
“The entry exists and is now already put into the changing/returning structure
“Then add it to the cache now
INSERT cs_&1 INTO TABLE lt_&1.
ENDIF.
ENDIF.
“At the end of the FORM-call, the sy-subrc <> 0 will indicate
“the not-found state, otherwise the changing parameter has valid data
ENDFORM.
END-OF-DEFINITION.
and you define your form routine like this:
cached_table_single T024 EKGRP EKGRP. “Define form for single cache on T024 table
There might be an ABAP OO version coming up.
So stay tuned.
Florin
Please ->rate or ->like
if you find this blog post useful or if you’d like to give feedback.
changelog: added simplified idiom description in HOW TO USE IT section