how to Filter on internal table using multiple fields - abap

My requirement is to filter internal table using multiple fields.
CONSTANTS:lc_star TYPE c VALUE '*'.
DATA: lit_x_all TYPE STANDARD TABLE OF ztt WITH NON-UNIQUE KEY extsystem ccode ekorg werks matkl,
lit_filter_e TYPE SORTED TABLE OF ztt-extsystem WITH NON-UNIQUE KEY table_line,
lit_filter_o TYPE SORTED TABLE OF ztt-ekorg WITH NON-UNIQUE KEY table_line,
lit_filter_c TYPE SORTED TABLE OF ztt-ccode WITH NON-UNIQUE KEY table_line,
lit_filter_w TYPE SORTED TABLE OF ztt-werks WITH NON-UNIQUE KEY table_line.
SELECT *
FROM ztt
WHERE a = #i_a
INTO TABLE #lit_x_all.
LOOP AT i_pit_input INTO DATA(lwa_input).
"filter to avoid select statement in loop
lit_filter_e = VALUE #( ( CONV #( lc_star ) ) ( lwa_input-extsystem ) ).
DATA(lit_final_e) = FILTER #( lit_approver_all IN lit_filter_e WHERE extsystem = table_line ).
lit_filter_o = VALUE #( ( CONV #( lc_star ) ) ( lwa_input-ekorg ) ).
DATA(lit_final_o) = FILTER #( lit_final_e IN lit_filter_o WHERE ekorg = table_line ).
lit_filter_c = VALUE #( ( CONV #( lc_star ) ) ( lwa_input-ccode ) ).
DATA(lit_final_c) = FILTER #( lit_final_o IN lit_filter_c WHERE ccode = table_line ).
lit_filter_w = VALUE #( ( CONV #( lc_star ) ) ( lwa_input-werks ) ).
DATA(lit_final_w) = FILTER #( lit_final_c IN lit_filter_w WHERE werks = table_line ).
ENDLOOP.
Currently I am using above code with filter for each field. Can we achieve same requirement with single filter instead of multiple filters?
Thanks
Phani

The documentation states:
Table filtering can also be performed using a table comprehension or a table reduction with an iteration expression for table iterations with FOR. The operator FILTER provides a shortened format for this special case and is more efficient to execute.
As in your case filtering with FILTER does not work as you're effectively ORing the 'star case' and 'filter value case', using the VALUE constructor to perform table comprehension is the better choice:
DATA(result) = VALUE #(
FOR entry IN entries
WHERE (
( a = '*' OR a = filter-a ) AND
( b = '*' OR b = filter-b )
"...
)
( entry )
).
This should also be by magnitutes faster as it avoids the creation of multiple intermediate internal tables.

Related

Concatenate using 'FOR' loop in VALUE operator with 'BASE' addition?

Is it possible to mix in a FOR with CONCATENATE along with BASE statement?
Normally, itab1 = VALUE #( BASE itab1 ( value1 ) ) will append line1 into itab1 without overwriting. Shouldn't it be the same when using FOR along with BASE?
This is my thought process, but I get the No component exists with the name "FOR" error:
itab1 =
VALUE #(
BASE itab1
( value1 && value2 )
( VALUE #(
FOR line in itab2
( line-fld1 && line-fld2 ) )
).
Shouldn't it be the same when using FOR along with BASE
Yes, the semantics is the same. You use BASE for preserving the rows of the LHS when adding rows of RHS, and line specification is the same:
itab1 = VALUE #( BASE itab1 FOR line in itab2 ( matnr = line-matnr
maktx = line-matnr && line-spras
spras = line-spras ) ).
However, there is a nuance you should remember here: you cannot put the same itab to the inline-declared LHS, to the BASE source and to the FOR loop, it will give you infinite loop and TSV_TNEW_PAGE_ALLOC_FAILED dump.
I have tried out what Sandra had suggested in the comments and it worked like a charm:
You may append the lines of an internal table only by using ( LINES OF
itab ), so in your case it should be ( LINES OF VALUE #( ... ) ) –
Sandra Rossi
itab1 =
VALUE #( BASE itab1 ( value1 && value2 )
( LINES OF VALUE #( FOR line in itab2 ( line-fld1 && line-fld2 ) ) ).

find duplicates in column and appending to internal table [duplicate]

We all know these excellent ABAP statements which allows finding unique values in one-liner:
it_unique = VALUE #( FOR GROUPS value OF <line> IN it_itab
GROUP BY <line>-field WITHOUT MEMBERS ( value ) ).
But what about extracting duplicates? Can one utilize GROUP BY syntax for that task or, maybe, table comprehensions are more useful here?
The only (though not very elegant) way I found is:
LOOP AT lt_marc ASSIGNING FIELD-SYMBOL(<fs_marc>) GROUP BY ( matnr = <fs_marc>-matnr
werks = <fs_marc>-werks )
ASSIGNING FIELD-SYMBOL(<group>).
members = VALUE #( FOR m IN GROUP <group> ( m ) ).
IF lines( members ) > 1.
"throw error
ENDIF.
ENDLOOP.
Is there more beautiful way of finding duplicates by arbitrary key?
So, I just put it as answer, as we with Florian weren't able to think out something better. If somebody is able to improve it, just do it.
TYPES tt_materials TYPE STANDARD TABLE OF marc WITH DEFAULT KEY.
DATA duplicates TYPE tt_materials.
LOOP AT materials INTO DATA(material)
GROUP BY ( id = material-matnr
status = material-pstat
size = GROUP SIZE )
ASCENDING REFERENCE INTO DATA(group_ref).
CHECK group_ref->*-size > 1.
duplicates = VALUE tt_materials( BASE duplicates FOR <status> IN GROUP group_ref ( <status> ) ).
ENDLOOP.
Given
TYPES: BEGIN OF key_row_type,
matnr TYPE matnr,
werks TYPE werks_d,
END OF key_row_type.
TYPES key_table_type TYPE
STANDARD TABLE OF key_row_type
WITH DEFAULT KEY.
TYPES: BEGIN OF group_row_type,
matnr TYPE matnr,
werks TYPE werks_d,
size TYPE i,
END OF group_row_type.
TYPES group_table_type TYPE
STANDARD TABLE OF group_row_type
WITH DEFAULT KEY.
TYPES tt_materials TYPE STANDARD TABLE OF marc WITH DEFAULT KEY.
DATA(materials) = VALUE tt_materials(
( matnr = '23' werks = 'US' maabc = 'B' )
( matnr = '42' werks = 'DE' maabc = 'A' )
( matnr = '42' werks = 'DE' maabc = 'B' ) ).
When
DATA(duplicates) =
VALUE key_table_type(
FOR key IN VALUE group_table_type(
FOR GROUPS group OF material IN materials
GROUP BY ( matnr = material-matnr
werks = material-werks
size = GROUP SIZE )
WITHOUT MEMBERS ( group ) )
WHERE ( size > 1 )
( matnr = key-matnr
werks = key-werks ) ).
Then
cl_abap_unit_assert=>assert_equals(
act = duplicates
exp = VALUE tt_materials( ( matnr = '42' werks = 'DE') ) ).
Readability of this solution is so bad that you should only ever use it in a method with a revealing name like collect_duplicate_keys.
Also note that the statement's length increases with a growing number of key fields, as the GROUP SIZE addition requires listing the key fields one by one as a list of simple types.
What about the classics? I'm not sure if they are deprecated or so, but my first think is about to create a table clone, DELETE ADJACENT-DUPLICATES on it and then just compare both lines( )...
I'll be eager to read new options.

List of BUKRS which the current user is allowed to see

Is there a way to get a list of all BUKRS which the current user is allowed to see?
I want to use this list as a filter in open sql. Imagine the result of the method I search stored the result in bk_list. Then I could use bk_list like this:
SELECT * FROM some_table WHERE bukrs IN bk_list
Another way to do it, based on the class CL_AUTH_OBJECTS_TO_SQL (officially supported, as of ABAP 7.50, as explained in the documentation of AUTHORITY-CHECK), here the program reads the flights from the read-authorized airline carriers :
DATA(authsql) = cl_auth_objects_to_sql=>create_for_open_sql( ).
authsql->add_authorization_object( EXPORTING
iv_authorization_object = 'S_CARRID'
it_activities = VALUE #( ( auth_field = 'ACTVT' value = '03' ) )
it_field_mapping = VALUE #(
( auth_field = 'CARRID'
view_field = VALUE #( table_ddic_name = 'SFLIGHT' field_name = 'CARRID' ) ) ) ).
DATA(where) = authsql->get_sql_condition( ).
SELECT * FROM sflight INTO TABLE #data(sflights) WHERE (where).
I am afraid you can do it one by one only. Roughly:
SELECT bukrs
INTO TABLE #DATA(lt_t001)
FROM t001
WHERE ... . "Selection critera, if necessary
LOOP AT lt_t001
ASSIGNING FIELD-SYMBOL(<ls_t001>).
DATA(lv_tabix) = sy-tabix.
AUTHORITY-CHECK OBJECT 'F_BKPF_BUK'
ID 'BUKRS' FIELD <ls_t001>-bukrs
ID 'ACTVT' FIELD '03'. "Here you need the proper activity (display '03' /change '02' / etc.)
IF sy-subrc <> 0. "Auth check failed
DELETE lt_t001 INDEX lv_tabix.
ENDIF.
ENDLOOP.
At the end lt_t001 contains only the company codes, for which the user has authorization.

Counting itab groups without a loop

I recently found out about the GROUP BY command in loops.
Now imagine following example:
I have an itab with a column categories. I want to know how many different categories there are.
Using the GROUP BY statement linked above I could count the number of times the loop is executed. Is there a simpler way without having to loop?
Here is a short example code wrapped in a report you can try on your system.
REPORT Z_GROUP_COUNT.
TYPES: BEGIN OF lty_st_for_reduce,
categories TYPE C LENGTH 4,
END OF lty_st_for_reduce.
DATA: lt_for_reduce TYPE STANDARD TABLE OF lty_st_for_reduce.
APPEND VALUE #( categories = 'ABAP' ) TO lt_for_reduce.
APPEND VALUE #( categories = 'OBJC' ) TO lt_for_reduce.
APPEND VALUE #( categories = 'ABAP' ) TO lt_for_reduce.
APPEND VALUE #( categories = 'ABAP' ) TO lt_for_reduce.
APPEND VALUE #( categories = 'OBJC' ) TO lt_for_reduce.
DATA(lv_categories_count) = REDUCE i( INIT count = 0
FOR GROUPS categories OF entry IN lt_for_reduce
GROUP BY ( categories = entry-categories )
NEXT count = count + 1 ).
" Will output `2`.
WRITE: lv_categories_count.

How to add new line to itab with VALUE expression

ABAP 7.40 brought us new syntax, I am still figuring it out.
I want to add a new line to the existing table lt_itab. I found a workaround by adding an empty line and figuring out the current length of the table for an update by index, but is there an easier way?
SELECT spfli~carrid, carrname, connid, cityfrom, cityto
FROM scarr
INNER JOIN spfli
ON scarr~carrid = spfli~carrid
WHERE scarr~carrid = #carrier
ORDER BY scarr~carrid
INTO TABLE #DATA(lt_itab).
"How can I simplify the following code part?"
DATA(lv_idx) = lines( lt_itab ).
APPEND INITIAL LINE TO lt_itab.
lt_itab[ lv_idx + 1 ] = VALUE #( carrid = 'UA'
carrname = 'United Airlines'
connid = 941
cityfrom = 'Frankfurt'
cityto = 'San Francisco' ).
It's all in the documentation:
lt_itab = VALUE #( BASE lt_itab ( carrid = ... ) ).
The index logic is pretty ugly, you can easily use the ASSIGNING addition to the APPEND command to get a field symbol to the newly added line. You can then use that field symbol to fill the table entry using the same VALUE construct you are using now.
Or you can do it in one statement:
APPEND VALUE #( ... ) TO lt_itab.