Professional Documents
Culture Documents
BPC Script Logic For Dummies
BPC Script Logic For Dummies
chapter 1
When I started in BPC I couldn't find much information about Logic Scripts,
and I accidentally came across a blog that changed my life, which is why I
want to share it for those of us who are in the SAP BPC world.
There are some documents and help files on script logic and HTG, but end
users may feel that it is not easy. I agree with that, but if you understand its
structure and concept, I can guarantee that you can read and understand what
it means and what the purpose of that script logic is. In addition to that, it is
possible to enable it to modify or create it. It's the same as writing a book is
not easy, but reading a book is a different story.
Let's learn it step by step.
1 reach
Unlike other script engines, there is no temporary variable in the logical script
engine, so it will create a record that has the same fact table structure and
replace or change its value by the '*REC' command. (Note: *REC means
'Record'.)
Here is the grammar of the *REC statement
*REC [([FACTOR | EXPRESSION = {Expression} [, {dim1} = {member},
{dim2} =?)]
It is easier? I hope so
Please note below rule.
o It means you have a scope of 1 record, but you can create multiple
records using this.
Therefore, you can imagine that there will be multiple * REC statements
in your currency conversion script logic.
The destination cell can be the same. All values will be accumulated.
The *REC statement will generate a new record, so it doesn't matter, even
if the destination is the same.
3. As a final step, we need to write data
to the database.
I explained 3 basic parts of script logic in the last post BPC Script logic for Dummies, chapter
We found out how to use XDIM_MEMBERSET last time. *XDIM_MEMBERSET is for scope
To achieve this, we need to use the ACCTYPE property which has the value of account type.
AST is the value for the ASSET account. (Note: The value is based on the BPC APSHELL.)
ID ACCTYPE
Extsales INC
CASH AST
TAXES EXP
NETINCOME INC
Suppose you have already used several *XDIM_MEMBERSET commands, and then data is
<Result>
So only one record will be selected from the above result because CASH is the only member of
the account that has the value 'AST' of the ACCTYPE property.
<Result>
We just figured out how to scope the source data based on the property. So someone could ask
this question. Can we base it on value? For example, can we select all data that has an account
example.
[*QUERY_TYPE = 0 | 1 | 2] // optional
* ENDXDIM
* APP = PLANNING
* ENDXDIM
d. The member value of CE0001000 of dimension P_ACCT must be greater than 100000
The answer is
The following records will not be selected even if P_ACCT is CE0001000 because its value is
less than 1000 or Datasrc is not INPUT or is not the secondary member of AAPJ (Asia Pacific)
CE0001000, 2011.FEB, BUDGET, INPUT, TURKEY, 1999 (Türkiye is not a member of AAPJ)
CE0001000, 2011.FEB, BUDGET, INPUT, JAPAN, 450 (value is less than 1000)
Here are some important notes for using this command.
Note 2 : If you do not specify the scope of each dimension, it will be performed on the
corresponding members of the preselected region that is defined with XDIMMEMBERSET from
Note 3 – This command will generate an MDX statement so it takes longer to execute. If your
data set only has base members, you can use *XDIM_GETINPUTSET . (Please refer to the
help file)
Let's say a user wants to add a USAS sales entity over a predefined set of members. In that
* XDIM_ADDMEMBERSET Entity = USASales The main reason we need this is that sometimes
For example, IN BPC NW, *XDIM_MEMBERSET = BAS (US), CANADA will not work.
execute a specific set of members every time the logic is executed, they must use
XDIM_ADDMEMBERSET
Sometimes we need to save our scope data in a script logic variable. But what if your dimension
members are updated frequently? As I know almost all clients update their dimension at least
once a month. If the client changes its dimension members, what will happen in your script
logic? You can use *Filter, but sometimes it may not work all the time.
We can then use the *SELECT and *MEMBERSET commands as a dynamic scoping tool. Like
other scripting engine, Logic script also supports Variable to save some data. The variable is
defined by the % symbol. Here are some examples, %MYTIME%, %CUR%, etc.
So how can we save some data in the variable and when can it be used? Generally, the
variable can be filled using the * SELECT command and the *MEMBERSET command. Both
are scope commands, but *SELECT will be faster because it will create an SQL statement.
This command will get the member ID of the currency dimension in which the GROUP property
has the value 'REP'. Actually, it will create a SQL statement like below:
SELECT ID from mbrCurrency where [GROUP] = 'REP'
After the above SQL command is executed, all results will be saved in the variable
%REPORTING_CURRENTIES%.
Here is an example of *MEMBERSET that will do the same result, but will execute the MDX
Let's assume that the dimension of the coin has below members.
GROUP ID
USD REP
EUR REP
KRW
JPY
When you define and populate the data with *SELECT and *MEMBERSET , please remember
version.
Example script:
<Scope record>
<Log generated>
DISCOUNTED_EXTSALES , 2011.JAN, BUDGET , USA, 9000
What if your scope record is not a single record, but multiple records?
<Scope record>
<Log generated>
DISCOUNTED_EXTSALES , 2011.JAN, BUDGET , USA. USA, 9000
DISCOUNTED_EXTSALES , 2011.JAN, BUDGET , KOREA, 2700
DISCOUNTED_EXTSALES , 2011.JAN, BUDGET , CANADA, 4500
As you can see, we change the count value, the category value, and the signed
data value (or measurement value) using the *REC statement; the other
dimension not specified in the *REC statement will be the same as the scope
data, so 2011.JAN and each country (entity) will not be changed.
<Scope record>
<Log generated>
EXTSALES, 2011.JAN, CURRENT, 30000
What happens if you want to add or divide ? then you should use the
expression . The EXPRESSION is any formula that will result in the new
value to be published. The formula can include regular arithmetic operators,
fixed values, and the script logic keyword %VALUE%, this represents the
original value retrieved from the scope record. Here is an example:
<Scope record>
<Log generated>
EXTSALES, 2011.JAN, CURRENT, 15000
Now we have the basics of the *REC statement, but you can ask the
following questions .
“There are some that are data-scoped and need to do different calculations
based on each member of the specific dimension.”
“I need to copy a value to multiple destinations”
“How can I get the value from the other app?”
“I want to use some value from other records to calculate the result”
“Can I use a property value to calculate the result? ”
“The script logic can handle the above requirements”
I'll explain the first question in this post and ask others in the next post.
“There is some scope data and I need to do some calculations based on each
specific dimension member.”
Yes. This is why you MUST use the *REC statement with * WHEN ~ * IS ~
* ELSE ~ * ENDWHEN .
Suppose you want to create salary forecast values and place them in the
forecast category based on the country's actual salary values for January 2011.
We need to increase 10% for the US, 5% for Canada and 3% for other
countries . Let's assume that the ENTITY dimension has country information.
To do this, you need to first scope:
*XDIM_MEMBERSET ACCT = SALARY
Finally, you must specify a condition for each *REC statement. To do this,
you MUST use the *WHEN~*IS~*ELSE~ENDWHEN statement.
First , write *WHEN and *ENDWHEN outside the *REC statement
*WHEN
* REC (FACTOR = 1.1, CATEGORY = “PROGNOSIS”) // 10%
* REC (FACTOR = 1.05, CATEGORY = “forecast”) // 5%
* REC (FACTOR = 1.03, CATEGORY = “PROGNOSIS”) // 3%
*ENDWHEN
NOTE: It is not necessary to use code indentation in the script logic, but I
would like to recommend using it for better readability.
Second , type a dimension name that you want to compare next to *WHEN .
In this example, it will be dimension ENTITY.
*WHEN ENTITY
* REC (FACTOR = 1.1, CATEGORY = “PROGNOSIS”) // 10%
* REC (FACTOR = 1.05, CATEGORY = “PROGNOSIS”) // 5%
* REC (FACTOR = 1.03, CATEGORY = “PROGNOSIS”) // 3%
*ENDWHEN
Third , place the *IS statement above each *REC statement and the *ELSE
statement above the last *REC statement. We need two statements *IS and
*ELSE because there are two conditions and others will be calculated as one
condition.
*WHEN ENTITY
*ES
*REC (FACTOR = 1.1, CATEGORY = “forecast”) // 10%
*ES
* REC (FACTOR = 1.05, CATEGORY = “forecast”) // 5%
*ELSE
* REC (FACTOR = 1.03, CATEGORY = “PROGNOSIS”) // 3%
* ENDWHEN
*WHEN ENTITY
*IS USA
*REC (FACTOR = 1.1, CATEGORY = “forecast”) // 10%
*IS CANADA
*REC (FACTOR = 1.05, CATEGORY = “PROGNOSIS”) // 5%
*ELSE
*REC (FACTOR = 1.03, CATEGORY = “PROGNOSIS”) // 3%
*ENDWHEN
Fourth , put * COMMIT at the end of the script so that the logic engine can
post data to the database.
*WHEN ENTITY
*IS USA
*REC (FACTOR = 1.1, CATEGORY = “forecast”) // 10%
*IS CANADA
*REC (FACTOR = 1.05, CATEGORY = “PROGNOSIS”) // 5%
*ELSE
*REC (FACTOR = 1.03, CATEGORY = “PROGNOSIS”) // 3%
*ENDWHEN
*COMMIT
Note 1: You can use multiple condition value like *IS VALUE_A, VALUE_B
Note 2: You can use >, <= with numeric value with the *IS statement,
example *IS > 4; defaults to equals (=), so it will be fine, even if you don't
specify it.
Note 3: AND, OR and NOT cannot be used with *IS
Note 4: (double quote) is not required to compare the string value with the
*IS statement.
Note 5: * The WHEN statement can be nested. For example,
*WHEN xxx
*IS “A”
*REC (…)
*REC (…)
*IS “B”
*REC (…)
*WHEN yyy
*IS “C”, “D”, “E”
*REC (…)
*ELSE
*REC (…)
*ENDWHEN
*ENDWHEN
Note 6: You can use the value of a property with the *IS statement. Example:
*IS Intco.Entity
The simplest example is currency conversion because you need to read the rate value from the
NOTE: *LOOKUP / *ENDLOOKUP can also be used to read the value of the current
application.
*LOOKUP {Application}
*DIM [{LookupID}:] {DimName} = “Value” | {CallingDimensionName}[.
{Property}]
[*DIM …]
*ENDLOOKUP
{Application} is the name of the application that will retrieve the value.
{LookupID} is an optional variable that will hold the value so you can use it
in the script. This is only necessary when multiple values must be retrieved.
1. You need to get the rate values from the rates app for currency conversion (LC to USD and
EUR).
2. The member ID of the RATE dimension in the rate application must be the same as the
3. The member ID of the RATEENTITY dimension in the rate application must be “DEFAULT”
First, you need to define *LOOKUP with the name of the application.
*LOOKUP RATE
* ENDLOOKUP
Second, specify the dimensions of the RATE application with the *DIM statement.
(Suppose the rate application has the dimension RATEENTITY, INPUTCURRENCY, RATE,
*LOOKUP RATE
*DIM RATEENTITY
*DIM INPUTCURRENCY
*DIM RATE
*DIM CATEGORY
*DIM TIME
* ENDLOOKUP
Third, assign the member ID value of each dimension from the current
application (Finance) or use a fixed value. If you need to retrieve multiple
values according to different member ID values of specific dimensions, make
copies of that dimension and assign different values.
*LOOKUP RATE
*DIM RATEENTITY = “DEFAULT” // Fixed value
*DIM INPUTCURRENCY = “USD” // Fixed value
*DIM INPUTCURRENCY = “EUR” // Fixed value, copy the same
dimension for another value
*DIM INPUTCURRENCY = ENTITY.CURR // added one more for
currency conversion as variable value
*DIM RATE = ACCOUNT.RATETYPE // Variable value based on current
application
*DIM CATEGORY
*DIM TIME
*ENDLOOKUP
Fourth, place variables for multiple recall values in front of each duplicate dimension name.
*LOOKUP RATE
*DIM TIME
* ENDLOOKUP
Note: If you want to get some value based on two or more dimensions, you must use the
*LOOKUP OWNERSHIP
*ENDLOOKUP
Although the member ID of the ACCOUNTOWN dimension is the same, the variable must
be defined as a different variable because the member ID of the ENTITY dimension is different
in the join. If the 'ENTITY' property of the INTCO dimension has a value I_FRANCE, *LOOKUP
will then select two records and each variable will have a different value.
Lastly, remove dimension names (TIME and CATEGORY> that do not have any fixed value
*LOOKUP RATE
*DIM RATEENTITY = “DEFAULT”
*DIM SOURCECUR: INPUTCURRENCY = ENTITY.CURR
*DIM DESTCURR1: INPUTCURRENCY = “USD”
*DIM DESTCURR2: INPUTCURRENCY = “EUR”
*DIM RATE = ACCOUNT
*ENDLOOKUP
So how can we use these values? You can use it by using
LOOKUP(Variable) in your *REC declaration as shown below
* WHEN ACCOUNT.RATETYPE
“USD”)
“EUR”)
*ENDWHEN
NOTE: You can use LOOKUP (variable) with *WHEN and *IS statement
*ENDWHEN
We review how to define the *LOOKUP / *ENDLOOKUP statement and how to use it.
Now it's time to find out how it works in the scripting engine.
Let's assume the following logs are in the velocity application and see what will happen during
Here are your current funding request records that need to be processed.
As you can see, there is no relationship between the finance app and the rates app. We know
that the currency of Switzerland is CHF, but there is no information in each record of the fact
So how can the script logic find the value and calculate it?
The key point is 'ENTITY.CURR', which we use to assign the dimension as shown below.
ENTITY.CURR means the 'CURR' property value of the ENTITY dimension. Therefore,
Switzerland, which is one of the members of the Entity dimension, must have the value 'CHF' in
Therefore, the value of the 'RATETYPE' property of the INVENTORY and REVENUE account
Therefore, the Script engine will perform the following steps to process the first record in the fact
table.
2. Select INPUTCURRENCY = “ CHF ” ( because the current value of the entity member's
OR INPUTCURRENCY = “USD”
OR INPUTCURRENCY = “EUR”
3. Select RATE = “ END ” ( because the value of the checking account member's
5. Select TIME = “2011.JAN” (there is no declaration, so it is the same as the TIME value of the
current application).
each variable.
DEFAULT, EUR, END, CURRENT, 2011.JAN, 1.24 => DESTCURR2 will be 1.24
DEFAULT, CHF, END, CURRENT, 2011.JAN, 0.93 => SOURCECUR will be 0.93
After the script logic engine executes the following statements, it will generate 2 records.
* WHEN ACCOUNT.RATETYPE
“USD”)
“EUR”)
*ENDWHEN
For the second record in the fact table, the 3 records below will be selected from the rate
After you process the 'REVENUE' records, there will be 6 records in the fact table as shown
below.
Question 1: What if the rate application does not have the value?
Question 2: I don't have any records in the current application's fact table. What will happen?
The script logic always reads logs from the current application. Therefore, if there are no
member)?
The MS version can do it with the *OLAPLOOKUP statement instead of *LOOKUP, but the NW
Like other program or script language, logic script also supports LOOP
statement. Let's see how it works.
Here is the syntax of the *FOR – *NEXT statement.
*FOR {variable1} = {set1} [AND {variable2 = {set2}]
{another statement...}
*NEXT
And here is an example.
2. The variable % CURR% will be replaced by two values USD and EURO.
because the *REC statement exists inside the *FOR – *NEXT statement.
*REC (FACTOR = 1.1, TIME = 2011.OCT, CURRENCY = USD)
*REC (FACTOR = 1.1, TIME = 2011.OCT, CURRENCY = EURO)
Suppose the variable %CURR% has USD, EURO, CHF, KRW and then it
will be translated as follows.
*NEXT
If the user is using the *REC statement, the user must write 36 statements
instead of a simple *FOR – *NEXT statement above.
(NOTE: BPC NW supports a nested FOR – NEXT in version 7.5)
Additionally, the user can use two sets of variables like the example below.
If the first variable has fewer values than the second variable,
If the first variable has more values than the second variable,
This is very useful when we use script logic with dynamic data set.