Skip to Content
Technical Articles
Author's profile photo Santhosini K

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

What goes beyond the code push down? It is the filter push down!

For a better understanding of code push down please refer to my below blog post on code pushdown.

https://blogs.sap.com/2021/04/14/sql-script-for-abap-managed-database-proceduresamdp-code-pushdown-for-a-better-performance/

As I have already discussed in my previous blog post, greater the number of records, more the time taken by the application.

We already know enough on minimizing the number of records returned by the database layer to the ABAP layer. Going beyond ,how do we reduce the number of records processed by the SAP HANA database layer?

Just by pushing down the filters from consumption level selection to the SAP HANA database table level selection.

Very often in the normal application design, the UI( User Interface) layer filters are applied at the consumption views level and not on the database table level. In high volume analytical applications this design would make the CDS views to process and return a larger dataset which in turn hits on the memory and the performance of the application.

The better approach for a high data volume application is to push down the filters as much as possible to the data base table level so that we will have to work with the required set of data in order to  achieve an optimized use of time and memory.

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.

Single%20Select%20Filter%20example

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

Multiple%20selection%20Filters%20example

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 !

Do let me know how it worked for you! I will meet you in my next blog post where I shall discuss more on the analytical CDS views and their graphical representation in the fiori application

Cheers!

Santhosini K

 

Assigned Tags

      3 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Aman Garg
      Aman Garg

      Hi Santosini,

      Thanks for sharing this information. I found it very useful.

      However, I have a question here. In the technique #1, where you explained Calling the parameterized CDS inside another CDS View, is it necessary to expose a field of the associated CDS, like the way you have exposed ProdID. I asked this because in your example, its 0..1 cardinality for association. I am just wondering if this would even work for 1..* cardinality

      Thanks,

      Aman Garg

      Author's profile photo Ste-Marie Beauchamp Gabriel
      Ste-Marie Beauchamp Gabriel

      Hello Santosini,

       

      Is there a way to acheive that without specifying fields (having it on every field on the CDS)?

       

      Thank you,

       

      Gabriel Ste-Marie Beauchamp

      Author's profile photo Santhosini K
      Santhosini K
      Blog Post Author

      Hi Ste-Marie Beauchamp Gabriel,

      In normal scenario it is very much possible , as in the case of filtering based on the CDS fields.

      In case of filtering based on CDS parameters, we must specify the name of the parameter while passing the values to them.

      Hope this helps!