2

In my AS ABAP 7.50 system, I have a table where the material length is 18 and I need to expose it via CDS as if the material length was 40 like in S/4. The material IDs in the system can be numeric (with leading zeros) or alphanumeric. The material field needs to be casted to MATNR40, and if the ID is numeric, the leading zeros need to be added up to the 40 characters.

First, I tried `lpad. But of course, it also adds the leading zeros to the alphanumeric values:

lpad( cast(matnr as matnr40), 40, '0' ) as material_long,

Then I added a case but I'm not able to make the condition work as I expect. As konstantin confirmed in the comments, it's not possible to use regex here as I attempted:

case when matnr like '%[^0-9.]%'
     then lpad( cast(matnr as matnr40), 40, '0' )
     else cast(matnr as matnr40)
end as material_long,

Is there a solution within the CDS itself to this problem?

Source table:

MATNR18 Description
000000000000000142 Numeric ID material
MATERIAL_2 Alphanumeric ID

Expected result:

MATNR40 Description
0000000000000000000000000000000000000142 Numeric ID material
MATERIAL_2 Alphanumeric ID
Suncatcher
  • 10,355
  • 10
  • 52
  • 90
RaTiO
  • 979
  • 2
  • 17
  • 33
  • Is the data just being read (limit/offset/all) or is the column of interest also filtered or used in some more complex queries? Because if the data you're trying to convert are used in any even simple filters (like OData) these filters probably fail to "understand" your casts, case structures, regex and all. Your initially well-intentioned and concise CDS code might then become a huge, hard-to-debug performance bottle neck with lots of unforseeable bugs. You might be better off writing an (unittested) DPC_EXT method that does the conversion for you than trying to solve this in "plain" CDS/SQL. – koks der drache Jan 11 '21 at 21:36
  • BTW: The `LIKE` comparison does not understand regular expressions. It's more wildcards but nothing fancy like you're trying to do. See the official sap documentation [regex](https://help.sap.com/doc/abapdocu_750_index_htm/7.50/en-US/abenregular_expression_glosry.htm) vs. [like](https://help.sap.com/doc/abapdocu_750_index_htm/7.50/en-US/abencds_cond_expr_like.htm). If you'd like to use the regex in some ABAP code you could use [`^\d+$`](https://regex101.com/r/Mpjlvg/1) to match only numerical values or `NOT LIKE %_%` in SQL/CDS if all non-numerical values contained an underscore. – koks der drache Jan 11 '21 at 21:44
  • @konstantin the problem is that some tables in the system already have the 40 characters length and I need to create joins and associations between tables using different field length. I'm not allowed to change the field length at this point so for now I just wanted to solve it in a CDS. Otherwise I will just do intermediate processing in ABAP and use FOR ALL ENTRIES... – RaTiO Jan 12 '21 at 07:41

3 Answers3

2

Due to the limited functionality in CDS syntax the only way I see is to nest 10 REPLACE functions to remove digits and compare the result with initial string. If it is initial, then you have only digits, so you can LPAD them with zeroes. If not - use the original value.

Here's my code:

@AbapCatalog.sqlViewName: 'Z_V_TEST'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Test'
define view Z_TEST as select from /bi0/mmaterial {
    cast(
      case replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(material,
        '0', ''), '1', ''), '2', ''), '3', ''), '4', ''), '5', ''), '6', ''), '7', ''), '8', ''), '9', '')
        when ''
        then lpad(material, 40, '0')
        else material
      end as abap.char(40)
    ) as MATERIAL_ALPHA,
    material
}

And the result is:

REPORT Z_TEST.

    select *
    from Z_V_TEST
    where material in ('LAMP', '000000000000454445')

      into table @data(lt_res)
    .
    cl_demo_output=>display( lt_res ).



MATERIAL_ALPHA                           | MATERIAL 
-----------------------------------------+-------------------
0000000000000000000000000000000000454445 | 000000000000454445 
LAMP                                     | LAMP 

astentx
  • 6,393
  • 2
  • 16
  • 25
  • 1
    Very clever thanks, but still not very elegant. If no one else comes with a better solution I will mark it as the correct answer. – RaTiO Jan 12 '21 at 14:38
  • wow, what a brutal approach for breaking CDS limitations :) – Suncatcher Jan 18 '21 at 12:58
  • Yes, it is quite weird, but this looks like how `alpha` is applied inside the HANA-generated BW transformation: `string(if ( ltrim(string( \"0DOC_NUMBER\"), ' 0123456789') = '',string( alphanum( trim ( string( \"0DOC_NUMBER\" ) ), 10) ),midstru(ltrim( \"0DOC_NUMBER\" ) , 1,10 ) ))`. So thanks to handy `trim` function, allowing to remove multiple characters, which is not presented in CDS (and such multicharacter `trim` is not presented in some DBMSes, for ex in Oracle and DB2).. – astentx Jan 19 '21 at 07:27
0

Here is the 7.55 way of doing the thing, when the custom entities were introduced:

  1. Create custom entity

    @EndUserText.label: 'Material List'
    @ObjectModel.query.implementedBy: 'ABAP:ZCL_MATERIAL_ZEROED'
    @Search.searchable: false
    define custom entity zcds_mat {
      key matnr  : abap.char( 40 );
          maktx  : abap.char( 40 );
    }
    
  2. Create implementation class for it

     CLASS ZCL_MATERIAL_ZEROED DEFINITION
       PUBLIC
       FINAL
       CREATE PUBLIC .
    
       PUBLIC SECTION.
         INTERFACES if_rap_query_provider.
       PROTECTED SECTION.
       PRIVATE SECTION.
     ENDCLASS.
    
     CLASS ZCL_MATERIAL_ZEROED IMPLEMENTATION.
       METHOD if_rap_query_provider~select.
         DATA: it_result   TYPE  TABLE OF zcustomer_zero. "Internal table to be returned , easier to handle return if internal table is as same type of our data definition
         DATA: lv_param    TYPE string."Local variable to fetch and save parameter value
         TRY.
             TRY.
                 IF io_request->is_data_requested( ). "Fetching incoming data
                   io_request->get_paging( ).
    
                   SELECT matnr maktx FROM makt INTO TABLE @it_result.
    
                   LOOP AT it_result ASSIGNING FIELD-SYMBOL(<fldsym>).
                     <fldsym>-matnr = | { <fldsym>-matnr ALPHA = OUT } |.
                   ENDLOOP.
    
                   io_response->set_total_number_of_records( lines( it_result ) ). "setting the total number of records which will be sent
                   io_response->set_data( it_result  ). "returning the data as internal table
                 ENDIF.
               CATCH cx_a4c_rap_query_provider INTO DATA(lx_exc). "CX_A4C_RAP_QUERY_PROVIDER is now deprecated so use CX_RAP_QUERY_PROVIDER
    
             ENDTRY.
           CATCH cx_rfc_dest_provider_error INTO DATA(lx_dest).
         ENDTRY.
       ENDMETHOD.
     ENDCLASS.
    

Here we adding leading zeroes though the string templates

You cannot consume custom entity directly, but you can use it in OData service, just in case you have this intention.

Suncatcher
  • 10,355
  • 10
  • 52
  • 90
0

how about to analyze result of "replace (lower(YourField), upper(YourField), '')"? Since replace is case sensitive then non empty result would indicate we have letters in the string.

pavel
  • 1
  • 2
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 13 '22 at 02:08