Dynamic access to internal table (or range)
Hello SCN,
So the other day I had the following requirement (I work on a SAP CRM 7.0 system): I wrote a new program in which I needed some data processing which was already coded in the subroutine of another – already existing – program. Since it concerned a pretty large piece of code, I decided not to simply copy-paste the logic but to call the subroutine from within my program like this:
PERFORM subroutine IN PROGRAM main_program CHANGING t_result.
Since the program in which I was calling the subroutine has a selection screen, and some of these parameters are used in the subroutine, I had to add an importing (USING) parameter to the subroutine which contained the values for these parameters. These values are partially supplied by the user in the selection screen of my program, and others are calculated in my program flow. So the above statement was corrected as follows:
PERFORM subroutine IN PROGRAM main_program
USING t_selscr_parameters
CHANGING t_result.
Now comes the tricky part 🙂 . The table T_SELSCR_PARAMETERS is a table with structure RSPARAMS (so basically the standard type for any selection screen, with components SELNAME, KIND, SIGN, OPTION, LOW and HIGH). Containing records with the exact names (SELNAME) of the corresponding selection screen parameter, and – of course – the value to be transferred to the selection screen parameter (e.g. SIGN=’I’, OPTION = ‘EQ’, LOW = ‘xxx’).
So I added some logic to the subroutine which we are calling: a loop over SELSCR_PARAMETERS to transfer the value of each table line into the corresponding parameter from our main program’s selection screen.
For a regular parameter, I knew I could work with a field symbol of type ‘any’, and simply assign the name of the parameter (LS_RSPARAM-SELNAME) to this field symbol – let’s name him <FS_ANY>. If the assignment works (which it should, because I named the parameter records in the SELSCR_PARAMETERS table exactly the same as the parameters in the selection screen), you can transfer the value in the selection screen parameter by using the following statement:
<FS_ANY> = LS_RSPARAM-LOW.
But.. next to the ‘regular’ parameters, there were also some ranges (SELECT-OPTIONS) which needed to be transferred into the selection screen. Ranges are in fact separate internal tables with header line
So you could use the same statement as for a regular parameter
ASSIGN ls_rsparam-selname TO <fs_any>.
But it would not be useful, since you need to append a structure of type RSPARAMS to your range (assigned to <FS_ANY>) and you can’t do that – because <FS_ANY> is not an internal table.
So, you might think, I simply create a new field-symbol <fs_anytab> TYPE ANY TABLE 🙂 . That way I can assign ls_rsparam-selname to <fs_anytab>, and append to that field-symbol.
True, syntactically this logic would not cause any problems, and your program would activate without errors. But once you step over the statement, you will get the following shortdump:
So below you can find how I solved this issue. I searched for answers in the forum discussions here on SCN, but couldn’t find it immediately. Perhaps it is out there somewhere (especially since this concept is widely used in R/3, not so much in CRM though) but I blogged about this nonetheless, hoping to save a fellow colleague some valuable time 😉 ;
DATA: ref(50) TYPE c,
dref TYPE REF TO data.
FIELD-SYMBOLS:
<fs_any> TYPE any,
<fs_any_1> TYPE any,
<fs_anytab> TYPE ANY TABLE.
LOOP AT i_selscr_parameters INTO ls_rsparam.
CASE ls_rsparam-kind.
WHEN ‘P’.
* This is a regular parameter
ASSIGN (ls_rsparam-selname) TO <fs_any>.
IF <fs_any> IS ASSIGNED.
<fs_any> = ls_rsparam-low.
UNASSIGN <fs_any>.
ENDIF.
WHEN ‘S’.
* This is a range. Now ranges are in fact tables with header line,
* and a row structure SIGN OPTION LOW HIGH.
CONCATENATE: ‘(‘ sy-repid ‘)’ ls_rsparam-selname ‘[]’ INTO ref.
CONDENSE ref NO-GAPS.
ASSIGN (ref) TO <fs_anytab>.
* So now we have the table (MAINPROGRAM)S_RANGE[] assigned to a
* field-symbol of type ANY TABLE without dumping 😉
IF <fs_anytab> IS ASSIGNED.
* We still need a structure which has the same line type as <fs_anytab>
CREATE DATA dref LIKE LINE OF <fs_anytab>.
* And now <fs_any> has our line type, we can start transferring the
* values to the different components of the structure
ASSIGN dref->* TO <fs_any>.
IF <fs_any> IS ASSIGNED.
ASSIGN COMPONENT ‘SIGN’ OF STRUCTURE <fs_any> TO <fs_any_1>.
IF <fs_any_1> IS ASSIGNED.
<fs_any_1> = ls_rsparam-sign.
UNASSIGN <fs_any_1>.
ENDIF.
ASSIGN COMPONENT ‘OPTION’ OF STRUCTURE <fs_any> TO <fs_any_1>.
IF <fs_any_1> IS ASSIGNED.
<fs_any_1> = ls_rsparam-option.
UNASSIGN <fs_any_1>.
ENDIF.
ASSIGN COMPONENT ‘LOW’ OF STRUCTURE <fs_any> TO <fs_any_1>.
IF <fs_any_1> IS ASSIGNED.
<fs_any_1> = ls_rsparam-low.
UNASSIGN <fs_any_1>.
ENDIF.
ASSIGN COMPONENT ‘HIGH’ OF STRUCTURE <fs_any> TO <fs_any_1>.
IF <fs_any_1> IS ASSIGNED.
<fs_any_1> = ls_rsparam-high.
UNASSIGN <fs_any_1>.
ENDIF.
ENDIF.
ENDIF.
ENDCASE.
ENDLOOP.
NOTE: The point of this blog is to elaborate on accessing internal table variables dynamically across programs, I certainly do not claim this was the best or most performant solution to my original requirement 😉 . Any comments on this blog however are highly appreciated!
Cheers,
Tom.
What I would have done would have taken the logic into a class (or a function module), and then called that from the old and new program. The interface would then be a lot easier to handle, and classes (and function modules) are designed to be re-used (i.e. you can reuse a program or form by external access, but it's a bit flaky!)
Hey Matthew,
First of all thanks for reading and moderating my first blog! 🙂 And to reply to your comment: I agree, my first suggestion was to copy the code from the subroutine into a function module and calling it from within both programs - I guess I could have mentioned that. This suggestion was however refused by the customer, since the main program is actually one of their largest, most complex developments, and this subroutine is the heart of that program. So I had to go and look for some other, less conventional solutions 😉 . That is also the reason why I added the final note, I definitely agree this is not the most obvious workaround, but I didn't immediately find any blog or discussion here which explains this concept, so I decided to share the/a solution when I found it 🙂
Kind Regards,
Tom.
It's perfectly sensible that a customer wouldn't want a major refactoring job with that kind of risk. Just shows - spend the money on good programmers in the first place, who have an eye to reusability and properly writing programs - and you'll save a huge amount of money in the long term.
Hi,
Again, I would avoid of such call to internal subroutine by any cost.
Anyway, I have few comments regarding the source code:
1. All these UNASSIGN statements seems little redundant.
You may check the result of ASSIGN statement according sy-subrc
(According my philosophy, you shouldn't check it at all, but this is subject to other discussion: Dumps are your friend. A different way of thinking.)
2. You may shorten the select-options related code with the following simple lines:
APPEND INITIAL LINE TO <fs_anytab> ASSIGNING <fs_any>.
MOVE-CORRESPONDING ls_rsparam TO <fs_any>.
3. The values in structure RSPARAMS (LOW/HIGH) are limited to 45 characters length, whereas actual SELECT-OPTIONS aren't.
Regards,
Shai
.
Hello Shai,
I really appreciate you taking the time to read and comment on my blog, your remarks are very valuable - especially the MOVE-CORRESPONDING statement is something I should have thought of myself 🙂 . It would have certainly reduced the size of my development, and increased readability. Now although I agree with the points made in the blog you referred to, these checks are client development standards I cannot avoid. As for the LOW/HIGH character lengths, I realized this while coding but all values in the select-options are (document, partner etc.) ID's never reaching the 45 character limits. Perhaps I could place a comment in my development as a warning for when the selection-screen should ever be extended with parameters crossing this limit.
If you don't mind me asking; would you have a different development approach for this requirement, keeping in mind that you can write additional code to the existing program and the subroutine, but cannot change anything about the code already there(except if they were little enhancements, like changing the i/o structure of the subroutine)? It's always valuable to get a different perspective, and could help me do this better next time 🙂
Kind regards,
Tom.
Hi Tom,
Well, I think that I would insist on encapsulating the existing logic/subroutine in external method (and calling it from the existing subroutine).
If that won't be possible by any means, I'll have to stick to your solution, I guess... 🙂
Regards,
Shai