In case of a sensitive range, apply this solution
In the documentation of CP we see:
|CP||Covers Pattern: True if the content of operand1 fits the pattern in operand2. Wildcard characters can be used to create the operand2 pattern, where “*” represents any character string (including a blank string) and “+”represents any character. It is not case-sensitive. Trailing blanks in the left operand are respected. If the comparison is true, sy-fdpos contains the offset of operand2 in operand1. Here, leading wildcard characters “*” in operand2 are ignored if operand2 also contains other characters. If the comparison is false, sy-fdpos contains the length of operand1. Characters in operand2 can be selected for direct comparisons by prefixing them with the escape character “#”. For characters flagged in this way in operand2, the operator is case-sensitive. Also, wildcard characters and the escape character are not subject to special handling and trailing blanks are relevant.|
You see that bit in red? That means ‘abc’ CP ‘Ab*’.
Good to know.
So, what about in select-options (and ranges)? Perhaps we’ve a bunch of RFC destinations in table RFCDES, and we’ve got destinations MATT, matt, and Matt. Slightly strange RFC destinations – one wonders about the narcissism of the definer!
So, I’ve got a select-option
DATA g_rfcdest TYPE rfcdest. SELECT-OPTIONS so_rfc FOR g_rfcdest.
In my select option, I have the single entry Ma*. What happens with a select from the database?
SELECT rfcdest INTO TABLE @data(destinations) WHERE rfcdest IN @so_rfc.
Great – it works. I end up with a table, containing only Matt. Let’s try the same with an internal table.
SELECT rfcdest INTO TABLE @DATA(destinations) FROM rfcdes. DELETE destinations WHERE rfcdest NOT IN so_rfc.
Oh. It doesn’t work. It turns out that with select options that have patterns, if they’re used with internal tables, the match is case-insensitive.
So what now? Is it so insensitive that it reduces us to tears?
To a man with hammer, everything is a nail. I’m an ABAPper, so naturally, I’m going for a programmatic solution.
Well, the documentation with CP says that we should use # as an escape character. If my range is 10 characters, and I only want Ma* then I supply #M#a*. OK. But now I want to see if there are matches to Matthe* – so I supply #M#a#t#t#h#e*. Oops – that 13 characters. Too long for the range.
What might not be widely appreciated is, that with a range or select option, it doesn’t matter too much what the actual type used is for the range. So even if my internal table field is ten characters, I could, for example, use a range of strings, and see if my entry is IN that.
So that’s what I did. A couple of small utility methods to do the conversion. And in 7.4 and above, you can make use of some of the new language features to make it very tidy indeed.
REPORT zcs_range. CLASS lcl_case_tools DEFINITION. PUBLIC SECTION. TYPES ty_string_range TYPE RANGE OF string. METHODS get_string_range IMPORTING i_range TYPE STANDARD TABLE RETURNING VALUE(r_string_range) TYPE ty_string_range. METHODS make_case_sensitive IMPORTING i_value TYPE clike RETURNING VALUE(r_cs_value) TYPE string. PRIVATE SECTION. METHODS is_letter IMPORTING i_letter TYPE string RETURNING VALUE(r_is_letter) TYPE abap_bool. ENDCLASS. CLASS lcl_case_tools IMPLEMENTATION. METHOD get_string_range. r_string_range = VALUE #( FOR wa IN CORRESPONDING ty_string_range( i_range ) ( sign = wa-sign option = wa-option low = me->make_case_sensitive( wa-low ) high = me->make_case_sensitive( wa-high ) ) ). ENDMETHOD. METHOD make_case_sensitive. IF i_value IS INITIAL. RETURN. ENDIF. DATA(pos) = 0. DO. IF pos EQ strlen( i_value ). EXIT. ENDIF. DATA(single_char) = substring( val = i_value off = pos len = 1 ). r_cs_value = r_cs_value && COND #( WHEN me->is_letter( single_char ) THEN '#' ) && single_char.. ADD 1 TO pos. ENDDO. ENDMETHOD. METHOD is_letter. r_is_letter = xsdbool( to_upper( i_letter ) CA sy-abcde ). ENDMETHOD. ENDCLASS. START-OF-SELECTION. TYPES ty_stuff TYPE STANDARD TABLE OF char3 WITH NON-UNIQUE KEY table_line. DATA(stuff_to_handle) = VALUE ty_stuff( ( 'abc' ) ( 'ABC' ) ( 'aBC' ) ). cl_demo_output=>begin_section( 'The stuff' ). cl_demo_output=>write_data( stuff_to_handle ). DATA stuff_range TYPE RANGE OF char3. stuff_range = VALUE #( ( sign = 'I' option = 'CP' low = 'A*' ) ). cl_demo_output=>next_section( 'The range' ). cl_demo_output=>write_data( stuff_range ). DATA(ncs_stuff) = stuff_to_handle. DELETE ncs_stuff WHERE table_line IN stuff_range. cl_demo_output=>next_section( 'Non-case sensitive removal' ). cl_demo_output=>write_data( ncs_stuff ). DATA(cs_stuff) = stuff_to_handle. DELETE cs_stuff WHERE table_line IN NEW lcl_case_tools( )->get_string_range( stuff_range ). cl_demo_output=>next_section( 'Case sensitive removal' ). cl_demo_output=>write_data( cs_stuff ). cl_demo_output=>end_section( ). DATA(pattern) = 'Ab*'. cl_demo_output=>begin_section( 'The pattern' ). cl_demo_output=>write_data( pattern ). cl_demo_output=>next_section( '''abc'' CP ''Ab*''' ). IF 'abc' CP pattern. cl_demo_output=>write_text( 'It covers it' ). ELSE. cl_demo_output=>write_text( 'It does not cover it' ). ENDIF. cl_demo_output=>next_section( 'Case sensitive ''abc'' CP ''Ab*''' ). IF 'abc' CP NEW lcl_case_tools( )->make_case_sensitive( pattern ). cl_demo_output=>write_text( 'It covers it' ). ELSE. cl_demo_output=>write_text( 'It does not cover it' ). ENDIF. cl_demo_output=>display( ).
I do have a couple of questions.
- Does anyone know where the case-insensitivity of select-options/ranges with respect to internal tables is documented. I couldn’t find it.
- How do select-options/ranges work with selects from internal tables? I’m assuming they’ll be case sensitive – but unfortunately, I don’t have such a system to play with.
I don't know if these are the documentations you're looking for:
itab ... WHERE ... IN ... : https://help.sap.com/http.svc/rc/abapdocu_752_index_htm/7.52/en-US/index.htm?file=abenlogexp_select_option.htm
OpenSQL ... WHERE ... IN ... : https://help.sap.com/http.svc/rc/abapdocu_752_index_htm/7.52/en-US/index.htm?file=abenwhere_logexp_seltab.htm
Yeah That's the one
Thanks for sharing, Matt (or MATT 🙂 )! I still don't get it though why CP can't just be case-sensitive...
Probably it's been like that too long and it's too late to change it now.