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.
Related
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.
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.
I have the following code snippet, that I would like to write in functional style :
data(lt_es) = me->prepare_process_part_ztoa1( ).
APPEND LINES OF me->prepare_process_part_protocol( ) to lt_es.
How to rewrite the code above in new ABAP 7.5?
Use the LINES OF construct (available since ABAP 7.40 SP 8).
For instance, it could be something like this:
lt_es = VALUE #( BASE me->prepare_process_part_ztoa1( )
( LINES OF me->prepare_process_part_protocol( ) ) ).
Whether it is better/simplier than the original, that's another question :)
It can be also done without BASE. However one must specify the type explicitly (usage of # ends with a syntax error).
REPORT ZZZ.
DATA: lt_t1 TYPE string_table,
lt_t2 TYPE string_table.
DATA(lt_t3) = VALUE string_table( ( LINES OF lt_t1 ) ( LINES OF lt_t2 ) ).
Would be interesting to know if this is maybe more performant than the usage of BASE if used in a loop for example.
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
I have a deep structure that I would like to display as a tree with the values of each field (sort of like the hierarchical display of a structure you can do in SE11, but with values).
Is there a class or function that does this for you? I really don't want to have to go reinvent the wheel.
Well, I would say it is faster to do DIY then to search for something generic enough to help you. You can try following coding as basis.
It does the plain recursion through the variable (be it table or structure) and it prints the fields found at bottom...
*&---------------------------------------------------------------------*
*& Form print_structure
*&---------------------------------------------------------------------*
form print_structure using im_data.
data: lr_typeref type ref to cl_abap_typedescr,
lf_ddic_in type fieldname,
lt_dfies type ddfields,
lf_string type c length 200.
field-symbols: <lt_table> type any table,
<ls_table> type any,
<lf_field> type any,
<ls_dfies> like line of lt_dfies.
lr_typeref = cl_abap_typedescr=>describe_by_data( im_data ).
case lr_typeref->type_kind.
when cl_abap_typedescr=>typekind_table. " internal table
assign im_data to <lt_table>.
loop at <lt_table> assigning <ls_table>.
perform print_structure using <ls_table>.
endloop.
when cl_abap_typedescr=>typekind_struct1 or
cl_abap_typedescr=>typekind_struct2. " deep/flat structure
lf_ddic_in = lr_typeref->get_relative_name( ).
call function 'DDIF_FIELDINFO_GET'
exporting
tabname = lf_ddic_in
all_types = 'X'
tables
dfies_tab = lt_dfies
exceptions
not_found = 1
others = 0.
check sy-subrc eq 0.
loop at lt_dfies assigning <ls_dfies>.
assign component <ls_dfies>-fieldname of structure im_data to <lf_field>.
perform print_structure using <lf_field>.
endloop.
when others. " any field
write im_data to lf_string.
write: / lf_string.
endcase.
endform. "print_structure
Would an ALV Tree work? CL_SALV_TREE
I've never seen such functionality and think there is no one in standard. Can't remeber any situation in standard where such functionality should be used. In my opinion most appropriate way to implement this - to use Column Tree. Take a look into SAPCOLUMN_TREE_CONTROL_DEMO