Skip to Content
Author's profile photo Dmitrii Sharshatkin

WebUI meets ABAP CDS

Introduction

ABAP CDS views allow us to build very nice and structured applications for any solution or UI.

This blog is about how to build a very nice, simple and really fast WebUI application for really crazy data model and volumes, by just using ABAP CDS.

 

Business Case

I had to build a search (F4 WebUI Popup) for one very “specific” application.

We are using CRM IBASE model to store certain objects, which can be grouped into several CDS Views or entities. In order to keep SAP’s secrets, I will not reveal the names and will call them: Entity1, Entity2, Entity3.

Behind each entity, we have about 8, almost always the same tables or views. Here is the list of the tables with total numbers of entries in these tables: IBIN (275.447.044), IBIB (2.271.052), ZSIB_PRD_IDX (4.418.220), COMV_PRSHTEXT (7.184.985), ZSST_SY_SMVV (72.926), IBPART_IDX (8.129.592), BUT000 (17.244.188), ZSST_SY_HEAD (3.163.696), ZSST_BUS_ADM (288.391), ZSST_BUS_SM (288.391).

So you can see the data volumes are really tremendous! But HANA can handle it. Yes, all this runs on HANA database; didn’t I tell it before? :-)))

 

Step 1: Define your entities and build ABAP CDS views

At the beginning, you need to define your entities (Entity1, Entity2, Entity3) and build proper ABAP CDS views. I will not describe how to do it, it was already well described million times on SCN. I will just give you some examples.

Entity1

define view Entity1
with parameters p_valid_at : timestamp
as select  
from ibin                    as component 
inner join ibib              as ibase      on component.mandt = ibase.mandt
                                          and component.ibase = ibase.ibase
inner join zsib_prd_idx      as prd_idx   on component.mandt = prd_idx.client
                                         and component.objnr = prd_idx.objnr
                                         and prd_idx.status <> 'SMTHHERE' 
inner join comv_prshtext     as product   on prd_idx.client       = product.client
                                         and prd_idx.product_guid = product.product_guid
                                         and product.object_family = 'SMTHHERE'
inner join zsst_sy_smvv      as smvv     on smvv.client        = product.client
                                            and smvv.product_guid  = product.product_guid
                                            and smvv.valid_from   <= :p_valid_at 
                                            and smvv.valid_to     >= :p_valid_at
left outer join ibpart_idx   as partner_idx on component.mandt     = partner_idx.mandt
                                           and ibase.ib_guid_16    = partner_idx.segment_recno 
                                           and partner_idx.segment = 1
inner join but000            as bp   on bp.client  = partner_idx.mandt
                                    and bp.partner = partner_idx.partner
                                    and ( bp.type = '2' or bp.type = '3' ) 
left outer join zsst_sy_head as sy_head  on sy_head.client       = product.client
                                        and sy_head.product_guid = product.product_guid 

                                   
{
26 fields from all tables
}
where 
    component.valfr <= :p_valid_at
and component.valto >= :p_valid_at
and ibase.ibtyp      = 'SMTHHERE' 
;

We just join 8 tables with hundreds of millions of entries inside! Entities 2 and 3 look very similar indeed. :-)))

 

Step 2: Define views for the relations and the searches

If you have multiple entities, as in mine case, probably you would like to connect them together and build the search views.

In my case for one search I had to use even union all. Therefore, my search CDS layer contains, in fact, two levels, so that I will just provide two ABAP CDS definitions.

Search Level 1: Entity1_SEARCH1

define view Entity1_SEARCH1
with parameters p_valid_at : timestamp
as select distinct from  Entity1 (p_valid_at: $parameters.p_valid_at)  as Ent1 
left outer join          Entity2 (p_valid_at: $parameters.p_valid_at)  as Ent2 on Ent2.key = Ent1.key
left outer join          Entity3 (p_valid_at: $parameters.p_valid_at)  as Ent3 on Ent3.key = Ent2.key
left outer join          SomeView                                      as Smth on Smth.key = Ent1.key
{  32 fields from all entities }
where
    Ent1.usage not like '%ABC%'
and Ent1.usage not like '%XYZ%'
and Ent1.deletion_flag <> 'D'
and Ent1.deletion_flag <> 'X'
and
 (
  Ent2.solution_id is not null
  or
  (     Ent2.solution_id   is null
    and Smth.sys_product_id is null 
  )
)
;

The entity Entity1_SEARCH2, which will be used in UNION ALL later, looks pretty similar.

 

Search Level 2: Entity1_SEARCH (combination of the search CDS from the level 1)

define view : Entity1_SEARCH
with parameters p_valid_at : timestamp
as 
select distinct from  
  Entity1_SEARCH1 (p_valid_at: $parameters.p_valid_at)   as Entity1_SEARCH1
{ All 32 fields }  
union all 
select distinct from  
  Entity1_SEARCH2 (p_valid_at: $parameters.p_valid_at)   as Entity1_SEARCH2
{ All 32 fields }
;


Performance Note
: when you create a view, or execute UNION ALL statement (most probably on many databases, including HANA), the search though the parts (i.e. Entity1_SEARCH1, Entity1_SEARCH2) will be executed in parallel reducing the response time! Use UNION ALL and do not call two similar selects from ABAP side.

 

Step 3: Create search- and search result- structures

When everything is ready on DB side, it’s a time to develop in ABAP (NetWeaver).

For our search, we need to create the search and result structures in SE11. I will skip this step, as it’s not important and well described.

Should I remind you that for simplicity you should give the names to the fields as they appear to be in the CDS view or in the corresponding generated ABAP view representation.

 

Step 4: Develop GENIL search classes

In my case I was dealing with IBASE component and therefore I inherited the class from the standard CL_IBCOMPTOCOMPADV_IL. To implement a search, you need to implement the method: SEARCH_DYNAMIC.

I wanted to publish a complete code of the method IF_IBASE_IL_SEARCH~SEARCH_DYNAMIC, but I need to anonymize really a lot and therefore will just post a couple of the most important things.

Important Thing 1

Performance Note: When you build your view on 10 different tables using LEFT OUTER JOIN and then during the selection you are using the fields (in SELECT … or WHERE… clauses) only from 1 table, most probably (must for HANA) the other 9 tables will be just ignored.

It’s very important to keep the selection list as small as possible, and select the only fields, which will be shown on the screen. You can pass the fields visible to the end-user via the variable IS_QUERY_PARAMS-SELECTION_HITS[].

 

    " Build Select List...
    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
    build_select_list( is_query_params-selection_hints[] ).

Important Thing 2

You should build your CDS views in way that no subsequent selects are needed. All the data has to be read from DB in one call.

    " Trigger a normal select...
    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
    select distinct (gt_select_list)                    "#EC CI_NOORDER
    from Entity1_SEARCH( p_valid_at = @lv_timestamp )       ##DB_FEATURE_MODE[VIEWS_WITH_PARAMETERS]
    where
          field0   in @lr_field0
           and field1   in @lr_field1       
           and field2   in @lr_field2
      and field3   in @lr_field3
      and field4   in @lr_field4
      and field5   in @lr_field5
      and field6   in @lr_field6
      and (lv_smth_else)
      and (lv_auth_tail)
    into corresponding fields of table @lt_result
    up to @lv_max_hits_new rows.

 

Always apply Max Hits parameter, which can be retrieved from IS_QUERY_PARAMS-MAX_HITS.

 

Step 5: Enhance BOL/GENIL model

At this step you need to enhance BOL/GENIL model (or create your own) and let the system know your objects. The way how you do it actually depends on the application. For 1Order, BUPA, CASE or IBASE applications, different approaches must be used, and we will skip it. It was also quite nicely described on SCN already.

In my case I had to put my query and result objects into the table CRMC_OBJ_IBIL and redefine object instatiation in the class CL_CRM_IBASE_IL_FACTORY.

(Some other example see here:

https://blogs.sap.com/2015/12/11/extend-1order-searches-how-to-guide-for-customers/

https://wiki.scn.sap.com/wiki/display/Snippets/How+to+Extend+Model+for+Business+Transactions+with+New+Relations)

So finally you should have the standard model to be enhanced or new custom model to be created.

 

 

Step 6: Build WebUI Component and Views

Finally, you need to consume your newly created BOL / GENIL objects. For that you might need to create a custom WebUI component with respective search and search result views and use the corresponding search and search result objects.

And of course, we will skip this part as well and fill focus on the most important things.

( For more details refer to https://blogs.sap.com/2010/03/28/crm-70-how-to-5e-search-view-and-search-result/ )

Remember, we said (switch on a boring professor’s voice and your imagination J))): “ …for better performance you should provide the list of the fields visible to the end user”. Yes, it’s easy. You can get it from personalization!

Your ON_SEARCH event handler should contain the following lines:

  method eh_onsearch.

    data: lr_query_service type ref to cl_crm_bol_dquery_service.
    data: lt_sel_fields    type crmt_name_value_pair_tab.

    lr_query_service ?= typed_context->searchquerynode->collection_wrapper->get_current( ).
    lr_query_service->get_property_as_value( exporting iv_attr_name = 'SELECTION_HINTS'
                                             importing ev_result    = lt_sel_fields  ).

    " Reload Selection Fields...
    delete lt_sel_fields where name = 'SEL_FIELD'.
    append lines of get_rv_fields( 'ZYOURCOMP/YourPage#ZYOURCOMP/YourResultView' ) to lt_sel_fields[].


    lr_query_service->set_property( exporting iv_attr_name = 'SELECTION_HINTS'
                                              iv_value     = lt_sel_fields  ).

And the method GET_RV_FIELDS (Get Result View Fields) looks as below:

  method get_rv_fields.

    data: lt_path      type table of string.
    data: lr_window    type ref to cl_bsp_wd_window.
    data: lr_rv        type ref to cl_bsp_wd_view_controller.
    data: lr_config    type ref to if_bsp_dlc_configuration.
    data: ls_config    type bsp_dlc_table_descr.

    split iv_view_path at '#' into table lt_path.
    if lt_path[] is initial.
      return.
    endif.

    lr_window = me->view_manager->get_window_controller( ).
    if lr_window is bound.
      loop at lt_path assigning field-symbol(<fs_path>).
        " First Level -> Window
        if sy-tabix = 1.
          lr_rv = lr_window->get_subcontroller_by_viewname( <fs_path> ).
        else.
          " Next Level -> View or Viewset
          if lr_rv is bound.
            lr_rv = lr_rv->get_subcontroller_by_viewname( <fs_path> ).
          endif.
        endif.
      endloop.

      " Latest View
      if lr_rv is bound.
        lr_config = lr_rv->configuration_descr.
        if lr_config is bound.
          call method cl_bsp_dlc_table_utility=>conf_xml_to_abap
            exporting
              iv_xml          = lr_config->get_config_data( )
            receiving
              rs_table_config = ls_config.

          " Take Only Standard, Showed Fields
          loop at ls_config-columndefinition assigning field-symbol(<fs_column>)
            where name cp 'STRUCT.*' and hidden = abap_false.

            replace first occurrence of 'STRUCT.' in <fs_column>-name with space.
            condense <fs_column>-name no-gaps.

            append value #( name = 'SEL_FIELD' value = <fs_column>-name  ) to et_fields[].
          endloop.
        endif. " if lr_config is bound.
      endif. " if lr_rv is bound.
    endif. " if lr_window is bound

  endmethod.

 

Relevant Information:

 

Extend 1Order Searches: How to guide for customers

https://blogs.sap.com/2015/12/11/extend-1order-searches-how-to-guide-for-customers/

… to add to Michael’s post – “Virtual fields in CRM reporting Framework”

https://blogs.sap.com/2017/05/04/virtual-fields-in-crm-reporting-framework/

How to Extend Model for Business Transactions with New Relationships

https://wiki.scn.sap.com/wiki/display/Snippets/How+to+Extend+Model+for+Business+Transactions+with+New+Relations

 

Handler Classes in SAP CRM WEBUI Framework

https://blogs.sap.com/2014/09/22/handler-classes-in-sap-crm-webui-framework/

 

Creating a Custom Genil/Bol object Model

https://blogs.sap.com/2012/08/06/creating-a-genilbol-object-model/

 

CRM 7.0 How to –5E Search View and Search Result

https://blogs.sap.com/2010/03/28/crm-70-how-to-5e-search-view-and-search-result/

 

Performance considerations when using ABAP Core Data Services on AnyDB

https://blogs.sap.com/2016/10/27/performance-considerations-using-abap-core-data-services-anydb/

 

 

 

Assigned Tags

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

      Thanks for sharing.

      I must admit I haven't read your complete solution in detail, but one thing that caught my eyes:

      Aren't CDS associations handy in such cases (of multiple related tables)?

       

      Author's profile photo Dmitrii Sharshatkin
      Dmitrii Sharshatkin
      Blog Post Author

       

      Hi! Well, maybe. But I'm old-fashioned. :-)))

      Author's profile photo Jan Revyn
      Jan Revyn

      I don't think associations are relevant in this specific (SAP CRM Web UI) case.

      Nice ... for an old-fashioned guy 😉