Thursday, November 3, 2022

Looping & Performance in ABAP - Work Area, Field Symbol or Data Reference

Let's consider following test scenarios:


1. Changing the number of rows and looping over an internal table of type BSEG​

The graph represents the avarage performance of 1000 runs​


2. Changing the number of fields in an internal table of 1000 rows​

The graph represents the avarage performance of 1000 runs​




Conclusions:

  • Field-symbols are slightly faster than reference variables​
  • The larger the internal table, the more it matters  how you loop​
  • In smaller internal tables you can use reference into, in order to achieve more object-oriented-like code​
  • The winner in terms of performance - field symbols




Design Patterns in ABAP - The Singleton Factory

Design Patterns provide reusable solutions for common problems. So why to solve a problem twice?

Singleton and factory are among the most widely used software patterns. This post demonstrates a combination both - the singleton factory.


Problem: Creating the same object and retrieving the same data objects multiple times throughout a session results in a huge performance issue.

 

Solution: Store the Object Instances in a static class and address them by key when needed. In terms of Design Patterns - a Singleton Factory 

 

Background: Design Patterns - Singleton and Factory 


  • Singleton 

The Singleton is a creational design pattern that makes sure that only one instance of a class is running at a time.  

The client does not even realise that they are working with the same instance all the time. 


  • Factory 

The Factory is a creational design pattern and provides an interface for creating objects. 


Goal: Use the Singleton instance as a data store for objects created through the factory method 


Let's move to ABAP and get more specific.


Problem: Too many duplicate objects of type ZCL_CATS_INTERNAL_ORDER are being created which results in poor performance. 


Solution:  ZCL_CATS_INTERNAL_ORDER_STORE  should be implemented as a singleton-factory in order to keep the data and make sure no duplicates are created.


- Singleton Implementation

In order to impose the singleton behaviour, we need to make sure that the class is defined with:


- private instantiation (private construcor)

- static attribute as a reference to an object of the very same class

- static method to provide access to the sole instance of the class



In our case there is no constructor method, because nothing is happening at the moment of the class creation. In another scenario, for example it would make sence to read and store some customizing data.








The method get_object (often also called  get_instance or instantiate) serves as an access point to the object. 




- Factory Method Implementation

The factory method returns an object of type ZCL_CATS_INTERNAL_ORDER.
If the object is in the store, it is taken from the store, if not it will be created and stored. 
Typically, the factory returns an object of an interface type. The class type itself is defined by the factory.





Used Links:

http://zevolving.com/2012/02/abap-objects-design-patterns-singleton-factory/

The Catalog of Design Patterns (refactoring.guru)

 




Tuesday, March 8, 2022

CDS Views: Performant Status Query using the JEST Table

 The application and the system status in SAP play a very big role in many reports, search helps and evaluations. 

The conventional approach to retrieve the JEST status is to use the dedicated function module STATUS_READ or STATUS_READ_MULTI from the function group BSVA.

If you need the application or the user status just in order to exclude certain objects from a result list or a search help, it is more comfortable to use dictionary objects, such as CDS Views.

In this post I am going to demonstrate how to classify QM notifications into a group of valid and invalid. The invalid notifications include the status values 'completed' and 'cancelled' and have to be filtered out. All the other notifications are valid and have to be shown.

This can be achieved with the help of two CDS Views. 

The main CDS View contains the join with the JEST Table and casts priorities to the status values in the where clause. It is split into two, because we want to have a 'valid' or 'invalid' for every notification.


Here goes the code for the first CDS View:

@AbapCatalog.sqlViewName: 'zcds_notif_main'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'Main View JEST, QMEL'
define view Z_CDS_NOTIF_MAIN as select distinct from qmel as notif
association [1] to jest as _jest on notif.objnr = _jest.objnr{
    _jest.objnr,
    _jest.stat,
    notif.qmnum,
    cast('invalid' as abap.char( 10 )) as status,
    cast('5' as abap.int1) as prio
} where _jest.stat = 'E0016' and  _jest.stat like 'E%' and _jest.inact = ''
// or _jest.stat = 'E0012' //completed or cancelled, maybe join with table tj30, this is just a demo!
union select distinct from qmel as notif
association [1] to jest as _jest on notif.objnr = _jest.objnr{
    _jest.objnr,
    _jest.stat,
    notif.qmnum,
    cast(' valid' as abap.char( 10 )) as status,
    cast('4' as abap.int1) as prio
} where _jest.stat != 'E0016' and  _jest.stat like 'E%' and _jest.inact = ''
//and _jest.stat != 'E0012'

The second CDS View accumulates the values of the first view, here we have one entry per QM notification, according to priority.

Here goes the code for the second CDS View:

@AbapCatalog.sqlViewName: 'ZDS_NOTIF_STATUS'
@AbapCatalog.compiler.compareFilter: true
@AbapCatalog.preserveKey: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'QMEL mit Status'
define view Z_CDS_NOTIF_STATUS as select from qmel  as NOTIF inner join zcds_notif_main as MAIN
on NOTIF.qmnum = MAIN.qmnum {
 key   NOTIF.qmnum,
 max(MAIN.status) as status,
 max(prio) as stat
} group by NOTIF.qmnum

You could create an auxiliary view on the JEST Table, using only the relevant status schema. Another improvement would be to push the 'valid / invalid' logics to a customizng table.

Have fun while trying, adjustung and improving the approach!


Friday, October 22, 2021

SAP: Business Technology Platform - How to Create an Entity and Deploy it to HANA Cloud DB

 In this post I am going to describe in simple steps how to create an entity (a table) using the Business Application Studio (BAS)  and Cloud Application Programming (CAP).

Before I start with the practical part, I want to share some thoughts on a possible business case where it makes sense to use CAP. 

For example, let us think of an application (FIORI Elements) that has the goal to store, monitor and analyse information related to a business object from the on Premise System, let's say a production plant. If the information we want to store is not related to production or logistics and not really needed in the BackEnd System, there is no good reason to store it on your on-Premise System. 

That is why it is always a good idea to ask ourselves and our team where we need the data. Do we need a picture of your factory in SAP BackEnd? Probably no. Does our BackEnd System need to know if the lightning in the Plant is good? Probably no. 
Our application can create Plant Management (PM) orders / notifications in the SAP BackEnd System only in the cases when somethings is not working, e.g. the lightning is out of order.
Is the information we are going to store on your Cloud DB sensitive? Does storing it on cloud violate the legal requirements of your company? If not, then you can consider saving it on the cloud. It is all about viewing the BPT and the on Premise System as one architecture and making the best choice.

The whole information and history of the stored data will be available on SAP BTP and we can create some analytical apps to monitor and evaluate it.

In the described case we would be are building an application that consumes (oData Services, probably some search helps)  from your on Premise system and stores the data on the SAP HANA Cloud.

To make the example more interesting, we will consider a customizing table where we define what information is going to be stored. Let us imagine something like outdoor area, indoor are, lightening, etc. Those areas can be evaluated with stars and with pictures. This table will be controlling the behavious of our application and we will need it on the Business Technology Platform.

Please consider that this and all other business scenarios on this blog are fully invented by me! 

Our first task is to create the customizing table and this is a quick and straightforward example.

In terms of CAP, the idea is to think of creating entities rather than creating tables. 

So the entity managing our applicaion will look like this:

Area : String(10)
Picture Upload:  Boolean
Evaluation Enabled: Boolean
PM Order Creation Enabled: Boolean

We also want to have some initial configuration like:

Area                    Picture Upload     Evaluation Enabled    PM Order Creation Enabled
Outdoor Area      true                       true                             false
Lightning            true                       true                             true


Let's have an overview of the prerequisites:

  • You have created a dedicated subaccount or you have a trial account on the Business Technology Platform
  • In your subaccount you have subscribed to the Business Application Studio
  • In you subaccount you have created a space and an SAP HANA Cloud DB Instance is running
Let's go to the BTP and create the entity:

  • If you haven't done so, now is the time to create and run your devspace of type Full Stack Cloud Application
  • In our devspace, we open a 'New Terminal' and change to projects by using 
cd projects
  • Then we create our projects by using the command
cds init plantapp

where plantapp where plantapp is the name of our project.
  • As a result we get three empty folders: app, bd and srv which are empty. In other terms, we have got the persistence layer, the UI layer and the service layer.
  • Now it is time to go ahead and create our entity. We go to db and create a file called schema of type CDS. The Type CDS is very important.
  • By saving the entity, it gets actually deployed to sqlite
  • The next step is to polulate our data. For this purpose we need to create a subfolder called data into our db folder. The name of the file is going to be the namespace / the name of the entity .csv
  • Through this we have filled the entity with the data specified in the .csv File
  • Now we can create a catalogue service. We create a file, for example cat-Service.cds in the folder srv. We need just a few lines of code for our service:
  • Time to test our Service
In the Terminal, type 'cds watch' and you will get the url of the oData Service Link.
When you klick it you will find the metadata of the service and even a ready fiori app where you can view your data.


  • Let's have a look at the Fiori Preview
This is what our Data looks like in Fiori


Our Application is running with in memory sqlite database.

We can deploy it to the cloud foundry by using the two following commands

  • cds add hana
  • cds deploy --to hana (Sets an HDI Container) 
For the creation of this post I have used this onboarding tutorial.



















Wednesday, May 5, 2021

ABAP: Less Looping & Better Performance with the REDUCE Statement

 The  REDUCE statement in ABAP is a newer, more performant way of grouping numbers, strings and of course tables without nested looping and declaring too many additional helping variables.

In this post I am going to give some simple theorethical examples with numbers and strings and one more business related example.


  • Simple Sum
This is a rather theorethical example, that will help us understand the syntax. Here we want to sum the numbers from 1 to 100.
The result is the variable lv_i of type i.
The sum is in the variable s and is only valid in the reduce statement. 
j is our counter and so we go.Execute and you will get 5050 .


 data(lv_i=  reduce iinit s 0
  for until j > 100
    next s + j ).

  • String operation
This is another theorethical example. It demonstrates that we can use the reduce statement for non-arithmetic operations.


DATA(lv_textREDUCE stringINIT text | |
                              FOR |AB| THEN t && |0|
                                          UNTIL strlen10
                              NEXT text text && |{ t }| && |,).
The result is:

  • Business-related example
Let us take an example from the accounting and assume that we want to sum the amounts per trading partner. For simplicity, we will do this in one company code.

This is what my code would look like by using the old LOOP AT NEW statement:

TYPESBEGIN OF ty_sum,
*        bukrs TYPE bukrs,
         vbund TYPE rassc,
         dmbtr TYPE dmbtr,
       END OF ty_sum.


DATAlt_sum TYPE SORTED TABLE OF ty_sum WITH UNIQUE KEY vbund,
      ls_sum TYPE ty_sum.

TYPESBEGIN OF ty_bsis,
         vbund TYPE bsis-vbund,
         bukrs TYPE bsis-bukrs,
         gjahr TYPE bsis-grant_nbr,
         belnr TYPE bsis-belnr,
         shkzg TYPE bsis-shkzg,
         dmbtr TYPE bsis-dmbtr,
       END OF ty_bsis.

DATA lt_bsis TYPE SORTED TABLE OF ty_bsis WITH NON-UNIQUE KEY vbund.



SELECT vbundbukrsgjahrbelnrshkzgdmbtr FROM bsis INTO CORRESPONDING FIELDS OF TABLE @lt_bsis
  WHERE bukrs EQ 'XXXX' AND gjahr '????' AND vbund NE @space.

CHECK lineslt_bsis 0.

GET RUN TIME FIELD tstart.
LOOP AT lt_bsis INTO DATA(ls_bsis).
  AT NEW vbund.
    ls_sum-vbund ls_bsis-vbund.
    LOOP AT lt_bsis ASSIGNING FIELD-SYMBOL(<fs_bsis>WHERE vbund ls_bsis-vbund.
      ls_sum-dmbtr ls_sum-dmbtr + COND dmbtrWHEN <fs_bsis>-shkzg EQ 'S' THEN <fs_bsis>-dmbtr ELSE <fs_bsis>-dmbtr * -).
    ENDLOOP.
    APPEND ls_sum TO lt_sum.
    CLEAR ls_sum.
  ENDAT.
ENDLOOP.

* Record end time
GET RUN TIME FIELD tstop.

trun tstop tstart ).

The same with REDUCE:

The filter statement generates an internal with the desired records only, so that we can avoid the LOOP AT ...WHERE situation above.

LOOP AT lt_sum ASSIGNING FIELD-SYMBOL(<fs_sum_>).
  <fs_sum_>-dmbtr REDUCE dmbtrINIT val TYPE dmbtr
                                FOR ls_bsis_r IN
                                FILTER #lt_bsis
                                          WHERE vbund EQ  <fs_sum_>-vbund )
                                NEXT val val + COND dmbtrWHEN ls_bsis_r-shkzg EQ 'S' THEN ls_bsis_r-dmbtr
                                                             ELSE ls_bsis_r-dmbtr * -).
  WRITE<fs_sum_>-vbund<fs_sum_>-dmbtr NEW-LINE.
ENDLOOP.

The result is cleaner and more performant code!