27
BPC Script logic for Dummies? (Part 1) Posted by James Lim in SAP Planning and Consolidation, version for SAP NetWeaver on May 24, 2011 10:45:56 AM in Share0 Even though I have a long experience with BPC, if someone asks me to a write a script logic, I might not write that code within 10 minutes. It is not a problem of my knowledge but writing a script logic needs to understand your financial requirements and knowledge of your application like account member id and name of properties. There are some documents and help files about the script logic and HTG but end users may feel that is not easy. I agree with that but if you understand its structure and concept, I can guarantee you can read and understand what it means and what the purpose of that script logic is. On top of that, you might be enable to modify or create it. It is the same thing that is not easy to write a book but reading a book is a different story. Let’s learn it step by step. 1. Understand 3 logic parts. Logic is a special script engine and it consists of 3 parts. Scoping, calculation(create/Record) and Writing. 2. Scoping BPC is based on NW BI or MSAS which has a lot of data. Therefore, if you don't specify scope, it will take a lot of time to read data. Let's say you need to calculate 2011.January, actual data and only one account like 'Discounted External sales' based on the External Sales. How can we scope this from a big database? The answer is.... *XDIM_MEMBERSET *XDIM_MEMBERSET is using for scope data by each dimension. Here is the grammar of XDIM_MEMBERSET. *XDIM_MEMBERSET <DIMENSIONNAME> = <MEMBERNAME 1>,<MEMBERNAME 2>...<MEMBERNAME n> Now, let't scope above exampe. for scoping 2011.January, *XDIM_MEMBERSET TIMEDIM=2011.JAN for scoping actual, *XDIM_MEMBERSET CATEGORYDIM=ACTUAL for scoping external sales, *XDIM_MEMBERSET ACCOUNTDIM=EXTSALES

Logic Script

Embed Size (px)

DESCRIPTION

Logic Script

Citation preview

Page 1: Logic Script

BPC Script logic for Dummies? (Part 1)Posted by James Lim in SAP Planning and Consolidation, version for SAP NetWeaver on May 24, 2011 10:45:56 AM inShare0 Even though I have a long experience with BPC, if someone asks me to a write a script logic, I might not write that code within 10 minutes.It is not a problem of my knowledge but writing a script logic needs to understand your financial requirements and knowledge of your application like account member id and name of properties.There are some documents and help files about the script logic and HTG but end users may feel that is not easy. I agree with that but if you understand its structure and concept, I can guarantee you can read and understand what it means and what the purpose of that script logic is. On top of that, you might be enable to modify or create it. It is the same thing that is not easy to write a book but reading a book is a different story.Let’s learn it step by step.

1. Understand 3 logic parts.

Logic is a special script engine and it consists of 3 parts. Scoping, calculation(create/Record) and Writing.

 

2. Scoping

   BPC is based on NW BI or MSAS which has a lot of data. Therefore, if you don't specify scope, it will take a lot of time to read data.    Let's say you need to calculate 2011.January, actual data and only one account like 'Discounted External sales' based on the External Sales.

 

   How can we scope this from a big database?

   The answer is....  *XDIM_MEMBERSET

   *XDIM_MEMBERSET is using for scope data by each dimension.   Here is the grammar of XDIM_MEMBERSET. 

         *XDIM_MEMBERSET <DIMENSIONNAME> = <MEMBERNAME 1>,<MEMBERNAME 2>...<MEMBERNAME n>

   Now, let't scope above exampe.   for scoping 2011.January, *XDIM_MEMBERSET TIMEDIM=2011.JAN   for scoping actual,       *XDIM_MEMBERSET CATEGORYDIM=ACTUAL   for scoping external sales,  *XDIM_MEMBERSET ACCOUNTDIM=EXTSALES                                (Note: we need to scope External sales because discounted External sales will be calculated based on the External Sales.) 

 

3. Now, we just finished scoping so it is time to calculate(create) data.

   Unlike other script engine, there is no temporary variable in the logic script engine so it will create a record that has same as fact table structure.   and it will replace or change its value uaing '*REC' command. (Note: *REC means 'Record'.)

Page 2: Logic Script

   Here is the grammar of *REC statement

 

            *REC[([FACTOR|EXPRESSION={Expression}[,{dim1}={member},{dim2}=?)] 

 

   Using that grammar, We can make our script as below.

         *REC (FACTOR = 0.9,ACCOUNT="DISCOUNT_EXTSALES")          Which means multiply by 0.9 to current scoped record and replace account member with DiSCOUNT_EXTSALES

   Here is an example what happens with above statement.    

 

   <Scoped record>          EXTSALES,2011.JAN,ACTUAL,10000

 

  <Generated record>         DISCOUNT_EXTSALES,2011.JAN,ACTUAL,9000

   What if you want to put generated record into BUDGET category?   Then statement should be

         *REC (FACTOR = 0.9,ACCOUNT="DISCOUNT_EXTSALES",CATEGORY="BUDGET")

   Now you want to put 80% value into FORECAST at the same time. what should we do?   We can use another *REC statement at the same time.

 

         *REC (FACTOR = 0.9,ACCOUNT="DISCOUNT_EXTSALES",CATEGORY="BUDGET")         *REC (FACTOR = 0.8,ACCOUNT="DISCOUNT_EXTSALES",CATEGORY="FORECAST")

 

 

   <Scoped record>          EXTSALES,2011.JAN,ACTUAL,10000

 

  <Generated record>         DISCOUNT_EXTSALES,2011.JAN,BUDGET,9000

         DISCOUNT_EXTSALES,2011.JAN,FORECAS,8000

Page 3: Logic Script

 

 

   Getting easier? I hope so

 

   Please keep in mind below rule.        a. Each REC instruction generates ONE new record.        b. Each source record can generate as many records as desired.            It means you scoped 1 record but you can create multiple records using this.           Currency translation is the best example because you need multiple           converted currencies using a local currency record.           Therefore, you can imagine there will be multiple *REC statement           in your currency conversion script logic.        c. Destination cell can be same. All values will be accumulated.           *REC statement will generate a new record so it doesn't matter even though destination is same.

4. As a final step, we need to write data into database.

   script statement is really simple one.

        *COMMIT

 

        Fortunately, it doesn't have any parameter. Just use *COMMIT.         When BPC script engine execute *COMMIT, generated records will be posted        to the table using BPC sending engine which is same engine that you submit        data from the Excel workbook.We reviewed three main parts of BPC Logic script as a first step.I will explain advanced scoping, recording and commit command in the next post.

Page 4: Logic Script

BPC Script logic for Dummies? (Part 2)Posted by James Lim in SAP Planning and Consolidation, version for SAP NetWeaver on Jun 20, 2011 10:19:13 AM inShare0 I explained basic 3 parts of script logic in the last post. It was Scoping, Calculation and writing.We will find out more advanced features for scoping In this post.

 

 

 

1. Scope using member property

 

      We found how to use *XDIM_MEMBERSET last time.      *XDIM_MEMBERSET is for scoping based on the member ID.      What if user wants to scope members based on a specific property value?      For example, a user wants to filter Account dimension members those are Asset.      To achieve this, we need to use ACCTYPE property which has the type value of account.      AST is the value for ASSET account. (Note: Value is based on the APSHELL of BPC.)

      The command is *XDIM_FILTER.       The usage is *XDIM_FILTER <DIMENSIONNAME> = [DIMENSIONName].Properties("Property name") = "Property value"

      So above example can be written as below.

      *XDIM_FILTER ACCOUNT = [account].properties(ACCTYPE='AST')

 

      Let's say Account dimension has 3 members as below.

 

         ID               ACCTYPE     Extsales                INC    CASH                   AST    TAXES                 EXP    NETINCOME       INC

 

    Then  *XDIM_FILTER_ACCOUNT will select CASH member only.

 

 

    Let's assume If you already used multiple *XDIM_MEMBERSET command and below are selected data from the fact tables.

Page 5: Logic Script

 

   *XDIM_MEMBERSET TIME = 2011.JAN   *XDIM_MEMBERSET CATEGORY = BUDGET

 

    <Result>     EXTSALES , 2011.JAN, BUDGET, 9000      CASH     , 2011.JAN, BUDGET, 3000      TAXES    , 2011.JAN, BUDGET,  800      NETINCOME, 2011.JAN, BUDGET, 1500

 

     Now if you add *XDIM_FILTER against ACCOUNT dimension.

    *XDIM_MEMBERSET TIME = 2011.JAN    *XDIM_MEMBERSET CATEGORY = BUDGET    *XDIM_FILTER ACCOUNT = [account].properties(ACCTYPE?='AST')

     Then only one record will be selected from above result because CASH is the only account member that has 'AST' value of ACCTYPE property.

 

    <Result>     CASH     , 2011.JAN, BUDGET, 3000

 

 

 

2. Scope using member value      We just figured out how to scope the source data based on the property.       Then someone might ask this question.       "Can we scope based on the value?

 

      For example, can we select all data that an account value greater than 100?      Of course, we can do it.      The command is *XDIM_GETMEMBERSET. Unlike other command,      it needs  *ENDXDIM command to specify data.      Here is the grammar of XDIM_GETMEMBERSET and an example.

      *XDIM_GETMEMBERSET {dimension} [={member set}]            [*APP={application}] //optional            [*XDIM_MEMBERSET {dimension} [={member set}] //as many of these as needed            [*QUERY_TYPE= 0 | 1 | 2] //optional            *CRITERIA {expression} //required       *ENDXDIM

Page 6: Logic Script

      *XDIM_GETMEMBERSET P_CC=[P_CC].[H1].[AAPJ].CHILDREN            *APP=PLANNING           *XDIM_MEMBERSET P_DataSrc=INPUT           *CRITERIA [P_ACCT].[H1].[CE0001000]>1000        *ENDXDIM 

      It will get data those are..

           a. Children member of AAPJ in the P_CC dimension.  AND           b. from the PLANNING application  AND           c. INPUT member of P_Datasrc dimension AND           d. CE0001000 member's value of the P_ACCT dimension should be greater than 100000

      Let's Assume Fact table has below records.

            CE0001000, 2011.JAN, ACTUAL, INPUT, KOREA , 2500      CE0002000, 2011.JAN, ACTUAL, INPUT, CHINA , 5000      CE0001000, 2011.JAN, ACTUAL, ADJ  , CHINA , 3000      CE0002000, 2011.JAN, ACTUAL, INPUT, JAPAN , 1999      CE0003000, 2011.JAN, ACTUAL, INPUT, JAPAN , 2222      CE0001000, 2011.FEB, BUDGET, ADJ  , KOREA ,  345       CE0001000, 2011.FEB, BUDGET, INPUT, TURKEY, 1999      CE0003000, 2011.JAN, ACTUAL, INPUT, TURKEY, 1100      CE0001000, 2011.FEB, BUDGET, INPUT, CHINA , 1050      CE0001000, 2011.FEB, BUDGET, INPUT, JAPAN ,  450

      Which records will be selected?

      The answer is                CE0001000, 2011.JAN, ACTUAL, INPUT, KOREA, 2500      CE0001000, 2011.FEB, BUDGET, INPUT, CHINA, 1050

      Below records will not be selected even though P_ACCT is CE0001000       because its value is less than 1000       or Datasrc is not INPUT       or it is not the child member of AAPJ (Asia Pacific)

      CE0001000, 2011.JAN, ACTUAL, ADJ  , CHINA , 3000  (datasrc is not input)      CE0001000, 2011.FEB, BUDGET, ADJ  , KOREA ,  345  (datasrc is not input)      CE0001000, 2011.FEB, BUDGET, INPUT, TURKEY, 1999  (Turkey is not child 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 1: This command works only for BPC MS.      Note 2: if you don't specify each dimension's scope, it will be performed in the corresponding               members of the pre-selected region which is defined with XDIMMEMBERSET of previous line or               Passed by Data Manager.

      Note 3: This command will generate MDX statement so it takes more time to execute.                        if your dataset has only base members, you can use *XDIM_GETINPUTSET. (please refer help file)

Page 7: Logic Script

 

 

 

3. When user wants to add more members on top of current scoped data.

 

      Let's say a user wants to add USASales entity on top of predefined memberset.      In that case user defines as below.      *XDIM_ADDMEMBERSET Entity = USASales      The main reason why we need this is          a. Sometimes XDIMMEMBERSET doesn't work with some specific functions like BAS(parent).             For example, IN BPC NW,  *XDIM_MEMBERSET = BAS(US),CANADA will not work.             Therefore, we should use *XDIM_MEMBERSET and *XDIM_ADDMEMBERSET.                          *Note: In BPC MS, BAS() will not work with XDIM_MEMBERSET.          b. if user always wants to run a specific memberset whenever logic runs,               should use XDIM_ADDMEMBERSET

 

 

 

4. Dynamic Scope and saving it to a variable.

 

      Sometimes we need to save our scoped data into a script logic variable.      But... what if your dimension members are updated frequently?      As I know, almost every customer updates their dimension at least once a month.      If customer changes their dimension members, what will happen in your script logic?      You can use *Filter but sometimes it may not work all the time.      Then we can use *SELECT and *MEMBERSET command as a dynamic scope tool.       Like other script engine, Logic script also supports Variable to save some data.      The Variable is defined using % symbol.Here are some examples, %MYTIME% , %CUR% etc.      So how can we save some data into the variable and when it can be used?      Usually, the variable can be filled using *SELECT command and *MEMBERSET command.      Both of them is scope command but *SELECT will be faster because it will create SQL statement.           Here is the grammar of both commands.         *SELECT (, {member set in MDX format})

 

      Let's see how to use *SELECT command.           *SELECT(%REPORTING_CURRENCIES%, “ID”, “CURRENCY”, “[GROUP] = 'REP'”)      This command will get the 'member ID(what)' from the 'currency dimension(From)' that the      GROUP property has the value 'REP' (where).      Actually, it will create a SQL statement as below

           SELECT ID from mbrCurrency where [GROUP] = 'REP'

Page 8: Logic Script

      After it executes above SQL command, all result will be saved into %REPORTING_CURRENCIES% variable.

 

      Here is an example of *MEMBERSET which will make same result but execute MDX statement instead of SQL.                     *MEMBERSET(%REPORTING_CURRENCIES%, Filter{[CURRENCY].members, [currency].properties(GROUP='REP')})                   The variable can be used anywhere in the logic, like in this example:

                        *XDIM_MEMBER_SET CURRENCY=%REPORTING_CURRENCIES%

      Let's assume Currency dimension has below members.

          ID           GROUP         USD            REP         EUR            REP         KRW                    JPY      Then above statement will be converted as                                              *XDIM_MEMBER_SET CURRENCY = USD,EUR 

      When you define and fill in data using *SELECT and *MEMBERSET,      please remember this as 'MEMBERSET Variable'           Note: MEMBERSET command is only supported by MS version.

 

 

 

We reviewed key command of scoping today.We will review advanced calculation command and control command like *IF or *FOR - *NEXT in the next post.

If you have questions or want to know something about it, please leave a comment.

Page 9: Logic Script

BPC Script logic for Dummies? (Part 3)Posted by James Lim in SAP Planning and Consolidation, version for SAP NetWeaver on Aug 4, 2011 7:37:49 PM inShare0 I am sorry for the late posting of this series but I had to take my vacation and needed to get some training about

HANA Let's start to learn how to caluate and write some data using the script logic.Again, the script logic consists of 3 parts; Scoping, Calculationand Writing.

 

1. Basic concept of Writing and *REC statement

  As we saw in my first posting of this series, *REC statement is used for writing data.  You need to keep in mind that *REC will create records based on the scoped records.

  For example, if your scoped record is same as below. 

 

    <Scoped record>          EXTSALES, 2011.JAN, ACTUAL, USA, 10000

 

         and your *REC statement is below.        *REC (FACTOR = 0.9, ACCOUNT="DISCOUNTED_EXTSALES", CATEGORY="BUDGET")

 

    Then your generated record will be 

 

    <Generated record>        DISCOUNTED_EXTSALES, 2011.JAN, BUDGET, USA, 9000

  What if your scoped record is not a single one but multiple record?

    <Scoped record>          EXTSALES, 2011.JAN, ACTUAL, USA,   10000         EXTSALES, 2011.JAN, ACTUAL, KOREA, 3000         EXTSALES, 2011.JAN, ACTUAL, CANADA, 5000

 

  Then your generated records will be 

    <Generated record>         DISCOUNTED_EXTSALES, 2011.JAN, BUDGET, USA,    9000         DISCOUNTED_EXTSALES, 2011.JAN, BUDGET, KOREA, 2700         DISCOUNTED_EXTSALES, 2011.JAN, BUDGET, CANADA, 4500

Page 10: Logic Script

 

  As you can see, we changed Account value, Category value and its signeddata vale (or measure value) using *REC statement.   The other dimension that is not specified in the *REC statement will be same as scoped data so 2011.JAN and each country (entity) doesn't  be changed.

2. Grammar of *REC statement.

   Here is the grammar of *REC statement. You can use FACTOR or EXPRESSION for various calculations for signeddata vale (or measure value).   And specify dimension name and member to change its value.     *REC[([FACTOR|EXPRESSION={Expression}[,{dim1}={member},{dim2}=?)] 

  3. What is the difference between FACTOR and EXPRESSION?

   The FACTOR is a factor(multiply) by which the retrieved amount is to be multiplied.

   Here is an example.

 

    <Scoped record>          EXTSALES, 2011.JAN, ACTUAL, 10000

         *REC(FACTOR=6/2)

    <Generated record>         EXTSALES, 2011.JAN, ACTUAL, 30000

 

 

   What if you want to add or divide? then you should use EXPRESSION.   The EXPRESSION is any formula that will result in the new value to post.    The formula can include regular arithmetic operators, fixed values and the Script logic keyword %VALUE%    this is representing the original retrieved value of the scoped record.

   Here is an example. 

    <Scoped record>          EXTSALES, 2011.JAN, ACTUAL, 10000

         *REC(EXPRESSION=%VALUE% + 5000)

    <Generated record>         EXTSALES, 2011.JAN, ACTUAL, 15000

Now we got the basic things of *REC statement but you may ask below questions.      "There are some scoped data and I need to do different calculations based on each specific dimension member."   "I need to copy a value to multiple destinations!" 

Page 11: Logic Script

   "How can I get the value from the other application?"   "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 above requirements.I will explain first question in this post and will do others in the next post.

"There are some scoped data and I need to do some calculations based on each specific dimension member."

   Yes. That's why *you MUST use *REC statement with *WHEN ~ *IS ~ *ELSE ~ *ENDWHEN statement.Let's assume you want to create forecast values of salary and put it into the forecast category based on the country's actual salary values of January, 2011.We need to increase 10% for US, 5% for Canada and 3% for other countries.Let's assume ENTITY dimension has country information.To do this, you need to scope first.     *XDIM_MEMBERSET ACCT = SALARY  *XDIM_MEMBERSET TIME = 2011.JAN  *XDIM_MEMBERSET CATEGORY = ACTUAL         Now you need to write the *REC statements   *REC(FACTOR = 1.1, CATEGORY=“FORECAST“)  // 10%   *REC(FACTOR = 1.05, CATEGORY=“FORECAST“) // 5%   *REC(FACTOR = 1.03, CATEGORY=“FORECAST“)  // 3%Finally, you should specify a condition of each *REC statement.For doing this, you MUST use *WHEN ~ *IS ~ *ELSE ~ ENDWHEN statement.

 

 

 

First, Write down *WHEN and *ENDWHEN outside of the *REC statement

 

     *WHEN              *REC(FACTOR = 1.1, CATEGORY=“FORECAST“)  // 10%           *REC(FACTOR = 1.05, CATEGORY=“FORECAST“)  // 5%           *REC(FACTOR = 1.03, CATEGORY=“FORECAST“)  // 3%     * ENDWHEN

 

       NOTE : You don't need to use the indentation of code in the script logic                 but I would like to recommend using it for better readability.

 

 

 

Page 12: Logic Script

Second, write a dimension name that you want to compare next to *WHEN.            In this example, it will be ENTITY dimension.

 

      *WHEN ENTITY            *REC(FACTOR = 1.1, CATEGORY=“FORECAST“)  // 10%           *REC(FACTOR = 1.05, CATEGORY=“FORECAST“)  // 5%           *REC(FACTOR = 1.03, CATEGORY=“FORECAST“)  // 3%     *ENDWHEN

 

 

 

Third, put *IS statement on top of each *REC statement and *ELSE statement on top of the last *REC statement.            We need two *IS statements and   *ELSE statement because there are two conditions and others will be calculated as one condition.

 

      *WHEN ENTITY             *IS                  *REC(FACTOR = 1.1, CATEGORY=“FORECAST“)  // 10%            *IS                  *REC(FACTOR = 1.05, CATEGORY=“FORECAST“)  // 5%            * ELSE                   *REC(FACTOR = 1.03, CATEGORY=“FORECAST“)  // 3%      ENDWHEN

 

 

 

Fourth, put each condition value next to *IS

 

        *WHEN ENTITY            ***IS USA                     *REC(FACTOR = 1.1, CATEGORY=“FORECAST“)  // 10%            ***IS CANADA                  *REC(FACTOR = 1.05, CATEGORY=“FORECAST“)  // 5%            ***ELSE                  *REC(FACTOR = 1.03, CATEGORY=“FORECAST“)  // 3%      *ENDWHEN

 

 

Page 13: Logic Script

 

As a last step, put *COMMIT at end of the script so that logic engine can post data to Database.

 

so final version should be same as below code.      *XDIM_MEMBERSET ACCT = SALARY      *XDIM_MEMBERSET TIME = 2011.JAN      *XDIM_MEMBERSET CATEGORY = ACTUAL      *WHEN ENTITY            ***IS USA                    *REC(FACTOR = 1.1, CATEGORY=“FORECAST“)  // 10%            ***IS CANADA                  *REC(FACTOR = 1.05, CATEGORY=“FORECAST“)  // 5%            ***ELSE                  *REC(FACTOR = 1.03, CATEGORY=“FORECAST“)  // 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 *IS statement.  ex) *IS > 4                   By default, it is equal (=) so it will be ok even though you don't specify it.       Note 3 : can't use AND, OR and NOT with *IS       Note 4 : " (double quotation) is not mandatory for comparing string value with *IS statement.       Note 5 : *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 property value with *IS statement.  ex) *IS Intco.Entity

BPC Script logic for Dummies? (Part 4)Posted by James Lim in SAP Planning and Consolidation, version for SAP NetWeaver on Aug 12, 2011 11:11:21 AM

Page 14: Logic Script

inShare0

OK, let’s start to find out the answer about one of the questions that we had in the last post.

 

"How can I get the value from the other application?"The simple answer is... USE *LOOKUP/*ENDLOOKUP!

The simplest example is the currency conversion because you need to read rate value from the rate application to convert

currency values of the finance application.

 (NOTE:*LOOKUP/*ENDLOOKUP also can be used for reading value of the current application.)

Here is the syntax of *LOOKUP/*ENDLOOKUP    The syntax is:  *LOOKUP {Application}*DIM [{LookupID}:] {DimName}="Value" | {CallingDimensionName}[.{Property}][*DIM …] *ENDLOOKUP     {Application} is the name of the application which you will retrieve value.    {DimensionName} is a dimension in the lookup application.    {CallingDimensionName} is a dimension in the current application.    {LookupID} is an optional variable that will hold the value so that you can use it in the script.              This is only required when multiple values must be retrieved.      

Now, let's do it step by step.

Here are our requirements for the currency conversion.1. You need to get the rate values from rate application for currency conversion (LC to USD and EUR). 2. The member id of RATE dimension in the rate application should be the same as RATETYPE property of the account dimension in the finance application.3. The member id of RATEENTITY dimension in the rate application should be "DEFAULT"4. The rule of currency conversion is 'DESTINATION CURRENCY/CURRENT CURRENCY'      First, you need to define *LOOKUP with application name.   *LOOKUP RATE       *ENDLOOKUP

Second, specify dimensions of RATE application with *DIM statement.   (Let's assume the rate application has RATEENTITY, INPUTCURRENCY, RATE, CATEGORY and TIME dimension.)  

   *LOOKUP RATE     *DIM RATEENTITY

Page 15: Logic Script

     *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 fixed value.       If you need to retrieve multiple value 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 same dimension for another value     *DIM INPUTCURRENCY=ENTITY.CURR // added one more for the currency conversion as variable value     *DIM RATE=ACCOUNT.RATETYPE     // Variable value based on the current application     *DIM CATEGORY     *DIM TIME   *ENDLOOKUP

Fourth, Put variables for multiple retrieving values in front of each duplicate dimension name. 

   *LOOKUP RATE     *DIM RATEENTITY="DEFAULT"                   *DIM DESTCURR1:INPUTCURRENCY="USD"          *DIM DESTCURR2:INPUTCURRENCY="EUR"          *DIM SOURCECUR:INPUTCURRENCY=ENTITY.CURR     *DIM RATE=ACCOUNT.RATETYPE                    *DIM CATEGORY     *DIM TIME   *ENDLOOKUP

   -------------------------------------------------------------------------   Note: If you want to get some value based on two or more dimensions,         You should use the same variable name when you map dimensions.         Here is an example.

         *LOOKUP OWNERSHIP             *DIM INTCO="IC_NONE"             *DIM PARENT="MYPARENT"

             *DIM PCON:ACCOUNTOWN="PCON"      // PCON is used for ACCOUNTOWN             *DIM PCON:ENTITY=ENTITY          // PCON is used for ENTITY

             *DIM IC_PCON:ACCOUNTOWN="PCON"    // IC_PCON is used even though it searches same "PCON"             *DIM IC_PCON:ENTITY=INTCO.ENTITY  // IC_PCON is used for INTCO.ENTITY         *ENDLOOKUP

         Even though the member id of ACCOUNTOWN dimension is same, the variable should be defined as a different variable because the member id of ENTITY dimension is different in the combination.          If the 'ENTITY' property of INTCO dimension has I_FRANCE value, above *LOOKUP will select below two records and each variable will have different value.

             IC_NONE,MYPARENT,PCON,FRANCE,100    => PCON             IC_NONE,MYPARENT,PCON,I_FRANCE,80   => IC_PCON   --------------------------------------------------------------------------- 

Page 16: Logic Script

Last, Remove dimension names (TIME and CATEGORY> that don’t have any fixed value or variable value because it will be passed as current value

automatically.     *LOOKUP RATE     *DIM RATEENTITY="DEFAULT"        *DIM SOURCECUR:INPUTCURRENCY=ENTITY.CURR     *DIM DESTCURR1:INPUTCURRENCY="USD"     *DIM DESTCURR2:INPUTCURRENCY="EUR"     *DIM RATE=ACCOUNT.RATETYPE    *ENDLOOKUP  Now we get the values so how can we use these values?You can use it using LOOKUP(Variable) in your *REC statement as below

   *WHEN ACCOUNT.RATETYPE      *IS "AVG","END"         *REC(FACTOR=LOOKUP(DESTCURR1)/LOOKUP(SOURCECURR),CURRENCY=”USD”)         *REC(FACTOR=LOOKUP(DESTCURR2)/LOOKUP(SOURCECURR),CURRENCY=”EUR”)   *ENDWHEN

   NOTE: You can use LOOKUP(variable) with *WHEN and *IS statement.

       Ex) *WHEN LOOKUP(PCON)          //as a condition value of when              *IS <= LOOKUP(IC_PCON)   //as a value of IS                  *REC(FACTOR=-1, PARENT ="MYPARENT",DATASRC="ELIM")           *ENDWHEN 

We reviewed how to define *LOOKUP/*ENDLOOKUP statement and how to use it.

Now it is time to find out how it works in the script engine.

Let's assume below records are in the rate application and see what will happen during execute of the script logic.

RATEENTITY, INPUTCURRENCY, RATE, CATEGORY, TIME, SIGNEDDATA

DEFAULT, USD, AVG, ACTUAL, 2011.JAN, 1DEFAULT, EUR, AVG, ACTUAL, 2011.JAN, 1.22DEFAULT, CHF, AVG, ACTUAL, 2011.JAN, 0.91DEFAULT, USD, END, ACTUAL, 2011.JAN, 1DEFAULT, EUR, END, ACTUAL, 2011.JAN, 1.24DEFAULT, CHF, END, ACTUAL, 2011.JAN, 0.93RATCALC, USD, AVG, ACTUAL, 2011.JAN, 1RATCALC, USD, AVG, ACTUAL, 2011.JAN, 1

Here are your current finance application records that need to be processed.

ACCOUNT, ENTITY, CATEGORY,  TIME, CURRENCY, SIGNEDDATAINVENTORY, SWITZERLAND, ACTUAL, 2011.JAN, LC, 5000REVENUE,   SWITZERLAND, ACTUAL, 2011.JAN, LC, 1000

As you can see, there is no relationship between finance application and rate application.We know Switzerland currency is CHF but there is no information in each fact table record.It only has LC (local currency) value.

Page 17: Logic Script

Then, how can script logic find the value and calculate it?

The key point is 'ENTITY.CURR' which we used it for mapping dimension as below.

    *DIM SOURCECUR:INPUTCURRENCY=ENTITY.CURR

ENTITY.CURR means 'CURR' property value of ENTITY dimension.Therefore, Switzerland which is one of the Entity dimension member should have 'CHF' value in its 'CURR' property.

Same thing is for mapping RATE dimension of rate application as below.    *DIM RATE=ACCOUNT.RATETYPE

So the 'RATETYPE' property value of INVENTORY and REVENUE account should have 'AVG' or 'END' value.

Therefore, the Script engine will do the following steps to process the first record of the fact table.

1. Select RATEENTITY = "DEFAULT" 

2. Select INPUTCURRENCY = "CHF” (because current Entity member's 'CURR' property value is 'CHF')   OR INPUTCURRENCY = "USD"        OR INPUTCURRENCY = "EUR"      

3. Select RATE = "END” (because current account member's 'RATETYPE' property value is 'END')

4. Select CATEGORY = "ACTUAL” (There is no statement so it is same as current application CATEGORY value.)

5. Select TIME = "2011.JAN” (There is no statement so it is same as current application TIME value.)

All above selection will be combined with 'AND' condition.

So the 3 records below will be selected and its signeddata value will be assigned to each variable.

DEFAULT, USD, END, ACTUAL, 2011.JAN, 1    => DESTCURR1 will be 1DEFAULT, EUR, END, ACTUAL, 2011.JAN, 1.24 => DESTCURR2 will be 1.24DEFAULT, CHF, END, ACTUAL, 2011.JAN, 0.93 => SOURCECUR will be 0.93

After the script logic engine executes below statements, it will generate 2 records.

   *WHEN ACCOUNT.RATETYPE      *IS "AVG","END"         *REC(FACTOR=LOOKUP(DESTCURR1)/LOOKUP(SOURCECURR),CURRENCY=”USD”)         *REC(FACTOR=LOOKUP(DESTCURR2)/LOOKUP(SOURCECURR),CURRENCY=”EUR”)   *ENDWHEN

ACCOUNT, ENTITY, CATEGORY,  TIME, CURRENCY, SIGNEDDATAINVENTORY, SWITZERLAND, ACTUAL, 2011.JAN, LC,  5000INVENTORY, SWITZERLAND, ACTUAL, 2011.JAN, USD, 5376.34     // 5000 * (1/0.93)INVENTORY, SWITZERLAND, ACTUAL, 2011.JAN, EUR, 6666.67     // 5000 * (1.24/0.93)

For the 2nd record in the fact table, the 3 records below will be selected from the rate application fact table because

Page 18: Logic Script

Revenue account has 'AVG' RATETYPE.

DEFAULT, USD, AVG, ACTUAL, 2011.JAN, 1DEFAULT, EUR, AVG, ACTUAL, 2011.JAN, 1.22DEFAULT, CHF, AVG, ACTUAL, 2011.JAN, 0.91

After it processes 'REVENUE' records, there will be 6 records in the fact table as below.(4 records will be generated in total.)

ACCOUNT, ENTITY, CATEGORY,  TIME, CURRENCY, SIGNEDDATAINVENTORY, SWITZERLAND, ACTUAL, 2011.JAN, LC,  5000INVENTORY, SWITZERLAND, ACTUAL, 2011.JAN, USD, 5376.34    INVENTORY, SWITZERLAND, ACTUAL, 2011.JAN, EUR, 6666.67    REVENUE,   SWITZERLAND, ACTUAL, 2011.JAN, LC,  1000REVENUE,   SWITZERLAND, ACTUAL, 2011.JAN, USD, 1098.90     // 1000 * (1/0.91)REVENUE,   SWITZERLAND, ACTUAL, 2011.JAN, EUR, 1340.66     // 1000 * (1.22/0.91)

 

We finished learning how to use *LOOKUP/*ENDLOOKUP statement.

Here are some questions and answers that I got frequently.

Question 1: What if rate application doesn't have the value?            Then currency conversion will not happen.

Question 2: I don't have any records in the fact table of current application. What will happen?            The script logic always reads records from the current application.            Therefore, if there are no records in the fact table of the current application,            Nothing will happen.

Question 3: Can we lookup parent member value instead of base member (leaf member)?            MS version can do it with *OLAPLOOKUP statement instead of *LOOKUP but NW version doesn't have it yet.

BPC Script logic for Dummies? (Final)Posted by James Lim in SAP Planning and Consolidation, version for SAP NetWeaver on Oct 20, 2011 1:45:35 PM inShare0

Page 19: Logic Script

As a last post of this script logic series, I would like to explain some special functions that only exist in BPC MS.Even though the user can implement below functions in BPC NW using BADI, in my personal opinion, I think we really need these functions in the BPC NW version.

1. FLD function (for using property value with *REC statement)

   Sometimes customer may want to use a property value for calculating with *REC statement.

   The syntax is:                    FLD(Dimensionname.Propertyname)

   and here is an example.

   *WHEN ENTITY.SCALE         *IS <>””               *REC(EXPRESSION=%VALUE%*FLD(ENTITY.SCALE))   *ENDWHEN

         It means the Records value will be calculated as a product of current record value and scale property value of entity

member in the current record.

   Let's assume fact table has below current records.

        ACCOUNT, ENTITY, CURRENCY, SIGNEDDATA

        Revenue, CT, USD, 100          Revenue, CA, USD, 200          Revenue, NJ, USD, 300          Revenue, NY, USD, 400  

   Here is an example logic script.

   *WHEN ENTITY.SCALE         *IS <>””               *REC(EXPRESSION=%VALUE%*FLD(ENTITY.TAXRATE)/100, ACCOUNT="TAX")   *ENDWHEN

   and let's say ENTITY dimension member worksheet looks like below.

           ID, PARENTH1, ..., TAXRATE           

       CT, USA, ..., 6.35       CA, USA, ..., 7.75       NJ, USA, ..., 7.00       NY, USA, ..., 8.875

   When you run the script logic, 4 records will be generated as below.

        ACCOUNT, ENTITY, CURRENCY, SIGNEDDATA

Page 20: Logic Script

        Revenue, CT, USD, 100          Revenue, CA, USD, 200          Revenue, NJ, USD, 300          Revenue, NY, USD, 400            TAX, CT, USD, 6.35               TAX, CA, USD, 15.5            TAX, NJ, USD, 21.00            TAX, NY, USD, 35.5 

2. GET function

   When we use *REC statement, we can use Lookup function to get a value from the other application. For example, FX

translation needs rate value. I explained it in the previous post.   In addition to above, users may want to get the value from other record within SELECTED REGION.    The syntax is:                      GET({dimension}={member name(ID)}[, {dimension}={member name(ID)}]…)  

   and here are some examples.

       GET(ACCOUNT= ”EXTSALES”)       GET(ACCOUNT= ACCOUNT.MYPROPERTY)          GET(ACCOUNT= ACCOUNT.MYPROPERTY + ".TOTAL" ) // support the concatenation using '+' operator.                Note: if ACCOUNT.MYPROPERTY has "TAX" value, it will get the value of TAX account record value.

       GET(ACCOUNT=ACCOUNT.ELIMACC,ENTITY=INTCO.ENTITY)

         Note: User can specify multiple dimension names to select record.                   All missing dimension will be same as source record. 

   Let's use the same example that we used above.   We will calculate NetRevenue using Revenue and Tax.

     *XDIM_MEMBERSET ACCOUNT = REVENUE, TAX // I will explain why we need it.     *WHEN ACCOUNT          *IS “REVENUE”               *REC(EXPRESSION=%VALUE% - GET(ACCOUNT=”TAX”), ACCOUNT=”NETREVENUE”)     *ENDWHEN      

   If we run above script, 4 records will be created.

         ACCOUNT, ENTITY, CURRENCY, SIGNEDDATA

        Revenue, CT, USD, 100          Revenue, CA, USD, 200          Revenue, NJ, USD, 300          Revenue, NY, USD, 400          TAX, CT, USD, 6.35          TAX, CA, USD, 15.5          TAX, NJ, USD, 21.00          TAX, NY, USD, 35.5 

Page 21: Logic Script

           NetRevenue, CT, USD, 93.65  // 100 - 6.35             NetRevenue, CA, USD, 184.5  // 200 - 15.5           NetRevenue, NJ, USD, 279    // 300 - 21             NetRevenue, NY, USD, 364.5  // 400 - 35.5

            There are two things you keep in mind to use this GET function.

  First, GET function will not search from database but search from 'SELECTED REGION'.         Therefore, you should specify members in the *XDIM_MEMBERSET before use GET function.         That's why we specified *XDIM_MEMBERSET ACCOUNT = REVENUE, TAX in the example.

  Second, the Script engine can't use the results of a previously calculated value without *COMMIT statement.                   This example will not work.          *WHEN ACCOUNT               *IS “UNITS”                    *REC(ACCOUNT=”REVENUE”, FACTOR=GET(ACCOUNT=”PRICE”))               *IS “REVENUE”                    *REC(ACCOUNT=”TAXES”, FACTOR=.5)         *ENDWHEN

  So we need to separate two calculations using Commit statement to make it work.                  *WHEN ACCOUNT               *IS “UNITS”                    *REC(ACCOUNT=”REVENUE”, FACTOR=GET(ACCOUNT=”PRICE”))               *ENDWHEN

         *COMMIT

         *WHEN ACCOUNT               *IS “REVENUE”                    *REC(ACCOUNT=”TAXES”, FACTOR=.5)         *ENDWHEN

  Usually, *COMMIT statement will post records into the database but what if the customer   wants to calculate without

posting data?    For that purpose, BPC MS has a special statement which is *GO.  *GO statement will not post data but can use the result value of the previous script.

 

3. Dummy memory variable

  User can save intermediate result and assign them to dummy members like dummy account members or any other dimension.  

   This member can be used as temporary records to store intermediate results that can be used as inputs for subsequent

Page 22: Logic Script

calculations. These temporary records will be automatically skipped during the commit time.

  Dummy members must be identified with a leading pound (#) sign.

  For example:          *REC(ACCOUNT=#TEMP_ACC)  Even though #TEMP_ACC member does not exist in the account dimension, the generated record can be used anywhere in the logic

as below example, but its value will not be stored in the database.        *WHEN ACCOUNT.FLAG          *IS = Y             *REC(ACCOUNT=#TEMP_ACC)      *ENDWHEN       *GO         *WHEN ACCOUNT          *IS #TEMP_ACC             *REC(FACTOR=GET(ACCOUNT=MULTIPLIER),ACCOUNT=FINAL)       *ENDWHEN

 This is useful to calculate a complex calculation like allocation. Let's assume you want to allocate some expense based on the each store's area. You must have a total area value of each store because the formula will be

      Each Store's Expense = Total Expense amount *  Each Store's area / Total area

 Even though each store's area is saved in the database as an account member, you need to have the total value. Of

course, it is easy to get the total value using MDX script statement but we need to do it using the SQL script statement

for better performance.

Finally, I finished the BPC Script logic for dummies(?) series.Well, it might not be enough for using it right now but as I addressed in the first post,I hope you get the basic concepts of BPC script logic so that you can understand and read what it means.

Thank you for reading this series. 

James Lim