Best practice for Table Expressions (NW 7.4) - abap

An official SAP presentation discussing new ABAP programming features in NetWeaver 7.4 (CD261) makes a big deal about table expressions, replacing the old syntax to read from an internal table:
READ TABLE lt_aufk INTO ls_aufk WITH KEY aedat = sy-datum.
lv_order = ls_aufk-aufnr.
with a single-line lv_order = lt_aufk[ aedat = sy-datum ]-aufnr.
However, it fails to mention that if a table expression fails to find a line it will raise exception CX_SY_ITAB_LINE_NOT_FOUND, so really that notation should actually be:
TRY.
lv_order = lt_aufk[ aedat = sy-datum ]-aufnr.
CATCH cx_sy_itab_line_not_found.
ENDTRY.
Which is both longer and makes any simple read logic look incredibly complex and hard to read. Since every individual read might need to fail or succeed individually this quickly balloons out of proportion:
TRY.
wa1 = lt_itab[ col1 = ... ].
CATCH cx_sy_itab_line_not_found.
ENDTRY.
TRY.
wa2 = lt_itab[ col2 = ... ].
CATCH cx_sy_itab_line_not_found.
ENDTRY.
TRY.
wa3 = lt_itab[ col3 = ... ].
CATCH cx_sy_itab_line_not_found.
ENDTRY.
TRY.
wa4 = lt_itab[ col4 = ... ].
CATCH cx_sy_itab_line_not_found.
ENDTRY.
TRY.
wa5 = lt_itab[ col5 = ... ].
CATCH cx_sy_itab_line_not_found.
ENDTRY.
Is there any way to improve the readability of these table expressions? Am I using them improperly?

I was typing a comment but it's getting to long...
Best practice
This is debatable I guess but my 2 cents: Sometimes ignoring an exception is OK, for instance if you just want to, in your case, display an empty cell. Technically, in the old scenario you should also be catching SUBRCs before moving into structures too. You have an additional issue - depending on your table definitions, I can't see those - that aedat might not be a primary key in which case you might be trying to cram another table type into a work area. The thing that matters most in the end is whether or not your program crashes, and whether or not you're displaying meaningful data.
As for design principles, over the whole I think I'd rather put some effort into the DRY principle if you do a lot of this in your program.
Having said that, the documentation mentions:
About the exception:
Source
There are ways around it.
If the specified row is not found, a handleable expression of the
class CX_SY_ITAB_LINE_NOT_FOUND is raised in all operand positions,
except when
a default value is specified in the definition of the type of the result,
a table expression is used in the statement ASSIGN, where sy-subrc is set to the value 4,
when used in the predicate function line_exists, where the logical value "false" is returned,
when used in the table function line_index, where the value 0 is returned.
Default values for table expressions
Source.
Neat if you're on SP08 or higher already:
Table expressions itab[ ...] cannot support sy-subrc. Up to now, an exception was raised anytime if a table line specified in the square brackets could not be found. Not everybody liked this behavior.
As a workaround, you can place a table expression inside a VALUE or REF expression, that contains a OPTIONAL or DEFAULT addition. If a line is not found, the OPTIONAL addition returns an initiial line while the DEFAULT addition returns a given value, that can be specified as an expression, especially another table expression.
TYPES:
BEGIN OF line,
id TYPE i,
value TYPE string,
END OF line,
itab TYPE SORTED TABLE OF line WITH UNIQUE KEY id.
DATA(def) = VALUE line( id = 0 value = `not found` ).
...
DATA(result) = VALUE #( itab[ id = ... ] DEFAULT def ).

You can use like below which is more similar to the read table syntax. Otherwise you need to use TRY CATCH for each expression if there is a probability the search fails.
ASSIGN lt_aufk[ aedat = lv_dat1 ] TO FIELD-SYMBOL(<ls_aufk>).
IF sy-subrc EQ 0.
lv_order1 = <ls_aufk>-aufnr.
ENDIF.
ASSIGN lt_aufk[ aedat = lv_dat2 ] TO <ls_aufk>.
IF sy-subrc EQ 0.
lv_order2 = <ls_aufk>-aufnr.
ENDIF.
...

I think you should enclose all your read table in one try catch

Related

Changing IDoc segment SDATA via field-symbols?

My scenario is that I am getting an IDOC segment's data into a field symbol and change some fields based on some validations.
My code:
READ TABLE idoc_data ASSIGNING FIELD-SYMBOL(<idocdata>) with key = 'E1EDK01'
IF sy-subrc = 0.
    lcl_struc ?= cl_abap_typedescr=>describe_by_name( 'E1EDK01' ).
    CREATE DATA dref TYPE HANDLE lcl_struc.
    ASSIGN dref->* TO FIELD-SYMBOL(<sdata>).
    IF <sdata> IS ASSIGNED.
      <sdata> = <idocdata>-sdata.
....
<idocdata>-sdata = <sdata>.
    ENDIF.
ENDIF.
Though the above snippet works fine, the continuity of field symbols is broken and now I have to pass back the changed data. How do I use ASSIGN and let the field symbols take care of the changes rather than an explicit statement?
Something similar to below snippet though this won't work since <IDOC_DATA>-SDATA and <SDATA> aren't compatible.
READ TABLE idoc_data ASSIGNING FIELD-SYMBOL(<idocdata>) with key = 'E1EDK01'
IF sy-subrc = 0.
FIELD-SYMBOLS: <sdata> TYPE E1EDK01.
ASSIGN <idocdata>-sdata TO <sdata>.
....
ENDIF.
My expectation is that when I change the data in <SDATA>-FIELD1, I want the changes to flow into <IDOCDATA>-SDATA without using <idocdata>-sdata = <sdata>.
As #Sandra mentioned above, the incompatibility of field-symbols can be resolved by using CASTING while assigning them. This would make the second snippet work.
...
IF sy-subrc = 0.
FIELD-SYMBOLS: <sdata> TYPE E1EDK01.
ASSIGN <idocdata>-sdata TO <sdata> CASTING.
...
ENDIF.

Use variable in offset and length in table expression component?

I am trying to see if there is a way to do the following
IF line_exists( company_accounts2[ saknr+0(2) = wa_company_accounts-saknr+0(wa_account_levels-num_of_digits) ] ).
ENDIF.
But the number 2 in saknr+0(2) with a parameter and specifically with the one that exists in the other side of equality (wa_account_levels-num_of_digits). Is there any way to do it with another way? Because if I replace the 2 with the wa_account_levels-num_of_digits I am getting the error "The length specification "WA_ACCOUNT_LEVELS-NUM_OF_DIGITS" is not numeric."
Thanks in advance
PS. What is not working and this is what I am asking below the above code is the following code:
IF line_exists( tab[ matnr+0(ls_mara-num_of_digits) = ls_mara-matnr+0(ls_mara-num_of_digits) ] ).
THIS CODE IS NOT WORKING.
Pass the left side of the equality as a text variable between parentheses, which contains the name of the variable and its offset:
DATA(lv_text) = |saknr+({ wa_account_levels-num_of_digits })|.
IF line_exists( company_accounts2[ (lv_text) = wa_company_accounts-saknr+0(wa_account_levels-num_of_digits) ] ).
CONTINUE.
ELSE.
"make the APPEND
ENDIF.
It works perfectly, for what you wanna achieve your wa_account_levels-num_of_digits should have primitive type i (INT1, INT2, INT4, INT8 in database).
Here is the working MARA sample
SELECT * UP TO 5 ROWS
FROM mara
INTO TABLE #DATA(tab).
READ TABLE tab INTO DATA(ls_mara) INDEX 1.
IF line_exists( tab[ matnr+0(2) = ls_mara-matnr+0(ls_mara-stfak) ] ).
ENDIF.
UPDATE: dynamic specification of the table components for read access is not possible:
If the data type of the components is character-like and flat, an offset/length +off(len) can be appended to the name of the component (as in substring access) to access subareas of the component. Only directly specified numbers or constants can be specified for off and len.

Lossless assignment between Field-Symbols

I'm currently trying to perform a dynamic lossless assignment in an ABAP 7.0v SP26 environment.
Background:
I want to read in a csv file and move it into an internal structure without any data losses. Therefore, I declared the field-symbols:
<lfs_field> TYPE any which represents a structure component
<lfs_element> TYPE string which holds a csv value
Approach:
My current "solution" is this (lo_field is an element description of <lfs_field>):
IF STRLEN( <lfs_element> ) > lo_field->output_length.
RAISE EXCEPTION TYPE cx_sy_conversion_data_loss.
ENDIF.
I don't know how precisely it works, but seems to catch the most obvious cases.
Attempts:
MOVE EXACT <lfs_field> TO <lfs_element>.
...gives me...
Unable to interpret "EXACT". Possible causes: Incorrect spelling or comma error
...while...
COMPUTE EXACT <lfs_field> = <lfs_element>.
...results in...
Incorrect statement: "=" missing .
As the ABAP version is too old I also cannot use EXACT #( ... )
Example:
In this case I'm using normal variables. Lets just pretend they are field-symbols:
DATA: lw_element TYPE string VALUE '10121212212.1256',
lw_field TYPE p DECIMALS 2.
lw_field = lw_element.
* lw_field now contains 10121212212.13 without any notice about the precision loss
So, how would I do a perfect valid lossless assignment with field-symbols?
Don't see an easy way around that. Guess that's why they introduced MOVE EXACT in the first place.
Note that output_length is not a clean solution. For example, string always has output_length 0, but will of course be able to hold a CHAR3 with output_length 3.
Three ideas how you could go about your question:
Parse and compare types. Parse the source field to detect format and length, e.g. "character-like", "60 places". Then get an element descriptor for the target field and check whether the source fits into the target. Don't think it makes sense to start collecting the possibly large CASEs for this here. If you have access to a newer ABAP, you could try generating a large test data set there and use it to reverse-engineer the compatibility rules from MOVE EXACT.
Back-and-forth conversion. Move the value from source to target and back and see whether it changes. If it changes, the fields aren't compatible. This is unprecise, as some formats will change although the values remain the same; for example, -42 could change to 42-, although this is the same in ABAP.
To-longer conversion. Move the field from source to target. Then construct a slightly longer version of target, and move source also there. If the two targets are identical, the fields are compatible. This fails at the boundaries, i.e. if it's not possible to construct a slightly-longer version, e.g. because the maximum number of decimal places of a P field is reached.
DATA target TYPE char3.
DATA source TYPE string VALUE `123.5`.
DATA(lo_target) = CAST cl_abap_elemdescr( cl_abap_elemdescr=>describe_by_data( target ) ).
DATA(lo_longer) = cl_abap_elemdescr=>get_by_kind(
p_type_kind = lo_target->type_kind
p_length = lo_target->length + 1
p_decimals = lo_target->decimals + 1 ).
DATA lv_longer TYPE REF TO data.
CREATE DATA lv_longer TYPE HANDLE lo_longer.
ASSIGN lv_longer->* TO FIELD-SYMBOL(<longer>).
<longer> = source.
target = source.
IF <longer> = target.
WRITE `Fits`.
ELSE.
WRITE `Doesn't fit, ` && target && ` is different from ` && <longer>.
ENDIF.

Ternary operator (alternatives)

Is there a ternary or conditional operator available in the ABAP syntax? I haven't found one so am assuming that the answer is no but is there then an alternative that I can use to clear up the common "dumb" IF statements that I routinely use?
For example, consider a method that logs a message with optional message parameters. To decide between using the imported parameter or the default I have to check the value like so:
IF iv_class IS INITIAL.
lv_message_class = 'DEFAULT'.
ELSE.
lv_message_class = iv_class.
ENDIF.
IF iv_number IS INITIAL.
lv_message_number = '000'.
ELSE.
lv_message_number = iv_number.
ENDIF.
IF iv_type IS INITIAL.
lv_message_type = 'E'.
ELSE.
lv_message_type = iv_type.
ENDIF.
A ternary operator would reduce each of these five-line statements to a single line as seen in the below code block. It could even make the use of a temporary variable unnecessary when the operator is used in-line.
lv_message_class = iv_class IS INITIAL ? 'DEFAULT' : iv_class.
lv_message_number = iv_number IS INITIAL ? '000' : iv_number .
lv_message_type = iv_type IS INITIAL ? 'E' : iv_type .
Is there a way to approximate this kind of programming style in ABAP or am I stuck with the clutter?
Release 7.40 brings a whole bunch of ABAP improvements which I'm finding heaps interesting. The ternary style declaration (at least something that resembles it) is one of them
Syntax:
COND dtype|#( WHEN log_exp1 THEN result1
[ WHEN log_exp2 THEN result2 ]
...
[ ELSE resultn ] ) ...
Example data declaration of a variable called 'bool' and a conditional value assignment in one line. Old skool ABAP this will take like 10 lines.
DATA(bool) = COND #( WHEN i * i > number THEN abap_true ELSE abap_false ).
More info: http://scn.sap.com/community/abap/blog/2013/07/22/abap-news-for-release-740
No, in ABAP there is no operator similar to the construct a ? b : c known from other languages. In your concrete example, however, you could declare default values for your method parameters iv_class etc. in the method's signature.
While declaring the variables you can set the default value or explicitly do the same as below.
lv_message_class = 'DEFAULT'.
lv_message_number = '000'.
lv_message_type = 'E'.
IF iv_class IS NOT INITIAL.
lv_message_class = iv_class.
ENDIF.
IF iv_number IS NOT INITIAL.
lv_message_number = iv_number.
ENDIF.
IF iv_type IS NOT INITIAL.
lv_message_type = iv_type.
ENDIF.

How to use LOOP AT itab INTO <fieldsymbol>

As I rarely loop into a field symbol, I often forget to use ASSIGNING instead of INTO which will promptly cause an abend. Is there a valid use of INTO with <fieldsymbol> or is this something that the syntax checker really ought to catch?
LOOP...INTO is perfectly valid but it will work differently. LOOP...INTO transports the values to the structure provided but ASSIGNING assigns the field symbol to the actual table rows.
The only difference is if you are going to change the table contents. See the following:
* Changes all entries in the CARRID column of lt_flights to 50.
LOOP AT lt_flights ASSIGNING <flight>.
<flight>-carrid = 50.
ENDLOOP.
* Does not change the entries in lt_flights (MODIFY...FROM would be required).
ASSIGN <flight> TO ls_flight.
LOOP AT lt_flights INTO <flight>.
<flight>-carrid = 50.
ENDLOOP.
LOOP...INTO with a field symbol would be useless unless you had some kind of dynamic programming requirement.
It is valid when <fieldsymbol> was previously assigned to a structure which has the type of the lines of the table you loop over.
It is a perfectly valid statement:
APPEND INITIAL LINE TO lt_foo ASSIGNING <ls_foo>.
READ TABLE lt_bar INTO <ls_foo> INDEX 1.
A field symbol just takes the place of a variable - at almost any point - so the syntax check can't flag this as invalid. It might issue a warning, though...