Monday, 17 May 2021

Filters Push down in ABAP CDS views – For a better performance of the application!

In this blog post I have discussed on various technics those can be used to push down the consumption level filters to the database table level

1. Pushing down the single selection filters from the fiori application using parameterized ABAP CDS views

2. Pushing down the multiple selection filters from the fiori application using the dynamic where clause – In combination with the ABAP Managed Database Procedure (AMDP ) table functions and ABAP CDS views.

Pushing down Single selection filters

In the below example, there is  a mandatory filter criterion  “Ad Effective Date” in the fiori application layer. Let us see how to taken the filter till the database level selection instead of using it as a consumption filter.

SAP ABAP Exam Prep, SAP ABAP Certification, SAP ABAP Preparation, SAP ABAP Career
Single Select Filter example

Place this filter as a parameter in the final Consumption CDS view and pass it down all through your layers till it reaches the actual table selection.

“P_adeffdate” is the parameter in my example. ZI_PROD_FILTER_BYDATE = > is the actual CDS view which hits the database.

My Consumption view – ZC_GEN_REPORT

@AbapCatalog.sqlViewName: 'ZCGENREPORT'
@AbapCatalog.compiler.compareFilter: true
@VDM.viewType: #CONSUMPTION
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Filter the products by date'
@ObjectModel: {
  usageType.serviceQuality: #D,
  usageType.sizeCategory: #XXL,
  usageType.dataClass: #TRANSACTIONAL
}
@Metadata.allowExtensions: true
define view ZC_GEN_REPORT
  with parameters
    p_adeffdate :char8    
  as select from ZI_PROD_FILTER_BYDATE(p_adeffdate:$parameters.p_adeffdate) as _Product
 {
  key  _Product.mandt                as Clnt,
  key  _Product.node_id              as NodeId,
  key  _Product.prod_id              as ProdId,
  key  _Product.prod_hr_id           as ProdHrId,
       _Product.prod_name_uc         as ArticleName      
}
@AbapCatalog.sqlViewName: 'ZIPRODSEARCH'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Filter the products by date'
define view ZI_PROD_FILTER_BYDATE
  with parameters
    p_adeffdate : dats
  as select from    /dmf/prod_nd_xr  as _Product
    inner join      /dmf/prod        as _ArticleChar on _Product.prod_id = _ArticleChar.prod_id
    association [0..1] to /dmf/prodt as _ArticleTxt  on _Product.prod_id = _ArticleTxt.prod_id
{
  key  _Product.mandt                as Clnt,
  key  _Product.node_id              as NodeId,
  key  _Product.prod_id              as ProdId,
  key  _Product.prod_hr_id           as ProdHrId,
       _ArticleTxt.prod_name_uc      as ArticleName   
}
where
      _Product.valid_fr <= $parameters.p_adeffdate
  and _Product.valid_to >= $parameters.p_adeffdate
 
Below are the snippets on how to call the parameterized ABAP CDS views from different layers of the application

Calling from the UI layer for an analytical application

/sap/opu/odata/sap/ZC_GEN_ANALYTICAL_REPORT_SRV/ZC_GEN_ANALYTICAL_REPORT(p_adeffdate=datetime'2020-07-26T00:00:00')/Results 

Calling from the UI layer for a normal application

/sap/opu/odata/sap/ZC_GEN_REPORT_SRV/ZC_GEN_REPORT(p_adeffdate=datetime'2020-07-26T00:00:00')/Set

Calling a parameterized CDS view from  ABAP layer

select * from ZIPRODSEARCH(p_adeffdate = @lv_adeffdate) into table  @data(lt_prod).
Calling the parameterized CDS views in AMDP method for a table function – SQL layer

Table function

@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Table function for Prod att'
@ClientHandling.type: #CLIENT_INDEPENDENT
define table function ZTABL_PROD
  with parameters
  p_adeffdate : dats
returns
{
  key clnt         : abap.clnt;
  key ProdHrId     : /dmf/hierarchy_id;
      ProdId       : /dmf/product_id;
}
implemented by method
  ZCL_COM_FINAL=>GET_PROD;

AMDP method

method get_prod
        by database function
        for hdb
        language sqlscript
        options read-only
        using ZIPRODSEARCH.
     Return select clnt, ProdHrId , ProdId from ZIPRODSEARCH(:p_adeffdate) as _Prod
            Where _Prod.clnt = session_context('CLIENT');
Endmethod.

Calling the parameterized CDS inside another CDS View

Association example

@AbapCatalog.sqlViewName: 'ZCRETAIL'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Retail Report'
}
define view ZI_RETAIL
  with parameters
    p_addeffdate : dats
as select from ZI_RETAIL as _Main
association [0..1] to ZI_PROD_FILTER_BYDATE as _Prod  on _Main.ProdId = _Prod.ProdId
{
     Key _Main.ProdHrId as ProdHrId,
         _Prod(p_adeffdate:$parameters.p_adeffdate).prodId as ProdId
}

Using inner join

Inner join  ZI_PROD_FILTER_BYDATE(p_adeffdate:$parameters.p_adeffdate) as _Prod  on _Main.ProdId = 
                                                                                              _Prod.ProdId
{
     Key _Main.ProdHrId as ProdHrId,
         _Prod.prodId   as ProdId
}

Pushing down the multiple selection(select options) type filters


Sometimes it takes a lot to pass the multiselect filters from the UI layer to the database layer. But when performance of the application is a key factor, we can make use of the dynamic where clause in combination with the AMDP table functions and achieve the desired filter push down

Here in my current example, I have considered a CDS entity Set , redefined the Get_entityset method of the DPC_EXT class in my ODATA project and converted the multiselect filters into a dynamic where clause. After which I have passed the where clause down to the database table in my AMDP method through the Table function via the Consumption CDS view.

Along with Ad effective date which is a single select filter , I have two multiselect filters – Department and Sales Org

SAP ABAP Exam Prep, SAP ABAP Certification, SAP ABAP Preparation, SAP ABAP Career
Multiple selection Filters example

Let me start with the consumption CDS view and then take you through the code in the ABAP layer.

My consumption CDS view will look like this

“Dept” and “SalesOrg” are my multiselect filters

@AbapCatalog.sqlViewName: 'ZCGETOFFER'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Get offers'
@ObjectModel: {
  usageType.serviceQuality: #D, 
  usageType.sizeCategory: #XL,
  usageType.dataClass: #TRANSACTIONAL
}
define view ZC_GET_OFFER
  with parameters
    @Consumption.hidden: true
    P_WHERE      : abap.char(1333)
    as select from ZTABL_ALL_OFR(P_WHERE:$parameters.P_WHERE) as _AllOfr
    {
 
     key _AllOfr.OfrId     as OfrId,
     key _AllOfr.OfrIntId  as OfrIntId,
         _AllOfr.Dept      as Department,
         _AllOfr.SalesOrg  as SalesOrg
     
    }

Let me expand the table function – ZTABL_ALL_OFR

@AccessControl.authorizationCheck: #CHECK
@ClientHandling.type: #CLIENT_DEPENDENT
@EndUserText.label: 'Offer details / Min adzone'
define table function ZTABL_ALL_OFR
  with parameters    
    P_WHERE      : abap.char(1333)
returns
{
  key clnt                   : abap.clnt;
  key OfrId                  : /dmf/ext_offer_id;
  key OfrIntId               : /bobf/conf_key;
      Dept                   : char2; 
      SalesOrg               : /dmf/org_unit_id_so;
      
}
implemented by method
ZCL_COM_FINAL=>ALL_OFFERS;

Let’s get more deeper into the AMDP method

method all_offers
         by database function
         for hdb
         language sqlscript
         options read-only
         using  ztabl_ofr. 
itab_ofr   =  SELECT _Ofr.mandt as clnt , _Ofr.OfrId , _Ofr.OfrIntId ,_Ofr.Dept , _Ofr.SalesOrg FROM 
              ztabl_ofr AS _Ofr where _Ofr.mandt = session_context('CLIENT');
itab_final = apply_filter ( :itab_ofr , :P_WHERE) ;
return select * from itab_final;
endmethod

After all the drill down, lets complete the top layer

Building a dynamic where clause and passing it as a parameter to the CDS entity

 method zc_Offer_get_entityset.

 constants : lc_dept           type c length 4 value 'DEPT',
             lc_salesorg       type c length 7 value 'SALESORG'.

 data: dept       type /iwbep/t_cod_select_options,
       salesorg   type /iwbep/t_cod_select_options.

 data:    lo_tech_request_clone type ref to /iwbep/cl_mgw_request,
          lt_headers            type tihttpnvp,
          lr_request            type ref to /iwbep/if_mgw_core_srv_runtime=>ty_s_mgw_request_context.          

data(ls_request) = cast /iwbep/cl_mgw_request( io_tech_request_context )->get_request_details( ).
data ls_key like line of ls_request-technical_request-key_tab
try.
 data(lt_dep) = it_filter_select_options[ property = lc_dept ]-select_options.
catch cx_sy_itab_line_not_found.
  clear lt_dep.
endtry.

try.
    data(lt_salesorg) = it_filter_select_options[ property = lc_Salesorg ]-select_options.
catch cx_sy_itab_line_not_found.
   clear lt_salesorg.
endtry.          

data(lv_where) = 
cl_shdb_seltab=>combine_seltabs( it_named_seltabs = value #( ( name = 'Dept' dref = ref #( lt_Dep[] ) )
                                                             ( name = 'SalesOrg' dref = ref #( 
                                                                               lt_salesorg[]  ) )  ) ).
    ls_key-name = 'P_WHERE'.
    ls_key-value = lv_where.
    DESCRIBE TABLE ls_request-technical_request-key_tab lines DATA(lv_lines). 
    Add 1 to lv_lines.
    insert ls_key into ls_request-technical_request-key_tab index lv_lines .

    get reference of ls_request into lr_request.
    data(lo_model_fw) = cast /iwbep/if_mgw_odata_fw_model( cast /iwbep/cl_mgw_dp_facade( me- >/iwbep/if_mgw_conv_srv_runtime~get_dp_facade( ) )->/iwbep/if_mgw_dp_int_facade~get_model( ) ).
    lo_tech_request_clone = new #( ir_request_details = lr_request it_headers = lt_headers io_model = 
                                                                                         lo_model_fw ).
  try.
        call method super->zc_get_offer_get_entityset
          exporting
            iv_entity_name           = iv_entity_name
            iv_entity_set_name       = iv_entity_set_name
            iv_source_name           = iv_entity_set_name
            it_filter_select_options = it_filter_select_options
            is_paging                = is_paging
            it_key_tab               = it_key_tab
            it_navigation_path       = it_navigation_path
            it_order                 = it_order
            iv_filter_string         = iv_filter_string
            iv_search_string         = iv_search_string
            io_tech_request_context  = lo_tech_request_clone
          importing
            et_entityset             = et_entityset
            es_response_context      = es_response_context.
      catch /iwbep/cx_mgw_busi_exception .
        clear : et_entityset,es_response_context.
      catch /iwbep/cx_mgw_tech_exception .
        clear : et_entityset,es_response_context.
    endtry.
endmethod.

This way we can take down the multiple select options till the database layer and not disturb the other query options rendered by the framework!

Now you are all set for a lightning fast application !

Source: sap.com

No comments:

Post a Comment