Best way to check if a line with non-initial field exists? - abap

Let's say I have a table quants and want to find out if any line exists, where the field lenum is not initial. The table is declared inline using a select statement, so I do not have a key available.
Because I don't have a key, the following solution does not work:
line_exists( VALUE #( FOR wa IN quants WHERE ( lenum IS NOT INITIAL ) ( wa ) ) )
Since I want to check for inequality, a table expression does not work:
line_exists( quants[ lenum NE '' ] )
The only solution that I have come up with so far is the following:
abap_true EQ REDUCE abap_bool( INIT bool = abap_false FOR quant IN quants WHERE ( lenum IS NOT INITIAL ) NEXT bool = abap_true )
Obviously there are "old fashioned" solutions, but is there any newer-style?
By "old fashioned" I mean solutions like this:
LOOP AT quants INTO DATA(wa).
IF wa-lenum IS INITIAL.
DATA(found) = abap_true.
ENDIF.
ENDLOOP.
IF found EQ abap_true.
...
ENDIF.

The only thin in "new fashion" would be SELECT, FROM #itab.
DATA(lv_exists) = abap_false.
SELECT SINGLE #abap_true FROM #lt_quant AS quant WHERE quant~lenum IS NOT INITIAL INTO #lv_exists.
See documentation link for performance impact (best case is handled like a table in the table buffer) and limitations (e.g. no string column).
The most performant and less restrictions would be this:
LOOP AT lt_quant TRANSPORTING NO FIELDS WHERE lenum IS NOT INITIAL.
EXIT.
ENDLOOP.
DATA(lv_exists) = xsdbool( sy-subrc = 0 ).

Related

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.

Can I check for initial or not equal values with line_exists?

ABAP 7.40 added the line_exists( ... ) predicate function to analyse internal tables. But is there any way I can check for the presence of a line where a particular column is initial or different from a target value?
For instance, how can I check for a line with an initial Material column like the third line in this table?
Document Country Material
9001287 US 198572111
9001296 FR 160023941
9001297 EG
9001299 DK 873001102
I could check for Danish entries with line_exists( lt_itab[ Country = 'DK' ] ) and line_exists( lt_itab[ Material = '' ] ) is valid but neither <> nor NE seem to be accepted. There also seems to be no way to check for lines where the country isn't 'FR' for instance?
If there's no way to do this with line_exists, what would be the most condensed alternative approach?
LOOP is one way to check, I don't know if there is anything better:
LOOP AT itab
TRANSPORTING NO FIELDS
WHERE country NE 'FR'.
EXIT.
ENDLOOP.
IF sy-subrc EQ 0.
" line exists
ELSE.
" line does not exist
ENDIF.
No, you cannot.
line_exists is simple predicate function which accepts only table expressions tab[ a = b ]. And, as we know, table expressions is simply a new syntax for READ TABLE, nothing more. All rules and constraints including allowed comparison type are applied to expressions as well.
Check H. Keller's blog for more details.
It's a little late. But now you can do the following:
xsdbool( line_exists( lt_itab[ Country = 'DK' ] ) ) = abap_false
A little bit later, here's another (shorter) way to do the same thing as in Andreas' answer:
IF NOT line_exists( lt_itab[ country = 'DK' ] ).
However, this tests whether there is no line equal to DK in the table. It does NOT test whether there is any line that is unequal to DK.
If you want the second thing, you have to resort to LOOP as József pointed out. Or you could compress it into one line like this:
IF lines( VALUE type( FOR x IN lt_itab WHERE ( country <> 'DK' ) ( x ) ) ) > 0.
Unfortunately, you cannot use VALUE #( ), so you have to put in the type of lt_itab.
If country is the primary key, another possibility is
IF lines( FILTER #( lt_itab WHERE country <> 'DK' ) ) > 0.
and if country is only the secondary key, you could do
IF lines( FILTER #( lt_itab USING KEY country WHERE country <> 'DK' ) ) > 0.

Change table row by index

How can I give a field a value with a specific index.
Or if the field is not initial I wanna change the value on this index
Example:
TYPES : BEGIN OF itab,
number1 type n,
endof itab.
lv_tabix = sy-tabix.
itab-number1 index lv-tabix = '1'.
Just use table expressions for this, if you are on the recent ABAP releases:
TRY.
itab[ lv-tabix ]-number1 = '1'.
CATCH cx_sy_itab_line_not_found INTO DATA(exc).
cl_demo_output=>display( exc->get_text( ) ).
ENDTRY.
You should read table by index and assign target line to filed symbol first:
READ TABLE {your itab} INDEX lv_tabix ASSIGNING FIELD-SYMBOL(<fs>).
<fs>-number1 = '1'.
Depending on your server version, seperate field symbol declaration may be needed:
FIELD-SYMBOLS <fs> TYPE {your table line type}
Check READ TABLE online help https://help.sap.com/doc/abapdocu_752_index_htm/7.52/en-US/abapread_table_outdesc.htm#!ABAP_ALTERNATIVE_2#2#
Check the help of the sentence MODIFY (itab):
https://help.sap.com/doc/abapdocu_750_index_htm/7.50/en-US/abapmodify_itab_single.htm
DATA: workarea TYPE your_type.
workarea-field = value.
"modifying for an index
MODIFY your_table INDEX your_index FROM workarea TRANSPORTING field.
"modifying for a condition
MODIFY your_table FROM workarea TRANSPORTING field WHERE your_condition.
Or you can use FIELD-SYMBOLS, as has been pointed before me by Anton.
This?
TYPES: begin OF ty_itab,
number1 TYPE n,
END OF ty_itab.
DATA: itab TYPE TABLE OF ty_itab,
wa_itab like line of itab,
lv_tabix TYPE sy-tabix.
APPEND wa_itab TO itab.
lv_tabix = sy-tabix.
READ TABLE itab INTO wa_itab INDEX lv_tabix.
wa_itab-number1 = '1'.
MODIFY itab FROM wa_itab INDEX lv_tabix.
BREAK-POINT.

Dynamic language output for structure/internal table

I have a selection screen with select-options where I want to enter several information about materials, for example: material number etc.
The user is also able to enter a language which the output should be in.
If the user chooses english the program shall display an internal table with material number, language, material name in english. If the user enters spanish, I want the output to be in spanish.
What do I have to do in order to define a dynamic structure / table which shows the respective columns dependent on the chosen language?
Thanks for your help
It's highly-dependent on the data structure you are going to show to user, but usually you don't need dynamic structure for this, but rather need to populate data dynamically, i.e. depending on current user language.
For example, material texts are stored in MAKT text table, where texts are stored along with language keys by which they are usually retrieved:
SELECT
a~matnr
a~werks
b~maktx FROM ekpo AS a
INNER JOIN makt AS b
ON b~matnr = a~matnr
AND b~spras = sy-langu
INTO CORRESPONDING FIELDS OF TABLE int_out
WHERE
a~matnr IN s_matnr and
a~werks IN s_werks.
Other descriptions in SAP are usually stored in text tables as well.
More about sy-langu and other system fields is here.
UPDATE: If you really want a dynamic structure with all the languages, see this sample:
DATA: lang TYPE SPRAS.
* language selection
SELECT-OPTIONS: s_lang FOR lang.
SELECT a~matnr, a~werks, b~maktx, b~spras UP TO 5000 ROWS
FROM ekpo AS a
JOIN makt AS b
ON b~matnr = a~matnr
INTO TABLE #DATA(int_out)
WHERE a~werks LIKE '3%'
AND a~matnr LIKE '1%'
AND b~spras IN #s_lang.
*finding unique languages
DATA lt_langs TYPE TABLE OF spras.
lt_langs = VALUE #( ( '' ) ).
LOOP AT int_out ASSIGNING FIELD-SYMBOL(<fs_out>)
GROUP BY ( lang = to_upper( val = <fs_out>-spras ) ) ASCENDING
WITHOUT MEMBERS
ASSIGNING FIELD-SYMBOL(<ls_lang>).
APPEND <ls_lang>-lang TO lt_langs.
ENDLOOP.
DATA :
ls_component TYPE cl_abap_structdescr=>component,
gt_components TYPE cl_abap_structdescr=>component_table.
*adding MATNR column
ls_component-name = 'MATNR'.
ls_component-type ?= cl_abap_datadescr=>describe_by_name( 'matnr' ).
APPEND ls_component TO gt_components.
*Creating dynamic structure with column for every lang
LOOP AT lt_langs ASSIGNING FIELD-SYMBOL(<fs_lang>).
CONDENSE <fs_lang>.
IF <fs_lang> IS NOT INITIAL.
ls_component-name = 'makt_' && <fs_lang>.
ls_component-type ?= cl_abap_datadescr=>describe_by_name( 'maktx' ).
APPEND ls_component TO gt_components.
ENDIF.
ENDLOOP.
* constructing dynamic structure
DATA: gr_struct_typ TYPE REF TO cl_abap_datadescr.
gr_struct_typ ?= cl_abap_structdescr=>create( p_components = gt_components ).
* constructing table from structure
DATA: gr_dyntable_typ TYPE REF TO cl_abap_tabledescr.
gr_dyntable_typ = cl_abap_tabledescr=>create( p_line_type = gr_struct_typ ).
DATA: gt_dyn_table TYPE REF TO data,
gw_dyn_line TYPE REF TO data.
FIELD-SYMBOLS: <gfs_line>,<gfs_line1>,<fs1>,
<gfs_dyn_table> TYPE STANDARD TABLE.
CREATE DATA: gt_dyn_table TYPE HANDLE gr_dyntable_typ,
gt_dyn_table TYPE HANDLE gr_dyntable_typ,
gw_dyn_line TYPE HANDLE gr_struct_typ.
ASSIGN gt_dyn_table->* TO <gfs_dyn_table>.
ASSIGN gw_dyn_line->* TO <gfs_line>.
LOOP AT int_out ASSIGNING <fs_out>.
* checking for duplicated
READ TABLE <gfs_dyn_table> ASSIGNING <gfs_line1> WITH KEY ('MATNR') = <fs_out>-matnr.
IF sy-subrc = 0.
CONTINUE.
ENDIF.
* assigning material number
LOOP AT gt_components ASSIGNING FIELD-SYMBOL(<fs_component>).
IF <fs_component>-name = 'MATNR'.
ASSIGN COMPONENT <fs_component>-name OF STRUCTURE <gfs_line> TO <fs1>.
IF <fs1> IS ASSIGNED.
<fs1> = <fs_out>-matnr.
UNASSIGN <fs1>.
ENDIF.
ENDIF.
* assigning languge-dependent names
READ TABLE int_out WITH KEY matnr = <fs_out>-matnr
spras = <fs_component>-name+5
ASSIGNING FIELD-SYMBOL(<fs_spras>).
IF sy-subrc = 0.
ASSIGN COMPONENT <fs_component>-name OF STRUCTURE <gfs_line> TO <fs1>.
IF <fs1> IS ASSIGNED.
<fs1> = <fs_spras>-maktx.
UNASSIGN <fs1>.
ENDIF.
ENDIF.
ENDLOOP.
APPEND <gfs_line> TO <gfs_dyn_table>.
CLEAR: <gfs_line>.
ENDLOOP.
DATA: l_lang TYPE spras VALUE 'E'.
* showing values in proper language depending on user input
LOOP AT <gfs_dyn_table> ASSIGNING <gfs_line>.
ASSIGN COMPONENT 'makt_' && l_lang OF STRUCTURE <gfs_line> TO <fs1>.
IF <fs1> IS ASSIGNED.
WRITE / <fs1>.
UNASSIGN <fs1>.
ENDIF.
ENDLOOP.

Disable Column in VA01

I have a requirement where I need to disable the full column in Sales Order Line item. Fields are VBAP-ARKTX and VBAP-KDMAT.
I've found the way to disable columns with data in them, but not the whole column.
I used USEREXIT_FIELD_MODIFICATION to achieve this using the following code;
IF sy-TCODE = 'VA02'.
IF screen-name = 'VBAP-KDMAT' .
screen-INPUT = 0.
modify screen.
ENDIF.
ENDIF.
Is there a way to disable the whole column?
Adjusting table control which contains items is the easiest and the most recommended way. It can be done for single user or for group of users.
Otherwise, try to create a screen variant in SHD0. It allows easily hide any column of any table and any field on the screen.
The specific problem I faced was how to disable two fields, but let standard mapped data to be displayed in them.
To cater this requirement I used the following;
Include: MV45AFZZ
User Exit Name: USEREXIT_FIELD_MODIFICATION
Enhancement Name: -Any name you want-
I created an Enhancement and wrote the following code;
"Specify the condition
IF VBAK-VKORG = '1234' AND ( sy-TCODE = 'VA02' OR sy-TCODE = 'VA01' ) AND ( screen-name = 'VBAP-KDMAT' OR screen-name = 'VBAP-ARKTX' ).
screen-input = 0."disable input
MODIFY SCREEN.
DATA: i_tab_mara TYPE TABLE OF MARA WITH HEADER LINE.
DATA: l_maktx TYPE MAKT-MAKTX.
DATA: WA_MARA LIKE LINE OF i_tab_mara.
DATA: i_tab_vbap TYPE TABLE OF VBAP WITH HEADER LINE.
DATA: wa_vbap LIKE LINE OF i_tab_vbap.
IF sy-TCODE = 'VA01' .
SELECT SINGLE * from MARA INTO WA_MARA WHERE MATNR eq VBAP-MATNR.
SELECT MAKTX FROM MAKT INTO l_maktx WHERE MATNR eq VBAP-MATNR.
ENDSELECT.
VBAP-KDMAT = WA_MARA-KDMAT.
VBAP-ARKTX = l_maktx.
MODIFY SCREEN.
ELSEIF sy-TCODE = 'VA02' .
SELECT SINGLE * FROM VBAP INTO WA_VBAP WHERE VBELN eq VBAK-VBELN AND POSNR eq VBAP-POSNR.
IF WA_VBAP-ARKTX eq ''." Check if the fileds are empty, otherwise old data is overwritten
SELECT MAKTX FROM MAKT INTO l_maktx WHERE MATNR eq VBAP-MATNR.
ENDSELECT.
VBAP-ARKTX = l_maktx.
MODIFY SCREEN.
ENDIF.
IF WA_VBAP-KDMAT eq ''." Check if the fileds are empty, otherwise old data is overwritten
SELECT SINGLE * from MARA INTO WA_MARA WHERE MATNR eq VBAP-MATNR.
VBAP-KDMAT = WA_MARA-KDMAT.
MODIFY SCREEN.
ENDIF.
ENDIF.
ENDIF.
There is one thing, that You can do in the dynpro-designer. There You can modify the sap-standard-dynpro as a dynpro-modification.
Nevertheless, this might be overwritten with the next release. Is this also an option for You ?