Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
<<Lesson 04. Enhanceable structures, aggregations, derefencation and transposition. And object retriev...Lesson 06: Unstructured returns, sorting and performance considerations >>

Hello everybody,

After the excursion into various topics last time (BPath. Lesson 04. Enhanceable structures, aggregations, derefencation and transposition. And object ...) we are looking forward to presenting some functions. Or at least kind of, since subs are not filed under functions from a syntactical point of view:

Sub functions

sub-functions are triggering a new BPath statement starting on the actual entity:

~*/SearchResFlightRel{!DIST=SUB(FlightConnectionRel/@DISTANCE);!VALUE=0}$/FlightBookRel{!VALUE=!VALUE+1}/*
Code Example 23, SUB(.../@FIELD), sub functions returning attributes

In the early days a new parser was instantiated for any sub query. This was changed and integrated into the syntax as some future features were too difficult with several installations of BPath.

Note that on this way only one field can be returned with every sub function.

Well, but there are other ways:

~*/SearchResFlightRel{!DS=SUB(FlightConnectionRel/*);!VALUE=0}$/FlightBookRel{!VALUE=!VALUE+1}/*
Code Example 24, SUB (.../*), sub functions returning structures or tables

Looks quite similar to the code above, but instead of attribute we sub-request a structure. This structure is consequently included into the target structure as reference. You may do the same with subs returning tables, which are also included as references.

Goal of this feature is the possibility to assemble hierarchical structured return data of course.

Some further remarks:

You may nest sub-functions even further and run a sub-query within a sub query.

One may transfer data from the embedding query to the sub query using the global variable table (see below). But it is not possible to access the data of the embedding structure from within the sub query.

String functions

To ease working with strings, some string functions were introduced as the following:

SearchResFlightRel/FlightBookRel[strcomp(@PASSNAME,"CA","äöüÄÖÜ")]/*$
Code Example 25, STRCOMP(), string comparisons

The STRCOMP function offers the string comparison functionalities as known in ABAP. These are: "CA", "CS", "CP", "CO", "CN", "NA", "NS" and "NP". The parameters are all of type string in the same order as in ABAP; this means that the comparator has to be specified (as string) as second parameter.

The function returns a Boolean Value.

For the often used CP (covers pattern) comparison a different syntax is also available:

SearchResFlightRel/FlightBookRel[@PASSNAME~="*sch*"]/*$
Code Example 26, ~=, covers pattern

The basic string functions LEN( string ), LEFT( string, len ) , RIGHT ( string, len ) and MID( string, offset, len ) were also included. In contrast to ABAP they are not dumping if len is exceeding the string length (then the complete string is returned) or if the offset exceeds the length (then an empty string is returned).

~LISTZEILE/SearchResFlightRel/FlightBookRel[LEN(@PASSNAME)>0][LEFT(@PASSNAME,1)=UPPER(RIGHT(@PASSNAME,1))]{!ZEILE=MID(@PASSNAME,1,LEN(@PASSNAME)-2)}$
Code Example 27, LEFT, RIGHT and CENTER ... oops sorry MID. And LEN.

Code example takes all non-empty Passenger Names where the first and the (uppercased) last character are equal and returns the content in between.

Get(), Local and global assignments and the global variable table

Well, we talked quite a lot on assignments, somehow navigating around the question whether "=" or ":=" should be used. Maybe it is time to squeeze some words on this topic between all the examples:

The original version followed a simple approach when it came to assignments. The return content was simply never cleared and overwritten by the next row (at least for the standard case). This is fine in most of the cases but when we used dereferenced targets for transposing data it turned out that the data was never cleared and hence incorrect. Unfortunately there is also no way to clear the data manually.

Hence we switched the concept completely and introduced the concept of locality and globality. Since the targets are not declared, the difference has to be expressed with the assignment itself.

A local target will be cleared after a row is written to the return table. The content of a global target will 'survive' the writing to the return table. Locality is expressed with the = operator, for a global assignment the := operator has to be used.

Technically the return structure will be cleared completely after adding a row to the return table. The content of the global targets is then filled again as done with the last assignment which is stored in the global variable table (see next chapter).

Note the usage as follows:

1. For normal fields written by each and every row local assignments with = should be used.
2. (De-referenced) Entries only touched by selected rows and valid only for these rows local assignments must be used.
3. Summation (concatenation...) fields must use global assignments.

In case you do not like to use mixed types of assignments you can avoid the := operator by simply using global variables in all cases were the globality is needed.

Oem, ok, but what are global variables?

Ah, here they come:

~*/SearchResFlightRel{!1=0;!!SUM=0}$/FlightBookRel{!1=!1+1;!!SUM=!!SUM+(1000*@LUGGWEIGHT);!AVG=!!SUM%!1}
Code Example 28, !1= or !!VAR=, aggregations with global variables

This is basically the same example as number 21, solved with global variables, so the intermediate variables do not appear in the return structure.

As learnt before, you may assign data to targets which are part of the returned data. Sometimes the user might want to calculate and store data which is not used immediately within the target structure. To do so the Global Variable Table was introduced which stores all variables. Technically it's a hash table with the variable name as key.

In general the following rules apply:

  • All identifiers beginning with a number are treated as variable, e.g. !1 refers to a variable and also !0Var. !Var refers to a target (means an attribute in the returned structure). Anyway attribute names beginning with numbers are not allowed in data structures.
  • In case you want to use speaking names without numbers the following syntax may be used !!Variable. The second ! indicates that it refers to a variable.
  • A variable is always global, hence it lives "forever". At least till the complete statement is executed.
  • All sub-queries share the same variable table.
  • A variable is initialized with the first usage on the left side of an assignment.

In some cases the initialization of aggregation data is very hard or even impossible. In case a query starts from a collection the syntax does not allow any assignment on the highest level (which is the collection). This means, if the collection contains 3 items, any assignment is executed at least 3 times.
For dereferenced summations it might be also impossible to know whether the target is available or must be instantiated. So we introduced a GET(x,y) method, which has the following semantics:

If x can be evaluated x is returned. If the evaluation of x leads to the access of a not initialized variable / target or attribute (last one makes hardly sense, since it is known at design time) y is used to initialize the return.

So a typical usage is !s=GET(!s,0)+1. Please note that the failed access is propagated, this means a statement like !s=GET(!s+1,1) would lead to the same result as the statement before. GET methods can be nested also.

The following example illustrates a non-trivial usage of the GET function, in a scenario where it can not be avoided since the field is added dynamically.

~*/SearchResFlightRel$/FlightBookRel{!1=LEFT(@PASSNAME,1);!1=IFF(!1="","EMPTY",!1);!1^!:=GET(!1^!,0)+1}
Code Example 29, GET, dynamic counters

The code extracts the first character of the passenger name. The iff statement is required to avoid creating fields with empty names (leading to a short dump). Then we try to access the column with the corresponding name ( e.g. column A ). If it is available we increase its value by one, otherwise we create it newly.

Before closing this lesson I have to make a small confession. Above example and also example 22 is only working after applying note 1586957 which solves several smaller problems I identified over the past days. The note also introduces the possibility to 'keep the global variable table alive'. This means you might use information stored by one BPath query at the execution of another BPath query.

I will add a small chapter on the available notes in one of the coming lessons, currently 4 notes are recommended.

That's it for today. Lesson 6 will bring sorting and unstructured returns. And some performance investigations in case I manage to do so.