cancel
Showing results for 
Search instead for 
Did you mean: 
Read only

Loop over dynamic hashed table with a key

5,076

Hello,

I'm working on custom planning function type that implements IF_RSPLFA_SRVTYPE_IMP_EXEC_REF (planning on reference data).

The EXECUTE method of the implementing class has I_TH_REF_DATA TYPE HASHED TABLE as an 'importing' parameter. This table actually includes my reference data. The structure of the table is defined dynamically at runtime in accord with the structure of an aggregation level. It includes all characteristics and keyfigures that the aggregation level is comprised from.

To avoid full scan of the table I want to loop over it using some kind of 'partial' key that includes only limited set of characteristics. Product assistance suggests something like that:

DATA spfli_tab TYPE HASHED TABLE
OF spfli
WITH UNIQUE KEY primary_key
COMPONENTS carrid connid
WITH NON-UNIQUE SORTED KEY city_from_to
COMPONENTS cityfrom cityto
WITH NON-UNIQUE SORTED KEY city_to_from
COMPONENTS cityto cityfrom.

LOOP AT spfli_tab ASSIGNING <spfli> USING KEY city_from_to.

This, however. does not apply to my task, as the table I_TH_REF_DATA is defined somewhere inside planning engine and I have no control over its structure, keys included.

I know beforehand the structure of the aggregation level, and want to loop using a key that includes, let's say, characteristics 0FISCPER, 0CURRENCY, ZSCOPE and ZVERSION. I need something like

LOOP AT I_TH_REF_DATA ASSIGNING <fs_line> USING KEY COMPONENTS '0FISCPER' '0CURRENCY' 'ZSCOPE' 'ZVERSION'

However, it seems that ABAP syntax does not provide for that.

Please advise, how can I loop over a dynamic hashed table avoiding full table scan?

Thank you,

Val

View Entire Topic
michael_piesche
Active Contributor

1) Using a dynamic keyname to access table by key sort order

So you cannot define the key while programming, but you can define it at runtime? And you want to use the sorted key of the table to loop through the records according to the keys sort order?

" dynmically set your keyname based on the definition based on aggregation level
DATA keyname TYPE c LENGTH 30.
CASE // IF.
  " ...
  keyname = 'MY_DYN_KEY_NAME'.
  " ...
ENDCASE // ENDIF.

LOOP AT I_TH_REF_DATA ASSIGNING <fs_line> USING KEY (keyname). " WHERE (restrictions). 
  " ...
ENDLOOP.

2) Getting keyname and keyattributes from table dynamically

What is unclear for me right now, is whether you already know the key at runtime or just the key components? But if you dont know the key, or the key components you can get them with reading the keys and components at runtime with class CL_ABAP_TABLEDESCR using method GET_KEYS.

DATA: lo_tabledescr TYPE REF TO cl_abap_tabledescr,
      lt_keys       TYPE abap_table_keydescr_tab.
FIELD-SYMBOLS:      <ls_keys>           LIKE LINE OF lt_keys,
                    <ls_key_components> TYPE abap_table_keycompdescr.

" get keys and key components of table by data
lo_tabledescr  ?= cl_abap_tabledescr=>describe_by_data( I_TH_REF_DATA ).

lt_keys = lo_tabledescr->get_keys( ).
LOOP AT lt_keys ASSIGNING <ls_keys> WHERE access_kind = 'S'  " sorted mode
                                         AND key_kind = 'K'. " with key components
   " check whether you want to use the key <ls_keys>-name with decision based on:
   " -is_primary     'X' yes // ' ' no
   " -access_kind    'H' hashed // 'S' sorted
   " -is_unique      'X' yes // ' ' no
   " -key_kind       'T' rowtype // 'K' key components
   " ...

   LOOP AT <ls_keys>-components ASSIGNING <ls_key_components>.
   " check whether you want to use the key component <ls_key_components>-name
   " ...       

   ENDLOOP.
ENDLOOP.
0 Likes

Hello Michael, thank you for the input.

My situation actually looks as follows:

- The hashed table I_TH_REF_DATA is defined by planning engine in accord with the structure of my aggregation level. It includes fields 0FISCPER, 0CURRENCY, ZSCOPE, ZVERSION, and more characteristics and keyfigures. The primary key for this table is also defined by planning engine and contains all characteristics.

- My custom planning function type accepts multiple single values for parameters 'Period' (0FISCPER), 'Currency' (0CURRENCY), 'Consolidation Scope' (ZSCOPE) and 'Version' (ZVERSION). So, my reference data (I_TH_REF_DATA) may contain multiple values for any of this characteristics.

- I have a custom logic implemented as a class method that accepts only single values of 'Period', 'Currency', 'Consolidation Scope' and 'Version'. I need to extract lines that belong to any unique combination of these characteristics from I_TH_REF_DATA, copy it to another dynamic table and pass this dynamic table to the custom logic as 'importing' parameter. To do this I need:

first - define all unique combinations of 'Period', 'Currency', 'Consolidation Scope' and 'Version' (this is done already);

second - for every unique combination of 'Period', 'Currency', 'Consolidation Scope' and 'Version' loop at I_TH_REF_DATA, selecting only lines that belong to this combination.

Doing it with simply LOOP AT - ASSIGNING - WHERE may probably have negative effect on performance, as this looks like plain and simple full table scan. So, what I need is an approach to properly scan I_TH_REF_DATA using its 'hashed' type to achieve better performance. I assume there has to be some instrument to define dynamic key and use it while looping at the table. Your first example looks very much like what I actually need. Could you please elaborate a bit more about defining dynamic key:

DATA keyname TYPE c LENGTH 30.
CASE // IF.
  " ...
  keyname = 'MY_DYN_KEY_NAME'.
  " ...
ENDCASE // ENDIF.

What this procedure may look like in my case?

Thank you,

Val

MateuszAdamus
Active Contributor

Hi Val,

There is no way to dynamically add a key to the already defined internal table. You need to create a new internal table with the required key and populated it with data. Then do your logic on the newly created internal table.

That's exactly what my below answer is showing.

Kind regards,
Mateusz
michael_piesche
Active Contributor
Val Teem, so contrary to what I understood first, the way you need to access the table is not dynamic, but static, based on "'0FISCPER', '0CURRENCY', 'ZSCOPE', 'ZVERSION'", and the only thing that is dynamic, if at all, might be the hashed key components of I_TH_REF_DATA, correct?Why dont you do the following:(I havnt syntax checked it and not all variables are defined)
" 1) get keys and key components of table by data
DATA: lo_tabledescr TYPE REF TO cl_abap_tabledescr, lt_keys TYPE abap_table_keydescr_tab. FIELD-SYMBOLS: <hash_key> LIKE LINE OF lt_keys, <ls_key_components> TYPE abap_table_keycompdescr. lo_tabledescr ?= cl_abap_tabledescr=>describe_by_data( I_TH_REF_DATA ). lt_keys = lo_tabledescr->get_keys( ). READ TABLE lt_keys ASSIGNING <hash_key> WITH KEY is_primary = abap_true.
IF sy-subrc <> 0. " something went wrong ENDIF.
" 2) create new table based on your key fields and the hashed key components
data: structtype type ref to cl_abap_structdescr, tabletype type ref to cl_abap_tabledescr, comp_tab type cl_abap_structdescr=>component_table, new_comp_tab like comp_tab,
new_key_tab type ABAP_KEYDESCR_TAB, linetype type ref to cl_abap_structdescr, dref type ref to data. field-symbols: <wa_comp> like line of comp_tab. field-symbols: <table> type any table.
field-symbols: <table_unique> type any table.
field-symbols: <line> type any.
field-symbols: <ls_key_components> TYPE abap_table_keycompdescr. structtype ?= cl_abap_typedescr=>describe_by_data( I_TH_REF_DATA ). comp_tab = structtype->get_components( ). loop at comp_tab assigning <wa_comp>. CASE <wa_comp>-name. WHEN '0FISCPER' OR '0CURRENCY' OR 'ZSCOPE' OR 'ZVERSION'. " append those as key components as well as new struc (see below)
INSERT <wa_comp> INTO TABLE new_key_tab.
OTHERS.
READ TABLE <hash_key>-components TRANSPORTING NO FIELDS WITH KEY name = <wa_comp>-name.
CHECK sy-subrc = 0. ENDCASE.
INSERT <wa_comp> INTO TABLE new_comp_tab.
endloop. linetype = cl_abap_structdescr=>create( new_comp_tab ). tabletype = cl_abap_tabledescr=>create( p_line_type = linetype p_table_kind = cl_abap_tabledescr=>TABLEKIND_SORTED
P_UNIQUE = abap_false
p_key = new_key_tab
P_KEY_KIND = cl_abap_tabledescr=>KEYDEFKIND_USER ). create data dref type handle tabletype. assign dref->* to <table>.

" unique table can be substituted by LOOP ... GROUP BY ... and LOOP AT GROUP ... statements.
tabletype = cl_abap_tabledescr=>create( p_line_type = linetype p_table_kind = cl_abap_tabledescr=>TABLEKIND_SORTED P_UNIQUE = abap_true p_key = new_key_tab P_KEY_KIND = cl_abap_tabledescr=>KEYDEFKIND_USER ). create data dref type handle tabletype.
assign dref->* to <table_unique>.
create data dref type handle linetype. assign dref->* to <line>.
create data dref type handle linetype. assign dref->* to <line_unique>.
" 3) move input table to new internal dynamic table LOOP AT I_TH_REF_DATA ASSIGNING <fs_line>.
create data dref type handle linetype. assign dref->* to <line>.
MOVE-CORRESPONDING <fs_line> to <line>.
INSERT <line> INTO TABLE <table>. INSERT <line> INTO TABLE <table_unique>.
ENDLOOP. " 4) create dynamic statements to access by keys
" where statement for known key access
lv_where = '0FISCPER = <line_unique>-0FISCPER AND 0CURRENCY = <line_unique>-0CURRENCY AND ZSCOPE = <line_unique>-ZSCOPE AND ZVERSION = <line_unique>-ZVERSION'.
" key statement for unkown (hashed) key access
LOOP AT <hash_key>-components ASSIGNING <ls_key_components>.
lv_hashed_key = lv_hashed_key && ' ' && <ls_key_components>-name && ' = <line>-' && <ls_key_components>-name .
ENDLOOP.

" 5) actual logic loop at <table_unique> assigning <line_unique>.
" aggregation level based on unique keys
" ...

LOOP at <table> assigning <line> WHERE (lv_where).
" data level
" ....

READ TABLE I_TH_REF_DATA ASSIGNING <fs_line> WITH TABLE KEY ( lv_hashed_key ).
IF sy-subrc = 0.
" input data level
" ...

ENDIF. ENDLOOP. endloop.
MateuszAdamus
Active Contributor
0 Likes

Definitely this will work.

However, why do you complicate this so much, when it does the same thing as the example code in my answer?

The TYPEDESCRIPTORs are good when you want to create the whole table structure dynamically from a list of fields. In this case the structure is known, the only change is the new key.

Kind regards,

Mateusz
michael_piesche
Active Contributor
0 Likes

Mateusz Adamus, if everything is static, yes then 'your coding template' will work, with the necessary changes. But I still dont know what all is dynamic from the planning engine from BW. And with the above coding, the OP will have the most possible degree of freedom. And freedom has a price, in this case the 'complexity' or at least the more lines of code.

MateuszAdamus
Active Contributor
0 Likes

Hello Michael,

Your code will work. I can't disagree with that.

But I'm not sure if you're not bringing a cannon to kill a mosquito. There are some assumptions made, e.g.: method parameters, and I think the code could reflect these. Too much freedom isn't always a good thing.

Kind regards,
Mateusz
0 Likes

Hi Mateusz, thank you for the input.

Let me get into more details to make situation more clear.

Any ABAP class implementing IF_RSPLFA_SRVTYPE_IMP_EXEC_REF interface implements EXECUTE method, which typically contains something as follows:

DATA:          l_th_ref_data TYPE REF TO data.
FIELD-SYMBOLS: <fs_th_ref_data>type hashed table,
               <fs_line>type any.

CREATE DATA l_th_ref_data LIKE i_th_ref_data.
ASSIGN l_th_ref_data->* TO <fs_th_ref_data>.

loop at i_th_ref_data assigning <fs_line>.
" Some logic here
collect <fs_line> into <fs_th_ref_data>.
endloop.

I understand that I cannot define any new key on I_TH_REF_DATA. So I need to copy data to another internal table and define key as needed for this new table. <fs_th_ref_data> may serve as an example of this 'new table'. However, I cannot define anything on this table, as the 'CREATE DATA LIKE' construct creates just a 'replica' of the source table (I_TH_REF_DATA). Moreover, I only can use the field symbol <fs_th_ref_data> to manipulate this table. Trying to use l_th_ref_data in the code results in error "l_th_ref_data is not an internal table".

So, it seems unavoidable to use RTTI to create a new table and define its structure accordingly, including key(s). In this way what Michael suggests looks pretty reasonable. Or so it seems to me.

Best regards,

Val