How to create a internal table based on object attribute? - oop

I am trying to create sorted table based on instances.
Here below my coding :
CLASS my_fancy_pony DEFINITION.
PUBLIC SECTION.
DATA mv_nice_key TYPE i.
ENDCLASS.
CLASS my_fancy_pony IMPLEMENTATION.
ENDCLASS.
DATA lt_my_fancy_ponys TYPE SORTED TABLE OF REF TO my_fancy_pony WITH UNIQUE KEY mv_nice_key.
DATA lt_my_fancy_ponys TYPE SORTED TABLE OF REF TO my_fancy_pony WITH UNIQUE KEY table_line->mv_nice_key.
This is not passing through the syntax check.
Could you tell how I can achieve that?

You can't use classes directly as structures.
However, you can construct a map that does what you need:
TYPES:
BEGIN OF map_entry,
mv_nice_key TYPE i,
ref_to_object TYPE REF TO my_fancy_pony,
END OF map_entry.
TYPES map_type TYPE SORTED TABLE OF map_entry WITH UNIQUE KEY mv_nice_key.
DATA(lt_my_fancy_pony_map) = VALUE map_type(
FOR each IN lt_my_fancy_ponys
( mv_nice_key = each->mv_nice_key
ref_to_object = each ) ).
The map can number any of the class's attributes that you need:
TYPES:
BEGIN OF map_entry,
mv_nice_key TYPE i,
some_other_att TYPE i,
" ...
ref_to_object TYPE REF TO my_fancy_pony,
END OF map_entry.
TYPES map_type TYPE SORTED TABLE OF map_entry WITH NON-UNIQUE KEY table_line.
DATA(lt_my_fancy_pony_map) = VALUE map_type(
FOR each IN lt_my_fancy_ponys
( mv_nice_key = each->mv_nice_key
some_other_att = each->some_other_att
" ...
ref_to_object = each ) ).

Related

Simplify select using hierarchy function

I wonder if it could be possible to simplify the following ABAP-based TDEVC selection using the new hierarchy functions of ABAP SQL.
Table TDEVC is SAP's table of packages (= development classes), with the columns (DEVCLASS,PARENTCL) definig the hierarchy.
The goal is to list each package in the custom namespace, whose name starts with the letter Z, together with its parent package and its root package (the uniquely defined ancestor with PARENTCL = ''.
This would be the selection without hierarchy, performed with plain old ABAP. I didn't succeed to define TDEVC as a hierarchy source in the SELECT statement itself, as described in the ABAP documentation.
types:
begin of ty_node,
devclass type devclass,
parentcl type devclass,
root type devclass,
end of ty_node,
ty_nodes type sorted table of ty_node
with non-unique key parentcl.
data:
lt_nodes type ty_nodes,
lt_root type ty_nodes,
lt_parents type ty_nodes.
field-symbols:
<ls_node> type ty_node,
<ls_parent> type ty_node.
select devclass, parentcl from tdevc
into table #lt_nodes
where devclass like 'Z%'.
loop at lt_nodes assigning <ls_node>
where parentcl eq ''.
insert value #(
devclass = <ls_node>-devclass
root = <ls_node>-devclass )
into table lt_root.
delete lt_nodes.
endloop.
lt_parents = lt_root.
while lt_parents is not initial.
data(lt_parents_new) = value ty_nodes( ).
loop at lt_parents assigning <ls_parent>.
loop at lt_nodes assigning <ls_node>
where parentcl = <ls_parent>-devclass.
<ls_node>-root = <ls_parent>-root.
insert <ls_node> into table lt_parents_new.
endloop.
endloop.
lt_parents = lt_parents_new.
endwhile.
* Result is now in lt_nodes

Generic way of handling concatenated table key

If I have a tabkey value, e.g., DATA(lv_tabkey) = '1000041508773180000013000'., which is the concatenated value of all table keys for an entry and I know the name of the corresponding table:
How I can get the table entry for it without splitting tabkey manually and therefore having to write the order and length of each key field?
Full example:
" The first 3 chars always belong to the 'mandt' field
" which can't be filtered in the SELECT, therefore
" I ignore it and start with key2
DATA(lv_tabkey) = '1000041508773180000013000'.
"ToDo - how to make this generic? - START
DATA(lv_key2) = lv_tabkey+3(12).
DATA(lv_key3) = lv_tabkey+15(3).
DATA(lv_key4) = lv_tabkey+18(4).
DATA(lv_key5) = lv_tabkey+22(3).
DATA(lv_where) = 'key2 = ' && lv_key2 &&
' AND key3 = ' && lv_key3 &&
' AND key4 = ' && lv_key4 &&
' AND key5 = ' && lv_key5.
"ToDo - how to make this generic? - END
SELECT *
FROM table_x
INTO TABLE DATA(lt_results)
WHERE (lv_where).
I think I have to somehow iterate over the table fields, find out the keys and their length - but I don't know how to do this.
The statement you are seeking is:
ASSIGN tabkey TO < structure> CASTING TYPE HANDLE r_type_struct.
Knowing type handle for the (table key) structure you can fill it with values in a generic way and query the table using the structure. Here is how:
DATA: handle TYPE REF TO data,
lref_struct TYPE REF TO cl_abap_structdescr.
FIELD-SYMBOLS: <key_fld> TYPE abap_componentdescr.
SELECT * UP TO 5000 ROWS
FROM cdpos
INTO TABLE #DATA(t_cdpos)
WHERE tabname NOT LIKE '/%'.
LOOP AT t_cdpos ASSIGNING FIELD-SYMBOL(<fs_cdpos>).
lref_struct ?= cl_abap_structdescr=>describe_by_name( <fs_cdpos>-tabname ).
* get key fields
DATA(key_fields) = VALUE ddfields( FOR line IN lref_struct->get_ddic_field_list( ) WHERE ( keyflag NE space ) ( line ) ).
* filling key field components
DATA(key_table) = VALUE abap_component_tab( FOR ls_key IN key_fields
( name = ls_key-fieldname
type = CAST #( cl_abap_datadescr=>describe_by_name( ls_key-domname ) )
)
).
* create key fields type handle
TRY.
DATA(r_type_struct) = cl_abap_structdescr=>create( key_table ).
CATCH cx_sy_struct_creation .
ENDTRY.
* create key type
CHECK r_type_struct IS NOT INITIAL.
CREATE DATA handle TYPE HANDLE r_type_struct.
ASSIGN handle->* TO FIELD-SYMBOL(<structure>).
* assigning final key structure
ASSIGN <fs_cdpos>-tabkey TO <structure> CASTING TYPE HANDLE r_type_struct.
* filling values
LOOP AT key_table ASSIGNING <key_fld>.
ASSIGN COMPONENT <key_fld>-name OF STRUCTURE <structure> TO FIELD-SYMBOL(<val>).
CHECK sy-subrc = 0.
<key_fld>-suffix = <val>.
ENDLOOP.
DATA(where_cond) = REDUCE string( INIT where = ` ` FOR <field> IN key_table WHERE ( name <> 'MANDT' ) NEXT where = where && <field>-name && ` = '` && <field>-suffix && `' AND ` ).
where_cond = substring( val = where_cond off = 0 len = strlen( where_cond ) - 4 ).
IF <fs_cdpos>-tabname = 'BNKA'.
SELECT *
INTO TABLE #DATA(lt_bnka)
FROM bnka
WHERE (where_cond).
ENDIF.
ENDLOOP.
Here I built the sample on table CDPOS that contain table names and additionally concatenated key values in field tabkey, in other words exactly what you are trying to use.
In a loop it detects table types, builds the key and make SQL query in a generic way. Here I used table BNKA for simplicity, but SQL SELECT can be generized as well via field-symbol. Also I made a trick by filling values into the same tab that contains structure components, in SUFFIX field.
P.S. Before passing where condition into query make proper data type validation to avoid such errors as SAPSQL_DATA_LOSS, because with new syntax it makes a strict check.
your use case reminds me that how I deal with Change Document key.(CDHDR/CDPOS).
Hope it helps!
DATA:
lv_tabkey TYPE char50,
ls_table TYPE table_x.
FIELD-SYMBOLS:
<ls_src_x> TYPE x,
<ls_tgt_x> TYPE x.
"Add Client info the Table key if your table is Client dependent.
CONCATENATE sy-mandt lv_tabkey INTO lv_tabkey.
ASSIGN lv_tab_key TO <ls_src_x> CASTING.
ASSIGN ls_table TO <ls_tgt_x> CASTING.
<ls_tgt_x> = <ls_src_x>.
"Now ls_table has the key info filled including MANDT if you have the MANDT in table key.
SELECT *
FROM table_x
INTO TABLE DATA(lt_results)
WHERE key2 = ls_table-key2 AND key3 = ls_table-key3
AND key4 = ls_table-key4 AND key5 = ls_table_key5.

How to assign internal table into a structure and then to a field in ABAP

With the below code I can retrieve the content of the internal table t_t005e, however when put into the field wa_upload-region, only the first column of the data is retrieved, however I want to retrieve the third column data.
TYPES: BEGIN OF ty_upload,
" ...
region TYPE regio,
" ...
END OF ty_upload.
DATA: wa_upload TYPE ty_upload,
t_t005e TYPE STANDARD TABLE OF t005e.
READ TABLE t_t005e
INTO wa_upload-region
WITH KEY land1 = 'GB'
regio = 'YK'
counc = ''.
As a result, I have created a work area wa_t005e, with the same type as the lines of t_t005e.
I want to first read the internal table t_t005e into the work area wa_t005e, then to the field wa_upload-region.
Following is my work in progress:
DATA: wa_t005e TYPE t005e.
LOOP AT t_t005e INTO wa_t005e.
ASSIGN COMPONENT wa_t005e-regio OF STRUCTURE
wa_t005e TO <wa_upload-region>.
ENDLOOP.
How to get the data of wa_t005e-regio into the field wa_upload-region?
There is no way of reading the value of only one column from a table directly into one field of a structure, at least in systems pre-7.40. If you do have a 7.40 system, you can use a "table expression" like this:
TRY.
wa_upload-region = t_t005e[ land1 = 'GB' regio = 'YK' counc = '' ]-regio.
CATCH cx_sy_itab_line_not_found.
ENDTRY.
In older system, you will have to read the whole table line into a structure, then you can just take the field from it, like this:
READ TABLE t_t005e INTO wa_t005e WITH KEY land1 = 'GB' regio = 'YK' counc = ''.
wa_upload-region = wa_t005e-regio.
If you want to use ASSIGN and the like, you can do that too. First you would read the table line into a structure again (in this case a field symbol to stay in theme). Then assign the needed component/field of the structure to a single-value field symbol.
DATA: t_upload TYPE STANDARD TABLE OF ty_upload,
t_t005e TYPE STANDARD TABLE OF t005e.
FIELD-SYMBOLS: <fs_upload> TYPE ty_upload,
<fs_t005e> TYPE t005e,
<region> TYPE regio. " or type any
SELECT *
FROM t005e
INTO CORRESPONDING FIELDS OF TABLE t_t005e.
READ TABLE t_t005e ASSIGNING <fs_t005e> WITH KEY land1 = 'GB' regio = 'YK' counc = ''.
ASSIGN COMPONENT 'REGIO' OF STRUCTURE <fs_t005e> TO <region>. " <---
*Other option: number of column
*ASSIGN COMPONENT 3 OF STRUCTURE <fs_t005e> TO <region>.
APPEND INITIAL LINE TO t_upload ASSIGNING <fs_upload>.
<fs_upload>-region = <region>.
WRITE <fs_upload>-region.
But is reading only one entry from the table really what you want to do? You didn't specify all keys of t005e in the READ statement. It would only select the first line that fits.

Creating a range for a field from internal table using RTTS

I want to create a function/custom class method that takes in 2 parameters:
1) IM_ITAB type ANY TABLE
2) IM_COMPONENT type STRING
and returns 1 parameter:
1) EX_RANGE type PIQ_SELOPT_T
So, algorithm is like this:
First of all, we check if the column with a component name at all exists
Then, we check that internal table is not empty.
Then, we loop through internal table assigning component and filling range table. Code is below.
METHODS compose_range_from_itab
IMPORTING
IM_ITAB type ANY TABLE
IM_COMPONENT type STRING
EXPORTING
EX_RANGE type PIQ_SELOPT_T.
...
METHOD compose_range_from_itab.
DATA: lo_obj TYPE REF TO cl_abap_tabledescr,
wa_range TYPE selopt,
lt_range TYPE piq_selopt_t.
FIELD-SYMBOLS: <fs_line> TYPE ANY,
<fs_component> TYPE ANY.
lo_obj ?= cl_abap_typedescr=>describe_by_data( p_data = im_itab ).
READ TABLE lo_obj->key TRANSPORTING NO FIELDS WITH KEY name = im_component.
IF sy-subrc IS INITIAL.
IF LINES( im_itab ) GT 0.
LOOP AT im_itab ASSIGNING <fs_line>.
ASSIGN COMPONENT im_component OF STRUCTURE <fs_line> TO <fs_component>.
wa_range-sign = 'I'.
wa_range-option = 'EQ'.
wa_range-low = <fs_component>.
APPEND wa_range TO lt_range.
ENDLOOP.
SORT lt_range BY low.
DELETE ADJACENT DUPLICATES FROM lt_range COMPARING low.
ex_range[] = lt_range[].
ENDIF.
ENDIF.
ENDMETHOD.
But I want to improve the method further. If the imported internal table has, let's say, 255 columns, then it will take longer to loop through such table. But I need only one column to compose the range.
So I want to get components of internal table, then choose only one component, create a new line type containing only that component, then create internal table with that line type and copy.
Here is the pseudo code corresponding to what I want to achieve:
append corresponding fields of im_itab into new_line_type_internal_table.
How can I "cut out" one component and create a new line type using RTTS?
You are overcomplicating everything, you don't need RTTS for that.
DEFINE make_range.
ex_range = VALUE #( BASE ex_range ( sign = 'I' option = 'EQ' low = &1 ) ).
END-OF-DEFINITION.
LOOP AT im_itab ASSIGNING FIELD-SYMBOL(<fs_line>).
ASSIGN COMPONENT im_component OF STRUCTURE <fs_line> TO FIELD-SYMBOL(<fs_field>).
CHECK sy-subrc = 0 AND <fs_field> IS NOT INITIAL.
make_range <fs_field>.
ENDLOOP.
And yes, as Sandra said, you won't gain any performance with RTTS, just the opposite.
Surprisingly, this variant turned out to be faster:
CLASS-METHODS make_range_variant_2
IMPORTING
sample TYPE table_type
column TYPE string
RETURNING
VALUE(result) TYPE range_type.
METHOD make_range_variant_2.
TYPES:
BEGIN OF narrow_structure_type,
content TYPE char32,
END OF narrow_structure_type.
TYPES narrow_table_type TYPE STANDARD TABLE OF narrow_structure_type WITH EMPTY KEY.
DATA narrow_table TYPE narrow_table_type.
DATA(mapping) =
VALUE cl_abap_corresponding=>mapping_table_value(
( kind = cl_abap_corresponding=>mapping_component srcname = column dstname = 'CONTENT' ) ).
DATA(mover) =
cl_abap_corresponding=>create_with_value(
source = sample
destination = narrow_table
mapping = mapping ).
mover->execute(
EXPORTING
source = sample
CHANGING
destination = narrow_table ).
LOOP AT narrow_table ASSIGNING FIELD-SYMBOL(<row>).
INSERT VALUE #(
sign = 'I'
option = 'EQ'
low = <row>-content )
INTO TABLE result.
ENDLOOP.
ENDMETHOD.
CL_ABAP_CORRESPONDING delegates to a kernel function for the structure-to-structure move, which apparently is faster than the ABAP-native ASSIGN COMPONENT [...] OF STRUCTURE [...] TO FIELD-SYMBOL [...]. The actual loop then seems to be faster because it uses fixed-name assignments.
Maybe somebody could verify.
I would not go for a Macro.
Data:
lr_data type ref to data.
FIELD-SYMBOLS:
<lv_component> TYPE any,
<ls_data> TYPE any.
CREATE DATA lr_data LIKE LINE OF im_itab.
ASSIGN lr_data->* TO <ls_data>.
"Check whether im_component exists
ASSIGN COMPONENT im_component OF STRUCTURE <ls_data> TO <lv_component>.
CHECK sy-subrc EQ 0.
LOOP AT im_itab INTO <ls_data>.
APPEND VALUE #( sign = 'I' option = 'EQ' low = <lv_component> ) TO ex_range.
ENDLOOP.

Conversion exception while working with constructor expressions

I'm working on a routine which moves the lines of a string table (in this case fui_elements) into a structure of unknown type (fcwa_struct).
DATA(li_temp) = ... "fill assignment table here
LOOP AT fui_elements ASSIGNING FIELD-SYMBOL(<lfs_element>).
ASSIGN COMPONENT li_temp[ sy-tabix ] OF STRUCTURE fcwa_struct
TO FIELD-SYMBOL(<lfs_field>).
IF sy-subrc <> 0.
"somethings wrong with the fui_elements data
ENDIF.
<lfs_field> = <lfs_element>.
ENDLOOP.
If the table i_field_customizing (STANDARD TABLE OF string) is not initial, I want to use its values.
Otherwise I want to generate an integer table (so that the loop runs equally often regardless of the table's values). Here lw_max is the amount of fields the imported structure has:
DATA(li_temp) = COND #( WHEN i_field_customizing[] IS INITIAL
THEN VALUE t_integer_tt( FOR X = 1
THEN X + 1
WHILE X <= lw_max
( X ) )
ELSE i_field_customizing ).
But when I run the report with i_field_customizing like that:
DATA(i_field_customizing) = VALUE t_string_tt( ( `KUNNR` ) ( `NAME1` ) ).
I get this exception on the line where I try to construct li_temp:
CX_SY_CONVERSION_NO_NUMBER (KUNNR cannot be interpreted as a number)
My current guess is that COND gets its type statically. Does anybody know how I can get around this?
What you are trying to achieve will not be possible because the type of an inline definition of a variable using COND is decided at compilation time and not at runtime.
Please see my question here. The type that will be taken is always the type of the variable that stands directly after THEN. You can decide what type will be chosen at compilation time by fiddling with negating the condition and switching places of variables after THEN at ELSE but it will be always either or and from what I understand you want to be able to do it dynamically so your ASSIGN COMPONENT statement works as expected with integers.
Even by specifically casting the variable after ELSE one gets the same short dump as you do.
DATA(li_temp) = COND #( WHEN i_field_customizing IS INITIAL
THEN VALUE t_integer_tt( ( 1 ) ( 2 ) )
ELSE CAST t_string_tt( REF #( i_field_customizing ) )->* ).
Alternatively you could cast to REF TO DATA but then you have to dereference it to a field symbol of type STANDARD TABLE.
REPORT zzy.
CLASS lcl_main DEFINITION FINAL CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
main.
ENDCLASS.
CLASS lcl_main IMPLEMENTATION.
METHOD main.
TYPES:
t_integer_tt TYPE STANDARD TABLE OF i WITH EMPTY KEY,
t_string_tt TYPE STANDARD TABLE OF string WITH EMPTY KEY.
FIELD-SYMBOLS:
<fs_table> TYPE STANDARD TABLE.
DATA: BEGIN OF l_str,
kunnr TYPE kunnr,
name1 TYPE name1,
END OF l_str.
* DATA(i_field_customizing) = VALUE t_string_tt( ( `KUNNR` ) ( `NAME1` ) ).
DATA(i_field_customizing) = VALUE t_string_tt( ).
DATA(li_temp) = COND #( WHEN i_field_customizing IS INITIAL
THEN CAST data( NEW t_integer_tt( ( 1 ) ( 2 ) ) )
ELSE CAST data( REF #( i_field_customizing ) ) ).
ASSIGN li_temp->* TO <fs_table>.
LOOP AT <fs_table> ASSIGNING FIELD-SYMBOL(<fs_temp>).
ASSIGN COMPONENT <fs_temp> OF STRUCTURE l_str TO FIELD-SYMBOL(<fs_field>).
ENDLOOP.
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
lcl_main=>main( ).