Quantcast
Channel: SCN : Document List - SAP Planning and Consolidation, version for SAP NetWeaver
Viewing all articles
Browse latest Browse all 192

How-To: Write default.lgf

$
0
0

In this article I decided to accumulate some knowledge regarding default.lgf scripts.

 

Purpose of default.lgf:

 

To perform calculations triggered by user data send by some input schedule or journal. It can be also launched (if user selects the option) at the end of some standard DM chains (Copy, Move, Import, etc..).

 

For DM chains like DEFAULT_FORMULAS used to run scripts the default.lgf is NOT triggered.

 

Scope of default.lgf

 

When launched the default.lgf will receive scope as a combination of all members of all dimensions of data sent by user and actually saved to the cube. If some records are rejected by write back or validation badi then the scope of default.lgf will not contain the rejected members combination.

Example:

 

Dimension: DIM1

Members: D1M1, D1M2

 

Dimension: DIM2

Members: D2M1, D2M2

 

Input form (all intersections in the cube has value 1):

 

 

The user decided to change value in the cells marked with yellow to 2:

 

 

2 values (D1M1,D2M1) and (D1M2,D2M2) will be sent to the cube.

 

As a result the scope will be a combination of the following members: D1M1,D2M1,D1M2,D2M2

Generating 4 possible combinations:

 

Sent by user: (D1M1,D2M1); (D1M2,D2M2) and extra: (D1M2,D2M1) and (D1M1,D2M2)

 

4 values will be processed by default.lgf.

 

If the default.lgf is like:

 

*WHEN ACCOUNT //or any dimension

*IS * //any member

*REC(EXPRESSION=%VALUE%+1)

*ENDWHEN

 

The result will be:

 

 

It means, that some extra combinations of members will be processed by default.lgf, not only changed data.

 

General rules:

 

1.Don't use *XDIM_MEMBERSET/*XDIM_ADDMEMBERSET in the default.lgf, do not redefine the scope. The original scope (not huge by the way) have to be processed.


2.Use *IS criteria in *WHEN/*ENDWHEN loop to select members for some calculations.

 

Sample:

 

For DM package script the code is like:


*XDIM_MEMBERSET SOMEDIM=%SOMEDIM_SET%  // member from user prompt - MEMBER1 or some fixed member

*WHEN SOMEDIM

*IS * // scoped in *XDIM_MEMBERSET

*REC(...)

*ENDWHEN

 

For default.lgf the code will be:

 

*WHEN SOMEDIM

*IS MEMBER1 // fixed member - condition to perform calculations in REC

*REC(...)

*ENDWHEN

 

3.*XDIM_FILTER can be used sometimes to narrow the scope, but the benefit of filtering against *IS is not clear.

 

Example:

 

ACCOUNT dimension contains 3 members: ACC1,ACC2,ACC3

 

*XDIM_FILTER ACCOUNT = [ACCOUNT].properties("ID") = "ACC1"

// The incoming scope will be filtered to ACC1 if present

*WHEN ACCOUNT

*IS *

*REC(EXPRESSION=%VALUE%+1) // +1 for ACC1

*ENDWHEN

 

*XDIM_MEMBERSET ACCOUNT=%ACCOUNT_SET%

// Filter is reset, %ACCOUNT_SET% contains original scope

*WHEN ACCOUNT

*IS *

*REC(EXPRESSION=%VALUE%+2) // +2 for ACC1,ACC2,ACC3

*ENDWHEN

 

*XDIM_FILTER ACCOUNT = [ACCOUNT].properties("ID") = "ACC2"

// The incoming scope will be filtered to ACC2 if present

*WHEN ACCOUNT

*IS *

*REC(EXPRESSION=%VALUE%+3) //+3 for ACC2

*ENDWHEN

 

User send 1 for all 3 accounts (ACC1,ACC2,ACC3). The result is:

 

ACC1: 4

ACC2: 6

ACC3: 3

 

You also have to be on some recent SP level for *XDIM_FILTER to work correctly (read notes - search on "XDIM_FILTER")

 

If you have to calculate some function, like:

 

Result = Func([SomeDim].[Member1],[SomeDim].[Member2],..,[SomeDim].[MemberN]) (N members total)

 

And store the Result in some member, then you have to write N *WHEN/*ENDWHEN loops to prevent aggregation if more then 1 member is in scope. Without multiple loops the result will be multiplied M times, where M is number of different members sent by input form simultaneously.

 

Example (multiply 3 members):

 

*WHEN SomeDim

*IS Member1

*REC(EXPRESSION=%VALUE%*[SomeDim].[Member2]*[SomeDim].[Member3],SomeDim=ResultMember)

*ENDWHEN

 

*WHEN SomeDim

*IS Member2

*REC(EXPRESSION=%VALUE%*[SomeDim].[Member1]*[SomeDim].[Member3],SomeDim=ResultMember)

*ENDWHEN

 

*WHEN SomeDim

*IS Member3

*REC(EXPRESSION=%VALUE%*[SomeDim].[Member1]*[SomeDim].[Member1],SomeDim=ResultMember)

*ENDWHEN

 

In this example the REC line can be the same for all 3 loops (%VALUE% can be replaced by direct member reference):

 

*REC(EXPRESSION=[SomeDim].[Member1]*[SomeDim].[Member2]*[SomeDim].[Member3],SomeDim=ResultMember)

 

with minimum performance decrease.

 

Using LOOKUP to the same model to get expression argument member

 

In some cases for simple formula like multiplication of 2 members (price * qty), but with long list of members, LOOKUP can be used:

 

Lets assume we have members in dimension SomeDim:

 

Price1, Price2, Price3, Price4

Qty1, Qty2, Qty3, Qty4

 

Result have to be written to:

Amount1, Amount2, Amount3, Amount4

 

Then we can add for dimension SomeDim properties: MULT, RESULT and TYPE and fill it:

 

ID           MULT     RESULT    TYPE

Price1    Qty1       Amount1    Price

Price2    Qty2       Amount2    Price

Price3    Qty3       Amount3    Price

Price4    Qty4       Amount4    Price

Qty1       Price1    Amount1    Qty

Qty2       Price2    Amount2    Qty

Qty3       Price3    Amount3    Qty

Qty4       Price4    Amount4    Qty

 

Code will be:

 

*LOOKUP SameModel

*DIM M:SomeDim=SomeDim.MULT //Get member ID stored in property MULT

*DIM MEASURES=PERIODIC //The default storage type of SameModel

*ENDLOOKUP

 

*XDIM_MEMBERSET MEASURES=PERIODIC //The default storage type of SameModel


*FOR %T%=Price,Qty //Or 2 loops - to prevent aggregation.


*WHEN SomeDim.TYPE

*IS %T%

*REC(EXPRESSION=%VALUE%*LOOKUP(M),SomeDim=SomeDim.RESULT)

*ENDWHEN


*NEXT

 

The lines *DIM MEASURES=PERIODIC and *XDIM_MEMBERSET MEASURES=PERIODIC are required for default.lgf (not required in DM package script)!

 

*FOR/NEXT Loops

 

In general long and nested *FOR/*NEXT loops have to be avoided due to terrible performance. In most cases instead of *FOR/NEXT loops some property can be created and used in the script code.

 

Using some value stored as property in calculations

 

Sometimes it looks as a good idea to store some value in a property and to use it in calculations. Actually it's a bad idea - you can't directly reference the property value in the expression, you have to use some %VAR% and long *FOR/*NEXT loop. Always store values in SIGNEDDATA, may be use some dummy members.

 

SIGN and ACCTYPE in EXPRESSION calculations

 

The calculations in default.lgf use different sign conversion logic with ACCTYPE then the script run by DM package. As a result the same script can produce different results as a default.lgf and as a script in DM package.

 

For default.lgf (BPC NW 10) all values read in the script scope are sign coverted based on ACCTYPE property and the result of EXPRESSION calculation is also sign converted based on ACCTYPE property of the target account:

 

SignedData_Result = if(Result.ACCTYPE=INC,LEQ, -1, 1) * Function(if(Argument1.ACCTYPE=INC,LEQ, -1, 1) * SignedData_Argument1, if(Argument2.ACCTYPE=INC,LEQ, -1, 1) * SignedData_Argument2, ...)

 

Example:

Dimension ACCOUNT: Members: A, B, C

 

ID   ACCTYPE

A    INC

B    EXP

C    INC

 

default.lgf

 

*WHEN ACCOUNT

*IS A

*REC(EXPRESSION=%VALUE%+[ACCOUNT].[B],ACCOUNT=C)

*ENDWHEN

 

The data sent by user in the input form will be:

 

A: 5

B: 10

 

This data will be stored as SIGNEDDATA:

 

A: -5

B: 10

 

Calculations:

 

(-1 * -5 + 1 * 10) * (-1) = -15 (SignedData_Result)

 

And on the input form:

 

C: 15

 

The same script launched by DM package (BPC NW 10) will not have any sign conversions, all calculations will be done with SGNEDDATA values:

 

-5 + 10 = 5

 

The result on the report:

 

C: -5

 

*DESTINATION_APP

 

If it's required to send data to the different model the *DESTINATION_APP statement can be used in default.lgf.

Sign conversion logic is also applicable to writing data using *DESTINATION_APP.

 

The same rules are applicable to *WHEN/*ENDWHEN loop after the *DESTINATION_APP (by the way, in BPC NW 10 *DESTINATION_APP statement is valid only to the next *WHEN/*ENDWHEN loop, have to be repeated before each *WHEN/*ENDWHEN sending data to other application (in BPC NW 7.5 all *WHEN/*ENDWHEN loops after single *DESTINATION_APP will write to target cube).

 

If some dimension is missing in the destination model *SKIP_DIM=SomeDim have to be used. But the issue can be in the following case:

 

SourceModel:

DimMissingInTarget: Member1, Member2, ..., MemberN (base) - having root parent All

SomeDim: Mem1, Mem2, ... - dimension in both Source and Target

 

TargetModel:

SomeDim: Mem1, Mem2, ... - dimension in both Source and Target

 

If some of Member1, Member2, ..., MemberN is changed in SourceModel the result of All have to be transferred to TargetModel

 

The code in default.lgf of SourceModel will be:

 

//some calculations in the SourceModel

...

 

*FOR %M%=Member1,Member2,...,MemberN //list of base members of the skipped dimension

 

*DESTINATION_APP=TargetModel

*SKIP_DIM=DimMissingInTarget

 

*WHEN DimMissingInTarget

*IS %M%

*WHEN SomeDim //SomeDim - dimension existing both in Source and Target

*IS Mem1,Mem2,... //some list of members of SomeDim changed by user and to be transferred to TargetModel

*REC(EXPRESSION=[DimMissingInTarget].[All]) //Parent All value is used!

*ENDWHEN

*ENDWHEN

 

*NEXT

 

N loops for N base members of DimMissingInTarget (useful for small N)

 

Another option for this particular case is to explicitely scope the scipped dimension with *XDIM_MEMBERSET:

 

*XDIM_MEMBERSET DimMissingInTarget=<ALL>

*DESTINATION_APP=TargetModel

*SKIP_DIM=DimMissingInTarget

 

*WHEN SomeDim //SomeDim - dimension existing both in Source and Target

*IS Mem1,Mem2,... //some list of members of SomeDim changed by user and to be transferred to TargetModel

*REC(EXPRESSION=%VALUE%)

*ENDWHEN

 

But in this case you have to put this code at the end of the default.lgf or restore original scope for DimMissingInTarget:

 

*XDIM_MEMBERSET DimMissingInTarget=%DimMissingInTarget_SET% // %xxx_SET% variable always contains the original script scope.

 

Custom Logic BADI in default.lgf

 

It's also possible to call Custom Logic BADI in default.lgf to perform some calculations that are not easy or even not possible to implement using script logic. The badi have to work with the current scope and can contain some fixed parameters.

 

Example:

 

//Some calculations before badi call

...

 

*START_BADI SOMEBADI

QUERY=ON //to get records from the current scope

WRITE=ON //to use default write to cube

DEBUG=OFF

SOMEPARAM=SOMEFIXEDVALUE

...

*END_BADI // Script scope will be reset to initial script scope here if changed before

 

//Some calculations after badi call

...

 

RUNLOGIC_PH BADI

 

It's also possible to use RUNLOGIC_PH BADI (How To Implement the RUNLOGIC_PH Keyword in SAP... | SCN) to speed up some calculations using CHANGED parameter. For example - single change of price have to recalculate values in multiple entities and multiple time periods.

 

*START_BADI RUNLOGIC_PH

QUERY=OFF

WRITE=ON

LOGIC = CALLED_LOGIC.LGF

APPSET = SameEnvironment

APP = SameModel

DIMENSION ENTITY=BAS(ALLENTITIES)

DIMENSION SomeDim=%Somedim_SET% //script initial scope

...

CHANGED=ENTITY

 

Write Back BADI instead of default.lgf

 

The same functionality can be achieved by Write Back BADI - perform calculations triggered by user input. The details are described here: Calculations in Write Back BADI - Default.lgf R... | SCN

The significant difference between Write Back BADI and default.lgf is that Write Back BADI will receive data sent by user before it's stored in the cube and only sent values will be processed.

 

B.R. Vadim

 

P.S. 2014.06.11 - incorrect case about function with "+/-" removed.

P.P.S. 2014.07.23 - sample for scope added

P.P.P.S. 2014.09.25 - *XDIM_FILTER functionality described

P.P.P.P.S. 2016.02.26 - effect of write back badi on the scope of default.lgf

P.P.P.P.P.S 2016.05.30 - LOOKUP - *DIM MEASURES=PERIODIC and *XDIM_MEMBERSET MEASURES=PERIODIC effect


Viewing all articles
Browse latest Browse all 192

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>