一个MDX计算语句的实例如下:
Use cube_test;
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[YearTotal]),MemberSet([Product].[TotalProduct],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price]->[Period].[Begbalance];
End Scope;
以上的计算语句,会用2022年每个月份的所有产品的销售量,乘以各产品设定在Begbalance期间成员上的单价,得到各个产品的销售额。 下面我们来逐句解释MDX计算语句中每一段的含义。
[use_section] --确定使用某个财务模型
Scope(member_expression|set_expression[,member_expression|set_expression[,...]]); --计算范围定义语句
目标成员|This = <计算公式> --计算公式
End Scope --限制范围结束
我们使用use表示其后面的计算语句会使用哪个财务模型元素进行计算。
Use cube_test;
在先胜云平台中,由于不同文件夹下的元素可以重名,因此如果不能确定应用中没有重名的财务模型/多维模型,需要在调用MDX查询时,同步传入元素的路径信息,如: “cube_test=root_folder/test_folder/mdx_test/cube1.cub”。
确定了使用的财务模型后,在遇到下一个Use语句之前,所有的计算语句都会使用当前的财务模型作为计算的目标模型。
MDX计算语句通过 Scope……End Scope的组合对需要被计算的目标值进行限定,随后在限定的范围内,对指定的维度成员进行计算。 Scope中通过维度成员的组合对计算范围进行限定。Scope中的入参可以为成员表达式,也可以是元组或集合表达式。如果是元组或集合表达式,则该元组或集合必须是一个由单一维度成员所组成的元组或此类元组所组成的集合。(关于集合的定义,详见《MDX的数据类型 - Set集合》)。 如,以下集合可以被用于Scope范围:
{[Account].[price], [Account].[vol], [Account].[amount]}
而以下集合则不允许用于Scope范围:
{([Year].[2021], [Period].[1]),([Year].[2021], [Period].[2]),([Year].[2021], [Period].[3]) }
由于在计算公式中,等号的左边最多只允许放一个维度成员,因此,Scope中的维度的数量必须大于等于所使用的财务模型的维度数量-1。 例如,当前财务模型有7个维度(注意,这里的维度名不等于维度元素名,详见《MDX的数据类型》中关于Member的类型的介绍),分别是Entity,Account,Scenario,Version,Year,Period和Product。那计算脚本可以这样写:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[YearTotal]),MemberSet([Product].[TotalProduct],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
End Scope;
从上面的例子可以看到,在Scope的参数中一共放了Entity,Scenario,Version,Year,Period和Product这六个维度,而缺失了Account维度,因此Account维度成员可以出现在下面的计算公式的等号左侧([Account].[Total_Sales])。在计算语句中,要求公式的等号左侧的维度必须在等号右侧的每一个项目中都出现,因此等号右侧也出现了科目成员([Account].[Volume]和[Account].[Price]) 以上的例子也可以写成下面的形式:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[YearTotal]),MemberSet([Product].[TotalProduct],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves),[Account].[Total_Sales] );
This = [Account].[Volume]*[Account].[Price];
End Scope;
同第一个例子不同的事,原本等号左侧的[Account].[Total_Sales] 被移动到了Scope参数内,因此,该财务模型所有的7个维度就在Scope参数中齐全了。这样,下面的计算公式中,等号左侧就不允许在放任何维度的成员了,而需要用This来表示当前范围内的每一个成员。
注意,在数学计算脚本中,scope中的维度和计算公式中等号左侧的维度,必须囊括财务模型的所有维度,也就是不允许少提供任何维度,否则,财务模型计算接口需要返回错误信息,提示缺少维度,并不执行计算操作。 如执行了如下脚本:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[Q1]));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price]->[Period].[BegBalance];
End Scope;
监测到Scope和计算脚本的左侧都没有Product和Entity维度的成员,因此会给出错误信息:“ 当前计算脚本缺少Entity,Product维度,计算终止。”
Scope语句中,一个维度可以出现多次,如下例中,Version维度出现了两次,分别指定了V1和V2成员。如果同一个维度出现了多次并且其维度成员之间有重复,则在计算的时候去重。
Scope([Scenario].[Budget], [Version].[V1], [Version].[V2],[Year].[2022],MemberSet([Period].[YearTotal]),MemberSet([Product].[TotalProduct],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
End Scope;
Scope语句可以做嵌套,如下例所示:
//限制Scenairo,Version,Year,Period和Entity
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[YearTotal]),Leaves([Entity].[TotalEntity]));
//限制Product维度为所有食品类的产品
Scope(MemberSet([Product].[TotalFood],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
End Scope;
//限制Product维度为所有饮料类的产品
Scope(MemberSet([Product].[TotalDrink],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price]*1.1;
End Scope;
End Scope;
在该例中,先用一个Scope限制了Scenairo,Version,Year,Period和Entity成员的范围,随后在内部的第一个Scope中计算食品的销售额,再在第二个Scope中计算饮料的销售额,两类产品的计算公式不完全一样。 如果我们不使用Scope嵌套的方式,也可以写成这样:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[YearTotal]),MemberSet([Entity].[TotalEntity],-1,Leaves),MemberSet([Product].[TotalFood],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
End Scope;
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[YearTotal]),MemberSet([Entity].[TotalEntity],-1,Leaves),MemberSet([Product].[TotalDrink],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price]*1.1;
End Scope;
可以看出,由于没有使用嵌套,代码的内容增多了,因为每个scope都需要设定好几个维度的限制范围,但其实很多都是重复的。通过Scope的嵌套,我们就可以避免这种重复限制的情况,降低了代码量并且增加了代码的层次感和可读性。
MDX语句嵌套后,外层的Scope成员范围和里层的Scope成员范围,最终在计算的时候会取并集,如下:
//限制Scenairo,Version,Year,Period和Entity
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[YearTotal]),Leaves([Entity].[TotalEntity]));
//限制Product维度为所有食品类的产品
Scope(MemberSet([Product].[TotalFood],-1,Leaves),[Version].[V2]);
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
End Scope;
//限制Product维度为所有饮料类的产品
Scope(MemberSet([Product].[TotalDrink],-1,Leaves),[Version].[V3]);
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price]*1.1;
End Scope;
End Scope;
如上例中,外层的Scope中已经有Version成员[V1],里层的Scope又分别设置了[V2]和[V3],因此在计算的时候,会对所有食品类的产品(第一个里层的Scope)使用[V1]和[V2],对所有饮料类的产品(第二个里层的Scope)使用[V1]和[V3]。
一个Scope内的计算公式可以连续写多个,他们共用同样的Scope限定范围。
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[YearTotal]),MemberSet([Product].[TotalProduct],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
[Account].[profit] = [Account].[Total_Sales]-[Account].[Cost];
End Scope;
单一维度内的计算指的是在计算公式中,等号右侧的所有参与计算的项都是来自同一个维度,如下例所示,参与计算的右侧的所有成员都在科目维度的成员:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[Q1]),MemberSet([Product].[TotalProduct],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
End Scope;
以上计算公式的含义是: 将Budget场景,V1版本,2022年,Q1所有后代期间,所有的末级实体,所有末级食品类产品上的Total_Sales科目的数值赋值成其同样场景、版本、年份、期间、实体和产品上Volume科目和Price科目的乘积。
如原有财务模型中的数据如下:
Entity |
Year |
Period |
Scenario |
Version |
Account |
Product |
data |
---|---|---|---|---|---|---|---|
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E1 |
2022 |
1 |
Budget |
V1 |
Price |
Hamburg |
3.5 |
E1 |
2022 |
2 |
Budget |
V1 |
Price |
Hamburg |
3.2 |
E1 |
2022 |
3 |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Sandwich |
20 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Sandwich |
25 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Sandwich |
30 |
E1 |
2022 |
1 |
Budget |
V1 |
Price |
Sandwich |
5.5 |
E1 |
2022 |
2 |
Budget |
V1 |
Price |
Sandwich |
5 |
E1 |
2022 |
3 |
Budget |
V1 |
Price |
Sandwich |
5 |
执行过该计算脚本后的数据为(绿色部分为计算后新增的数据):
Entity |
Year |
Period |
Scenario |
Version |
Account |
Product |
data |
---|---|---|---|---|---|---|---|
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E1 |
2022 |
1 |
Budget |
V1 |
Price |
Hamburg |
3.5 |
E1 |
2022 |
2 |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
3 |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Sandwich |
20 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Sandwich |
25 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Sandwich |
30 |
E1 |
2022 |
1 |
Budget |
V1 |
Price |
Sandwich |
5.5 |
E1 |
2022 |
2 |
Budget |
V1 |
Price |
Sandwich |
5 |
E1 |
2022 |
3 |
Budget |
V1 |
Price |
Sandwich |
5 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Hamburg |
350 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Hamburg |
330 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Hamburg |
360 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Sandwich |
110 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Sandwich |
125 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Sandwich |
150 |
在该示例中,通过计算,将Budget场景,V1版本,2022年,Q1的 所有后代(1,2,3月),所有的末级实体(E1),所有末级食品类产品(Hamburg,Sandwich)上的对应Price和Volume科目的值相乘,得到的值赋值给对应Total_Sales科目的Cube单元格上。
下例展示的是基于Scenario维度的单维度计算,原理同上面的示例一样
Scope([Version].[V1], [Year].[2022],MemberSet([Period].[Q1]), MemberSet([Product].[TotalProduct],-1,Leaves), MemberSet([Entity].[TotalEntity],-1,Leaves),[Account].[Total_Sales]);
[Scenario].[Forecast_4_8] = [Scenario].[Budget]*1.1;
End Scope;
该计算逻辑运行后的结果如下:
Entity |
Year |
Period |
Scenario |
Version |
Account |
Product |
data |
---|---|---|---|---|---|---|---|
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E1 |
2022 |
1 |
Budget |
V1 |
Price |
Hamburg |
3.5 |
E1 |
2022 |
2 |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
3 |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Sandwich |
20 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Sandwich |
25 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Sandwich |
30 |
E1 |
2022 |
1 |
Budget |
V1 |
Price |
Sandwich |
5.5 |
E1 |
2022 |
2 |
Budget |
V1 |
Price |
Sandwich |
5 |
E1 |
2022 |
3 |
Budget |
V1 |
Price |
Sandwich |
5 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Hamburg |
350 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Hamburg |
330 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Hamburg |
360 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Sandwich |
110 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Sandwich |
125 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Sandwich |
150 |
E1 |
2022 |
1 |
Forecast_4_8 |
V1 |
Total_Sales |
Hamburg |
385 |
E1 |
2022 |
2 |
Forecast_4_8 |
V1 |
Total_Sales |
Hamburg |
363 |
E1 |
2022 |
3 |
Forecast_4_8 |
V1 |
Total_Sales |
Hamburg |
396 |
E1 |
2022 |
1 |
Forecast_4_8 |
V1 |
Total_Sales |
Sandwich |
121 |
E1 |
2022 |
2 |
Forecast_4_8 |
V1 |
Total_Sales |
Sandwich |
137.5 |
E1 |
2022 |
3 |
Forecast_4_8 |
V1 |
Total_Sales |
Sandwich |
165 |
在2.1的例子中,我们在每个月上都有商品的平均单价和销售量,通过将相同月份上的平均单价和销售量的相乘,得到当月的销售额。还有一种场景,如果平均单价被统一维护在了BegBalance(期初)这个期间上,而销售量仍然是按月维护。数据如下:
Entity |
Year |
Period |
Scenario |
Version |
Account |
Product |
data |
---|---|---|---|---|---|---|---|
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Hamburg |
3.5 |
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Sandwich |
20 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Sandwich |
25 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Sandwich |
30 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Sandwich |
5.5 |
这时再计算销售额就涉及到跨维度取数的计算,如下:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[Q1]),MemberSet([Product].[TotalFood],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price]->[Period].[BegBalance];
End Scope;
以上计算公式的含义是: 将Budget场景,V1版本,2022年,Q1所有后代期间,所有的末级实体,所有末级食品类产品上的Total_Sales科目的数值赋值成其同样场景、版本、年份、期间、实体和产品上Volume科目同期间在BegBalance上,其他维度都相同的Price科目的乘积。
计算后的结果如下:
Entity |
Year |
Period |
Scenario |
Version |
Account |
Product |
data |
---|---|---|---|---|---|---|---|
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Sandwich |
20 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Sandwich |
25 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Sandwich |
30 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Sandwich |
5 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Hamburg |
300 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Hamburg |
330 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Hamburg |
360 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Sandwich |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Sandwich |
125 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Sandwich |
150 |
在脚本计算中,公式等号右侧的维度成员同Scope范围内组成的单元格,是允许使用动态汇总后的单元格的,如下例:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[Q1]),MemberSet([Product].[TotalProduct],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
[Account].[Sales_percent] = [Account].[Total_sales]/[Account].[Total_Sales]->[Product].[TotalProduct];
End Scope;
在上例中,我们需要计算每个商品的销售额占全部商品销售总额的占比,因此用当前商品的销售额除以所有商品的销售额,这里所有商品的销售额由于是在通用类维度Product的汇总节点TotalProduct上,并且版本也不具备TopDown属性,因此所有商品的销售额是需要动态汇总出来的,随后再参与销售占比的计算。
在财务模型的计算中,参与计算的数是被限定了一个范围,因此必然会遇到参与计算的数值为空的情况,如下例:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[Q1]),MemberSet([Product].[TotalFood],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price]->[Period].[BegBalance]+[Account].[Sales_Adjs];
End Scope;
当前脚本计算的Entity范围是所有的叶子节点,但并不代表所有的叶子节点都会参与计算。比如,Entity维度中有3个叶子节点 E1,E2,E3,E4。计算前财务模型中的数据如下:
Entity |
Year |
Period |
Scenario |
Version |
Account |
Product |
data |
---|---|---|---|---|---|---|---|
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Sandwich |
20 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Sandwich |
25 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Sandwich |
30 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Sandwich |
5 |
E2 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E2 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E2 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E3 |
2022 |
1 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
50 |
E3 |
2022 |
2 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
80 |
E3 |
2022 |
3 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
-110 |
当Entity等于E1时,等号右侧获取了 [Account].[Volume]和[Account].[Price]->[Period].[BegBalance]上的值,但[Account].[Sales_Adjs]为空,因此计算的结构就是 [Account].[Volume]*[Account].[Price]->[Period].[BegBalance];
当Entity等于E2时,等号右侧首先查询到 [Account].[Volume]的值,而并没有查询到[Account].[Price]->[Period].[BegBalance],因此不需要执行 [Account].[Volume]*[Account].[Price]->[Period].[BegBalance]的计算;同时也没有查询到[Account].[Sales_Adjs],因此也不需要做+[Account].[Sales_Adjs]的计算,即在E2上不产生任何计算结果。
当Entity等于E3时,等号右侧首先没有查询到 [Account].[Volume]和[Account].[Price]->[Period].[BegBalance],因此不需要执行 [Account].[Volume]*[Account].[Price]->[Period].[BegBalance]的计算;但查询到 [Account].[Sales_Adjs]的值,因此需要执行+[Account].[Sales_Adjs]的计算,因此E3上会产生计算结果。
当Entity等于E4时,等号右侧没有查询到任何值,因此对E4来说也不需要执行任何计算。
计算后的结果如下:
Entity |
Year |
Period |
Scenario |
Version |
Account |
Product |
data |
---|---|---|---|---|---|---|---|
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Sandwich |
20 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Sandwich |
25 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Sandwich |
30 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Sandwich |
5 |
E2 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E2 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E2 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E3 |
2022 |
1 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
50 |
E3 |
2022 |
2 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
80 |
E3 |
2022 |
3 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
-110 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Hamburg |
300 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Hamburg |
330 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Hamburg |
360 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Sandwich |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Sandwich |
125 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Sandwich |
150 |
E3 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Hamburg |
50 |
E3 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Hamburg |
80 |
E3 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Hamburg |
-110 |
所以,对于空值的计算,设定的规则是,乘除法中如果有任何一方是NULL,则乘除计算的结果是NULL;加减法中如果有任何一方是NULL,则当做0处理,如果两方都是NULL,则结果也是NULL。 由于一次计算,通常情况下NULL值是占绝大多数的,因此NULL值不会回写进系统!
NULL值不回写的逻辑会带来一个问题,比如,仍然使用上例中的MDX语句,但计算前财务模型的数据如下:
Entity |
Year |
Period |
Scenario |
Version |
Account |
Product |
data |
---|---|---|---|---|---|---|---|
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Sandwich |
20 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Sandwich |
25 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Sandwich |
30 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Sandwich |
5 |
E2 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E2 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E2 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E3 |
2022 |
1 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
50 |
E3 |
2022 |
2 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
80 |
E3 |
2022 |
3 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
-110 |
E2 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Hamburg |
500 |
E2 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Hamburg |
650 |
E2 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Hamburg |
600 |
在财务模型中,由于各种原因(比如第一次计算是其实是有E2上的Price数据,但随后Price数据被用户删除了),已经存在了E2的[Account].[Total_Sales]数据,这时再进行计算,会发现由于E2上不会产生任何计算结果,因此也不会写入任何E2的数据,因此计算后的财务模型的数据如下:
Entity |
Year |
Period |
Scenario |
Version |
Account |
Product |
data |
---|---|---|---|---|---|---|---|
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Hamburg |
3 |
E1 |
2022 |
1 |
Budget |
V1 |
Volume |
Sandwich |
20 |
E1 |
2022 |
2 |
Budget |
V1 |
Volume |
Sandwich |
25 |
E1 |
2022 |
3 |
Budget |
V1 |
Volume |
Sandwich |
30 |
E1 |
2022 |
BegBalance |
Budget |
V1 |
Price |
Sandwich |
5 |
E2 |
2022 |
1 |
Budget |
V1 |
Volume |
Hamburg |
100 |
E2 |
2022 |
2 |
Budget |
V1 |
Volume |
Hamburg |
110 |
E2 |
2022 |
3 |
Budget |
V1 |
Volume |
Hamburg |
120 |
E3 |
2022 |
1 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
50 |
E3 |
2022 |
2 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
80 |
E3 |
2022 |
3 |
Budget |
V1 |
Sales_Adjs |
Hamburg |
-110 |
E2 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Hamburg |
500 |
E2 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Hamburg |
650 |
E2 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Hamburg |
600 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Hamburg |
300 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Hamburg |
330 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Hamburg |
360 |
E1 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Sandwich |
100 |
E1 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Sandwich |
125 |
E1 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Sandwich |
150 |
E3 |
2022 |
1 |
Budget |
V1 |
Total_Sales |
Hamburg |
50 |
E3 |
2022 |
2 |
Budget |
V1 |
Total_Sales |
Hamburg |
80 |
E3 |
2022 |
3 |
Budget |
V1 |
Total_Sales |
Hamburg |
-110 |
这种情况其实并不是我们希望的结果,因此,在计算前,我们推荐首先将可能的结果数据做清除,随后再进行计算,可以避免这种情况产生。下一节我们将介绍数据的清除公式。
MDX计算脚本中通过cleardata函数对数据进行清除。Cleardata函数同样需要被包括在Scope范围内。如下例所示:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[Q1]),MemberSet([Product].[TotalFood],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
Cleardata([Account].[Total_Sales],[Account].[Price]);
End Scope;
以上计算公式的含义是: 清除在Budget场景,V1版本,2022年,Q1所有后代期间,所有的末级实体,所有末级食品类产品上的Total_Sales科目和Price科目的数值。
同数学计算脚本类似,这里也需要财务模型的所有维度都被包括在scope以及clearData的参数中。如果遇到缺少的维度,系统会给出错误提示。
有了数据清楚功能,我们就可以解决2.1.4中遇到的问题,如下:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[Q1]),MemberSet([Product].[TotalFood],-1,Leaves),MemberSet([Entity].[TotalEntity],-1,Leaves));
Cleardata([Account].[Total_Sales]);
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price]->[Period].[BegBalance]+[Account].[Sales_Adjs];
End Scope;
这样,在计算[Account].[Total_Sales]前,会先将该成员上所有可能已经存在的数据都删除,随后再进行计算。
在财务模型中,有些维度组合上的数据,是在每次查询时,动态从系统中获取的,比如通用列上的汇总维度成员。因此,这类节点,即使在计算脚本的Scope范围内被包含了,也不会触发对应的计算。如下例:
Scope([Scenario].[Budget], [Version].[V1], [Year].[2022],MemberSet([Period].[Q1]),MemberSet([Entity].[TotalEntity],-1,Leaves),MemberSet([Product].[TotalFood]));
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price]->[Period].[BegBalance];
End Scope;
上例中,MemberSet([Product].[TotalFood]))这个表达式其实是会返回TotalFood这个节点下的所有后代成员,假设Product维度对应的列被设置为了通用类,TotalFood下的节点的成员如下:
那么,上面的计算逻辑仅仅会对Hamburg,SandWich,Potato,Tomato和Onion这5个节点计算,而不会对FastFood和Vegetable进行计算,因为这两个成员是汇总成员,他们对应的数值来自下级的数据的汇总,而不是公式的计算。
再看如下的
Scope([Scenario].[Budget], [Version].[V_TopDown],[Version].[V2], [Year].[2022],MemberSet([Period].[Q1]),MemberSet([Entity].[TotalEntity],-1,Leaves),[Account].[Total_Sales]);
[Product].[TotalFood] = [Product].[TotalFood]->[Version].[V1]->[Year].[2021]->[Scenario].[Actual]
End Scope;
在该示例中,我们需要将2021实际的所有商品的销售额的总数,拷贝到2022年的预算数上,这里虽然[Product].[TotalFood]这个成员是个通用类维度的汇总节点,但由于版本维度上的成员V_TopDown的isTopDown属性为True,因此等号左侧的维度成员同Scope范围内的成员可以组成可输入的单元格,因此该计算可以执行。而版本维度的V2成员我们假设他的isTopDown为False,因此该版本成员无法参与计算,将被自动忽略。 同理,科目维的base成员InputOnParentNode属性为True时,此时TotalFood汇总时,不包含该成员的数值,为False时,则包含该成员; 模型开始累计数功能时,View成员和期间成员则也需要按照现有的规则限制仅支持在符合的层级成员录入和保存。
最终,以上脚本在执行的时候,会把Scope参数中的[Version].[V2]成员忽略,并在剩余的成员的限定范围内执行计算。
如果条件为真,则执行某语句,否则执行另一段语句。 语法
IF expression
THEN
assignment
END IF;
IF expression
THEN
assignment1
ELSE
assignment2
END IF;
IF expression1
THEN
assignment1
ELSEIF expression2
THEN
assignment2
ELSEIF expression3
THEN
assignment3
...
END IF;
参数 expression 计算结果为返回 True 或 False 的布尔值的多维表达式 (MDX)。 assignment 为子多维数据集或计算属性赋值的 MDX 表达式。或完整的scope语句块。 备注 IF语句可以使用在两个地方,计算公式语句里,和Scope语法块之外。以下例子会分别介绍。
这种用法常见于对Scope范围内不同的成员做不同的计算处理。 下例表示了根据Entity维度的ud1做判断,当Entity成员的 ud1等于不同的值时,进行不同的计算逻辑。
Scope(
[Scenario].[Budget],
[Version].[V1],
[Year].[2022],
Base([Period].[TotalPeriod])
IDescendant([Entity].[Total])
);
// 根据Entity维度的ud1做判断,
// ud1等于Shanghai的Entity成员按照Amount1+Amount2计算,
// 其余Entity成员按照Amount1+Amount3计算。
IF [Entity].CurrentMember.Properties("ud1") = "Shanghai"
//等价于 IF Properties(CurrentMember([Entity]),"ud1") = "Shanghai"
THEN
[Account].[Amount_A] = [Account].[Amount1] + [Account].[Amount2];
ELSE
[Account].[Amount_B] = [Account].[Amount1] + [Account].[Amount3];
END IF;
End Scope;
下例表示了根据Entity维度成员的父级做判断,不同父级下的成员,进行不同的计算逻辑。 需要注意的是,在判断两个对象是否相等时,要注意MDX对象和普通数据类型的区别。对于维度,维度成员,集合等MDX对象的判断需要用IS关键字,对于数值,字符串等普通类型需要用等号=。
Scope(
[Scenario].[Budget],
[Version].[V1],
[Year].[2022],
Base([Period].[TotalPeriod])
IDescendant([Entity].[Total])
);
// 根据Entity维度的父级做判断
IF [Entity].CurrentMember.Parent IS [Entity].[Region01]
THEN
[Account].[Amount_A] = [Account].[Amount1] + [Account].[Amount2];
ELSEIF [Entity].CurrentMember.Parent IS [Entity].[Region02]
THEN
[Account].[Amount_B] = [Account].[Amount1] + [Account].[Amount3];
ELSE
[Account].[Amount_B] = [Account].[Amount1] + [Account].[Amount2];
END IF;
End Scope;
多个逻辑判断语句通过and或or连接时,需要注意是否加上括号()来表示判断的优先级。如下例中 ([Entity].CurrentMember.Parent IS [Entity].[Region01] AND [Entity].CurrentMember.Properties(“ud1”) = “Shanghai”) OR ([Entity].CurrentMember.Parent IS [Entity].[Region02] AND [Entity].CurrentMember.Properties(“ud1”) = “BeiJing”) 表示先判断Entity成员的父级是[Entity].[Region01]并且属性ud1是Shanghai的, 同时判断Entity成员的父级是[Entity].[Region02]并且属性ud1是BeiJing的,将两个判断的结果再取并集:
Scope(
[Scenario].[Budget],
[Version].[V1],
[Year].[2022],
Base([Period].[TotalPeriod])
IDescendant([Entity].[Total])
);
// 注意判断优先级
IF ([Entity].CurrentMember.Parent IS [Entity].[Region01] AND
[Entity].CurrentMember.Properties("ud1") = "Shanghai")
OR
([Entity].CurrentMember.Parent IS [Entity].[Region02] AND
[Entity].CurrentMember.Properties("ud1") = "BeiJing")
THEN
[Account].[Amount_A] = [Account].[Amount1] + [Account].[Amount2];
ELSEIF [Entity].CurrentMember.Parent IS [Entity].[Region02]
THEN
[Account].[Amount_B] = [Account].[Amount1] + [Account].[Amount3];
ELSE
[Account].[Amount_B] = [Account].[Amount1] + [Account].[Amount2];
END IF;
End Scope;
关于运算符计算的默认优先级,请参考:运算符优先级
IF可以嵌套,如下例:
Scope(
[Scenario].[Budget],
[Version].[V1],
[Year].[2022],
Base([Period].[TotalPeriod])
IDescendant([Entity].[Total])
);
// 注意判断优先级
IF [Entity].CurrentMember.Parent IS [Entity].[Region01] THEN
IF [Entity].CurrentMember.Properties("ud1") = "Shanghai") THEN
[Account].[Amount_A] = [Account].[Amount1] + [Account].[Amount2];
ELSE
[Account].[Amount_A] = [Account].[Amount1] + [Account].[Amount3];
END IF;
ELSEIF [Entity].CurrentMember.Parent IS [Entity].[Region02] THEN
IF [Entity].CurrentMember.Properties("ud1") = "BeiJing") THEN
[Account].[Amount_B] = [Account].[Amount1] + [Account].[Amount2];
ELSE
[Account].[Amount_B] = [Account].[Amount1] + [Account].[Amount3];
END IF;
END IF;
End Scope;
这种用法常见于,根据变量的不同,计算不同的Scope范围。 下面例子是一个滚动预算场景下的IF用法。根据外部传参Scenario的不同,定义不同的Scope范围和计算逻辑。
IF
$Scenario="Forcast1_11"
THEN
Scope(
StrToMember('Scenario',$Scenario), // Scenario维度成员从表单pov获取
[Version].[V1],
[Year].[2022],
{[Period].[2],[Period].[3],[Period].[4],[Period].[5],[Period].[6],[Period].[7],[Period].[8],[Period].[9],[Period].[10],[Period].[11],[Period].[12]},
IDescendant([Entity].[Total])
);
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
End Scope;
ELSEIF
$Scenario="Forcast2_10"
THEN
Scope(
StrToMember('Scenario',$Scenario), // Scenario维度成员从表单pov获取
[Version].[V1],
[Year].[2022],
{[Period].[3],[Period].[4],[Period].[5],[Period].[6],[Period].[7],[Period].[8],[Period].[9],[Period].[10],[Period].[11],[Period].[12]},
IDescendant([Entity].[Total])
);
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
End Scope;
ELSEIF
$Scenario="Forcast3_9"
THEN
Scope(
StrToMember('Scenario',$Scenario), // Scenario维度成员从表单pov获取
[Version].[V1],
[Year].[2022],
{[Period].[4],[Period].[5],[Period].[6],[Period].[7],[Period].[8],[Period].[9],[Period].[10],[Period].[11],[Period].[12]},
IDescendant([Entity].[Total])
);
[Account].[Total_Sales] = [Account].[Volume]*[Account].[Price];
End Scope;
...
END IF;
1, 用在Scope范围定义中
Scope(
[Scenario].[Budget],
[Version].[V1],
[Year].[2022],
// 以下是错误用法
IF
$Scenario="Forcast1_11"
THEN
{[Period].[2],[Period].[3],[Period].[4],[Period].[5],[Period].[6],[Period].[7],[Period].[8],[Period].[9],[Period].[10],[Period].[11],[Period].[12]}
ELSE
...
ENDIF,
IDescendant([Entity].[Total])
);
// 根据Entity维度的父级做判断
IF [Entity].CurrentMember.Parent IS [Entity].[Region01]
THEN
[Account].[Amount_A] = [Account].[Amount1] + [Account].[Amount2]
ELSEIF [Entity].CurrentMember.Parent IS [Entity].[Region02]
THEN
[Account].[Amount_B] = [Account].[Amount1] + [Account].[Amount3]
ELSE
[Account].[Amount_B] = [Account].[Amount1] + [Account].[Amount2]
END IF;
End Scope;
2, 用在计算公式等号右边
Scope(
[Scenario].[Budget],
[Version].[V1],
[Year].[2022],
Base([Period].[TotalPeriod])
IDescendant([Entity].[Total])
);
// 以下是错误用法
[Account].[Amount_A] =
IF [Entity].CurrentMember.Parent IS [Entity].[Region01]
THEN
[Account].[Amount1] + [Account].[Amount2]
ELSE
[Account].[Amount1] + [Account].[Amount2]
END IF;
End Scope;
回到顶部
咨询热线