2021 Dec 08 4:06 AM
Hi all,
I have a peculiar question. Can VALUE #( ) be used to populate multiple fields based on the same source? Before you point me to the documentation, let me clarify what I need. Basically, I want to have only one FOR loop which populates, at least, 2 tables: original records (for deletion) and modified records (for insertion). Using FOR is nice, but it doesn't allow, as it seems, use of a single work area to be assigned to multiple table components of the target structure...
types: begin of ty_db_updates,
headers_old type some_db_header_table_type,
headers_new type some_db_header_table_type,
end of ty_db_updates.
*Here I have 2 tables: kind of driver table (or filter) that drives
*the selection from the HEADERS and the ITEMS tables. For simplicity sake,
*let's limit the example to only 2 tables. Let's call the tables DRIVER and HEADERS,
*both having the same single-field key KEY.
*What i am trying to achieve here is avoiding multiple LOOPS.
data(updates) = value ty_db_updates( for <driver> in driver
for <header> in headers where ( key = <driver>-key )
( headers_old = value #( base headers_old value #( ( <header> ) ) ) "original header
headers_new = value #( base headers_new value #( ( some transformation of the original <header> ) ) )
) ).
At this point, i'm getting error:
No component exists with the name "FOR". "FOR".
I know that I can use traditional LOOPs here, but a LOOP can't be passed as a parameter. LOOP is easy:
types: begin of ty_db_updates,
headers_old type some_db_header_table_type,
headers_new type some_db_header_table_type,
end of ty_db_updates.
data(updates) = value ty_db_updates( ).
loop at driver assigning field-symbol(<driver>).
loop at headers assigning field-symbol(<header>).
"Insert original header into the OLD table
insert <header> into udpates-headers_old.
"Here goes some transformation of the header record
"Now we insert modified header record into the NEW table
insert <header> into the updates-headers_new.
endloop.
endloop.
So, is something like the above at all possible using FOR? Or am I looking in vain? Maybe, horst.keller can clarify? I couldn't find any relevant examples - the terms FOR, LET, table comprehensions are too generic...
Thanks.
P.S. LOOP can be wrapped into a functional method though, so not a major show stopper. However, understanding the mechanics of nested tables assignment in constructor expressions will be beneficial. It would be nice to be able to replace that LOOP with a constructor expression...
2021 Dec 10 6:30 AM
It is not feasible to process multiple tables (e.g., with parent-child relationships) and populate them in the fields of the same target structure using REDUCE. It's impossible to do so with VALUE either as the latter is not intended for such purpose. Nested LOOPs should be used wrapped in a method call.
2021 Dec 08 11:22 AM
You can't use VALUE ... ( FOR ... if the target is a structure.
That would be nice if you could provide a minimal reproducible example that we can try on our system, and we'll tell you why you have the error "No component exists with the name "FOR".".
Probably you must use REDUCE.
2021 Dec 09 5:36 AM
Thanks sandra.rossi . This is a more-or-less correct representation of the logic that needs to be implemented. Please disregard potential functional issues with incomplete key access - this is not something i'm responsible for. However, the basic idea should be true for any case that deals with DB updates where one of the key fields must be changed - in this case, old records must be deleted and new records must be inserted.
report zztest1 message-id zfi_vendor_msg.
types: ty_pp_headers type standard table of reguh with empty key,
ty_pp_items type standard table of regup with empty key.
types: begin of ty_response,
supplier_code type reguh-lifnr,
account_code type reguh-zbnkl,
account_number type reguh-zbnkn,
end of ty_response,
ty_responses type standard table of ty_response with empty key.
types: begin of ty_updates,
headers_old type ty_pp_headers,
headers_new type ty_pp_headers,
items_old type ty_pp_items,
items_new type ty_pp_items,
end of ty_updates.
parameters: p_date type reguh-laufd,
p_id type reguh-laufi,
p_prponl type reguh-xvorl.
select * from reguh
where laufd = @p_date
and laufi = @p_id
and xvorl = @p_prponl
and vblnr is not initial
and zbnks = 'AU' "you can remove it or change to your country
into table @data(pp_headers).
select * from regup
for all entries in @pp_headers
where laufd = @pp_headers-laufd
and laufi = @pp_headers-laufi
and xvorl = @pp_headers-xvorl
and lifnr = @pp_headers-lifnr
into table @data(pp_items).
*Here data is prepared for sending to a vendor verification platform.
*For simplicity sake, let's make it look like none of the vendors was
*successfully verified.
*Generating a mock-up response from the vendor verification platform.
data(responses) = corresponding ty_responses( pp_headers mapping supplier_code = lifnr
account_code = zbnkl
account_number = zbnkn ).
*To be more life-like, this example needs some of the responses removed
*which means that one needs a multi-vendor payment proposal for testing
delete responses index 1.
check lines( responses ) > 0.
*LOOP version of what is needed
data updates_loop type ty_updates.
loop at responses assigning field-symbol(<resp>).
loop at pp_headers assigning field-symbol(<header>)
where lifnr = <resp>-supplier_code
and zbnkl = <resp>-account_code
and zbnkn = <resp>-account_number.
"Old header - added unchanged for further deletion from the DB
updates_loop-headers_old = value #( base updates_loop-headers_old ( <header> ) ).
"New header should not rever to a valid document
updates_loop-headers_new = value #( base updates_loop-headers_new
( value #( base <header> vblnr = value #( ) ) ) ).
loop at pp_items assigning field-symbol(<item>)
where lifnr = <header>-lifnr
and vblnr = <header>-vblnr
and zlspr is initial.
"Old item - added unchanged for further deletion from the DB
updates_loop-items_old = value #( base updates_loop-items_old ( <item> ) ).
"New item should be blocked for payment
updates_loop-items_new = value #( base updates_loop-items_new
( value #( base <item> vblnr = value #( )
zlspr = 'E' ) ) ).
endloop.
endloop.
endloop.
Can the same be achieved in the FOR version? Preparing this example I started seeing why VALUE (FOR) can't be used in this case... But can REDUCE be used in this case? That is, to populate 4 table fields of a structure?
2021 Dec 09 6:03 AM
I managed to process headers with REDUCE, but I don't understand yet how to process items in the same REDUCE statement:
data(updates_reduce) = reduce ty_updates( init upd type ty_updates
for <resp1> in responses
for <header1> in pp_headers
where ( lifnr = <resp1>-supplier_code
and zbnkl = <resp1>-account_code
and zbnkn = <resp1>-account_number )
next upd-headers_old = value #( base upd-headers_old ( <header1> ) )
upd-headers_new = value #( base upd-headers_new
( value #( base <header1> vblnr = value #( ) ) ) ) ).
Complexity here lies in the header-item relationships, that is, I need a kind of another FOR loop after the NEXT clause...
2021 Dec 09 10:02 AM
I think you are over-engineering here. It's good to go to constructor expressions only when it simplifies the reading. In your case, you could keep your existing code with LOOP AT, but just refactor/extract the method.
data(updates_reduce) = extract( responses = responses headers = headers ).
That makes it as clear as a constructor expression.
Of course, you could still use REDUCE, but it's much less legible (and we should also optimize because there are 2 loops for items that we should merge into 1 loop):
data(updates_reduce) = reduce ty_updates( init upd type ty_updates
for <resp1> in responses
for <header1> in pp_headers
where ( lifnr = <resp1>-supplier_code
and zbnkl = <resp1>-account_code
and zbnkn = <resp1>-account_number )
next upd-headers_old = value #( base upd-headers_old ( <header1> ) )
upd-headers_new = value #( base upd-headers_new
( value #( base <header1> vblnr = value #( ) ) ) )
upd-items_old = VALUE #(
BASE upd-items_old
FOR <item1> IN pp_items
WHERE ( lifnr = <header>-lifnr
AND vblnr = <header>-vblnr
AND zlspr IS INITIAL )
( <item1> ) )
upd-items_new = VALUE #(
BASE upd-items_new
FOR <item1> IN pp_items
WHERE ( lifnr = <header>-lifnr
AND vblnr = <header>-vblnr
AND zlspr IS INITIAL )
( VALUE #( BASE <item1> vblnr = VALUE #( )
zlspr = 'E' ) ) ) ).
2021 Dec 10 6:25 AM
I agree. While it may work with REDUCE for 1 table for which we are generating DB entries (e.g., for INSERT, UPDATE and DELETE as is usually done in SAP FMs), we are hitting an dramatical increase in complexity as soon as we introduce a dependent table. Even worse, we may have several top-level tables, that is, tables that have 1:1 relationships with the header, and their corresponding dependent tables up to a few levels deep... It becomes a nightmare. Not impossible, but not worth it either.
Well, thank you for answering my question. There still are limitations in ABAP... (:
2021 Dec 10 6:30 AM
It is not feasible to process multiple tables (e.g., with parent-child relationships) and populate them in the fields of the same target structure using REDUCE. It's impossible to do so with VALUE either as the latter is not intended for such purpose. Nested LOOPs should be used wrapped in a method call.
2021 Dec 10 8:42 AM
Why do you say "not feasible"? It's feasible, but you risk the constructor expression to not be legible, and the goal of constructor expression is to bring legibility to the code.
2021 Dec 10 12:18 PM
"Feasible" also means "practical", that's why I distinguished between non-feasibility of REDUCE vs. impossibility of VALUE. Besides, I explicitly mentioned that complexity grows tremendously with introduction of nested/linked tables. That's why "not feasible" - that is, hard to implement, difficult to understand, nightmare to support and virtually impossible to extend...
2021 Dec 10 10:24 AM
I wouldn't say like that. People think they must use "new abap" (constructor expressions) to replace all "old abap", and they are completely wrong in the "must use new abap". Constructor expressions are useful for what they have been designed, and developers should learn and understand that.
This is a simplified expression (but still less legible than original LOOP AT code):
TYPES: BEGIN OF ty_updates,
BEGIN OF headers,
old TYPE ty_pp_headers,
new TYPE ty_pp_headers,
END OF headers,
BEGIN OF items,
old TYPE ty_pp_items,
new TYPE ty_pp_items,
END OF items,
END OF ty_updates.
...
DATA(updates_reduce) = REDUCE ty_updates(
INIT upd TYPE ty_updates
FOR <resp1> IN responses
FOR <header1> IN pp_headers
WHERE ( lifnr = <resp1>-supplier_code
AND zbnkl = <resp1>-account_code
AND zbnkn = <resp1>-account_number )
NEXT upd-headers = VALUE #(
old = VALUE #( BASE upd-headers-old
( <header1> ) )
new = VALUE #( BASE upd-headers-new
( VALUE #( BASE <header1>
vblnr = VALUE #( ) ) ) ) )
upd-items = REDUCE #(
INIT it TYPE ty_updates-items
FOR <item1> IN pp_items
WHERE ( lifnr = <header>-lifnr
AND vblnr = <header>-vblnr
AND zlspr IS INITIAL )
NEXT it-old = VALUE #( BASE it-new
( <item1> ) )
it-new = VALUE #( BASE it-new
( VALUE #( BASE <item1>
vblnr = VALUE #( )
zlspr = 'E' ) ) ) ) ).
2021 Dec 10 12:57 PM
Well, I thought of such a solution, but that's not practical. Not that I have such a requirement, but what if a 3rd table must be introduced? I mean, something like subitems? Wrapper method with a single return parameter is far more convenient in this case. We can add new or remove old fields from the TY_UPDATES type, but it's not that easy with REDUCE. Besides, if we need to do some calculations in the nested table to update the parent table, this solution falls apart. (Actually, I do have to calculate something at the items level to update certain values at the header level as this was done in the original.)
As for the urge to use new syntax per se, this is not the case. I asked a question to better understand the constructor expressions, which I do now. I see that they don't support multiple layers of LET/FOR to evaluate helper variables first and then assign them to the target structure fields. I thought of constructor expressions as of a functional version of the LOOP statement, but they are not exactly that. This is the design constraint I have to keep in mind choosing between "good old LOOPs" and CEs.
By the way, new syntax should be used as it brings optimisations (; For example, LOOPS with secondary keys. That's what I learned working on the problem in this post.
Anyway, thanks a lot - I learned much during these 2 days.
2021 Dec 10 1:06 PM
Speaking of limitations... it would be great to limit the scope of work areas/field symbols to their respective constructor expressions... it's annoying when I can't declare, say, <header> in several consecutive CEs... 😞
2021 Dec 10 1:31 PM