Conversion exception while working with constructor expressions - dynamic

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( ).

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

Looping through a dynamic internal table

I am trying to split internal table to smaller chunks.
In below example the internal table big_table is split into small tables.
My question is how do we get the actual data from lt_small_tables for further processing.
TYPES: BEGIN OF lty_line,
column1 TYPE i,
column2 TYPE c LENGTH 4,
END OF lty_line.
CONSTANTS: lc_test_data_amount TYPE i VALUE 44,
lc_split_at_amount TYPE i VALUE 7,
DATA: lt_big_table TYPE STANDARD TABLE OF lty_line,
lv_string TYPE string,
lt_small_tables TYPE STANDARD TABLE OF REF TO data,
lr_small_table TYPE REF TO data.
FIELD-SYMBOLS: <lg_target> TYPE STANDARD TABLE.
" Generate test data
DO lc_test_data_amount TIMES.
CALL FUNCTION 'GENERAL_GET_RANDOM_STRING'
EXPORTING number_chars = 4 " Specifies the number of generated chars
IMPORTING random_string = lv_string. " Generated string
APPEND VALUE #( column1 = sy-index
column2 = CONV #( lv_string ) ) TO lt_big_table.
ENDDO.
" Split
DATA(lo_descr) = CAST cl_abap_tabledescr(
cl_abap_typedescr=>describe_by_data( lt_big_table ) ).
LOOP AT lt_big_table ASSIGNING FIELD-SYMBOL(<ls_line>).
IF ( sy-tabix - 1 ) MOD lc_split_at_amount = 0.
CREATE DATA lr_small_table TYPE HANDLE lo_descr.
ASSERT lr_small_table IS BOUND.
APPEND lr_small_table TO lt_small_tables.
ASSIGN lr_small_table->* TO <lg_target>.
ASSERT <lg_target> IS ASSIGNED.
ENDIF.
APPEND <ls_line> TO <lg_target>.
ENDLOOP.
Thanks,
Kris
There are at least a couple of ways
Solution 1: cast internal table reference into a known type, so that you can directly access its fields.
FIELD-SYMBOLS: <fs_table> like lt_big_table.
LOOP AT lt_small_tables into lr_small_table.
ASSIGN lr_small_table->* TO <fs_table>.
ASSERT <fs_table> IS ASSIGNED.
LOOP AT <fs_table> into data(ls_line).
" do something with line
write: / ls_line-column1, ls_line-column2.
ENDLOOP.
ENDLOOP.
Solution 2: dynamic access to the tables fields
FIELD-SYMBOLS: <generic_fs_table> TYPE ANY TABLE.
LOOP AT lt_small_tables into lr_small_table.
ASSIGN lr_small_table->* TO <generic_fs_table>.
ASSERT sy-subrc = 0.
LOOP AT <generic_fs_table> ASSIGNING FIELD-SYMBOL(<generic_fs_line>).
" dinamically access to line fields
ASSIGN COMPONENT 'COLUMN1' of STRUCTURE <generic_fs_line> to FIELD-SYMBOL(<generic_fs_field1>).
ASSERT sy-subrc = 0.
ASSIGN COMPONENT 'COLUMN2' of STRUCTURE <generic_fs_line> to FIELD-SYMBOL(<generic_fs_field2>).
ASSERT sy-subrc = 0.
" do something with component2
write: / <generic_fs_field1>, <generic_fs_field2>.
ENDLOOP.
ENDLOOP.

Prevent CL_SALV_TABLE from removing leading zeros?

Please take a look on this following piece of code in which I put zeros in Test2 as value and two zeros in Test3 as value.
i used set_leading_zero but still leading zeros are removed.
CLASS lcl_main DEFINITION FINAL CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
main.
ENDCLASS.
CLASS lcl_main IMPLEMENTATION.
METHOD main.
TYPES: BEGIN OF l_tys_test,
name TYPE string,
value TYPE i,
END OF l_tys_test,
l_tyt_test TYPE STANDARD TABLE OF l_tys_test WITH EMPTY KEY.
DATA(lt_test) = VALUE l_tyt_test(
( name = `Test1` value = 1 )
( name = `Test2` value = 02 )
( name = `Test3` value = 003 )
).
cl_salv_table=>factory(
IMPORTING
r_salv_table = DATA(lo_salv_table)
CHANGING
t_table = lt_test
).
lo_salv_table->get_columns( )->get_column( 'VALUE' )->set_leading_zero( abap_true ).
lo_salv_table->display( ).
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
lcl_main=>main( ).
Agree with Jozsef and Sandra, there is no way you can do it with integer data type.
The only way how to implement this by means of ALV grid is to redeclare you VALUE field as numchar
TYPES: BEGIN OF
...
value TYPE n LENGTH 10,
...
END OF.
and utilize EDIT_MASK function, which is available both for WRITE statement and for ALV grid as well.
Put this instead of set_leading_zero( ) call in your method
lo_salv_table->get_columns( )->get_column( 'VALUE' )->set_edit_mask( '___________' ).
LENGTH specification in type definition is the number of leading zeroes to put.
You can use set_zero method to false.
DATA(lo_column) = lo_alv->get_columns( )->get_column( 'PERNR' ). lo_column->set_long_text( 'Staff No' ). lo_column->set_zero( abap_false ).

Checking against value in a STRING_TABLE in a WHERE clause

I have a procedure with the parameter IT_ATINN:
IMPORTING
REFERENCE(IT_ATINN) TYPE STRING_TABLE
IT_ATINN contains a list of characteristics.
I have the following code:
LOOP AT values_tab INTO DATA(value).
SELECT ( #value-INSTANCE ) AS CUOBJ
FROM IBSYMBOL
WHERE SYMBOL_ID = #value-SYMBOL_ID
AND ATINN ??? "<======== HERE ???
APPENDING TABLE #DATA(ibsymbol_tab).
ENDLOOP.
How can I check if ATINN (in the WHERE clause) is equal to any entry in IT_ATINN?
To achieve what you want (and I assume you want dynamic SELECT fields) you cannot use inline declarations here, both in LOOP and in SELECT:
The structure of the results set must be statically identifiable. The SELECT list and the FROM clause must be specified statically and host variables in the SELECT list must not be generic.
So either you use inline or use dynamics, not both.
Here is the snippet that illustrates Sandra good suggestion:
TYPES: BEGIN OF ty_value_tab,
instance TYPE char18,
symbol_id TYPE id,
END OF ty_value_tab.
DATA: it_atinn TYPE string_table.
DATA: rt_atinn TYPE RANGE OF atinn,
value TYPE ty_value_tab,
values_tab TYPE RANGE OF ty_value_tab,
ibsymbol_tab TYPE TABLE OF ibsymbol.
rt_atinn = VALUE #( FOR value_atinn IN it_atinn ( sign = 'I' option = 'EQ' low = value_atinn ) ).
APPEND VALUE ty_value_tab( instance = 'ATWRT' ) TO values_tab.
LOOP AT values_tab INTO value.
SELECT (value-instance)
FROM ibsymbol
WHERE symbol_id = #value-symbol_id
AND atinn IN #rt_atinn
APPENDING CORRESPONDING FIELDS OF TABLE #ibsymbol_tab.
ENDLOOP.
Overall, it makes no sense select ibsymbol in loop, 'cause it has only 8 fields, so you can easily collect all necessary fields from values_tab and pass them as dynamic fieldstring.
If you wanna use alias CUOBJ for your dynamic field you should add it like this:
LOOP AT values_tab INTO value.
DATA(aliased_value) = value-instance && ` AS cuobj `.
SELECT (aliased_value)
...
Remember, that your alias should exists among ibsymbol fields, otherwise in case of static ibsymbol_tab declaration this statement will throw a short dump.

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.