这一部分包含了两部分内容,一部分是对 MDX Infinite Recursion 无限循环的原理解析,第二部分是对计算成员的 SOLVE_ORDER 原理进行了解析。
计算成员中上下文冲突的问题 - 避免无限循环 Avoiding Infinite Recursion
直接看一个例子,并假设我们的纬度中多了一个地理纬度和国家这个层次结构。
WITH MEMBER [Product].[Category].[All Products].[Bikes & Accessories] AS ([Geography].[Country].[United States]) + ([Geography].[Country].[Canada]) SELECT { ([Date].[Calendar Year].[CY 2003]), ([Date].[Calendar Year].[CY 2004]) } ON COLUMNS, {[Product].[Category].AllMembers} ON ROWS FROM [Step-by-Step];
查询结果
双击 单元格中出现的 #Error 出现下面的信息,指出 Bikes & Accessories 依赖于 Bikes & Accessories 构成了一个无限循环 Infinite Recursion
我们来分析下造成这个结果的原因:
按照我们之前所分析的过程,首先要对某一个具体的元组进行填充,直接用之前的例子并加一个地理纬度。
( [Date].[Calendar Year].[CY 2003], [Date].[Fiscal Year].[All Periods], [Product].[Category].[Bikes & Accessories], [Product].[Subcategory].[All Products], [Measures].[Reseller Sales Amount], [Geography].[Country].[All Countries] )
当这个元组被填充完毕后,需要进入到[Product].[Category].[Bikes & Accessories] 计算成员中分析具体的元组,当前计算成员中的表达式是:
([Geography].[Country].[United States]) + ([Geography].[Country].[Canada])
那么需要对当前这两个局部元组进行填充,按照上一篇笔记中提到了一个非常重要的原则 - 引用当前环境下没有的引用。也就是说除了[Geography].[Country].[All Countries] 不引用外,其它的都引用,这样来填充一个局部元组,就变成了:
( [Date].[Calendar Year].[CY 2003], [Date].[Fiscal Year].[All Periods], [Product].[Category].[Bikes & Accessories], [Product].[Subcategory].[All Products], [Measures].[Reseller Sales Amount], [Geography].[Country].[United States] )
加上
( [Date].[Calendar Year].[CY 2003], [Date].[Fiscal Year].[All Periods], [Product].[Category].[Bikes & Accessories], [Product].[Subcategory].[All Products], [Measures].[Reseller Sales Amount], [Geography].[Country].[ Canada] )
然后发现[Product].[Category].[Bikes & Accessories]还需要被解析,这样一直解析下去就会出现刚才的那种错误,错误指出[Product].[Category].[Bikes & Accessories] 依赖于 [Product].[Category].[Bikes & Accessories] 就是这个原因。
一般情况下,为了避免这种问题的发生,最好的方式就是确保一旦定义这个计算成员属于哪一个属性层次结构,那么在它的表达式中也最好引用同一个属性层次结构下的其它成员。如果有一个或者多个引用引用了其它的计算成员,也要确保那些计算成员不要直接或者间接地再指回来,这样也会造成无限循环。
控制解决的顺序 Controlling Solve Order
当一个元组包含一个以上的计算成员的时候,有时需要使用 SOLVE_ORDER 属性来控制它们的运算顺序。
WITH MEMBER [Measures].[Combined Sales Amount] AS ([Measures].[Reseller Sales Amount])+([Measures].[Internet Sales Amount]) MEMBER [Product].[Category].[All Products].[Percent Bikes] AS ([Product].[Category].[Bikes])/([Product].[Category].[All Products]) ,FORMAT_STRING="Percent" SELECT { ([Measures].[Reseller Sales Amount]), ([Measures].[Internet Sales Amount]), ([Measures].[Combined Sales Amount]) } ON COLUMNS, {[Product].[Category].AllMembers} ON ROWS FROM [Step-by-Step]
这个示例中有两个计算成员:
一个是度量值成员 [Measures].[Combined Sales Amount] 用来存放零售和网络销售的总金额。
另一个是Product 纬度下的Bikes这类产品在占整个产品的销售比例。
在查询的结果中 Percent Bikes 和 Combined Sales Amount 对应的那一个单元格的值应该是 82.41+96.46 的平均百分比。
当没有指定 SOLVE_ORDER 的时候,SSAS 可能正好选择了 Combined Sales Amount 为高优先级别。也就是说先计算 Combined Sales Amount,后计算 Percent Bikes 的值。 (我理解的和书上不一样)
把这个例子改简单点,然后准备分解它:
WITH MEMBER [Measures].[Combined Sales Amount] AS ([Measures].[Reseller Sales Amount])+([Measures].[Internet Sales Amount]) ,SOLVE_ORDER = 2 MEMBER [Product].[Category].[All Products].[Percent Bikes] AS ([Product].[Category].[Bikes])/([Product].[Category].[All Products]) ,FORMAT_STRING="Percent" ,SOLVE_ORDER = 1
SELECT { ([Measures].[Combined Sales Amount]) } ON COLUMNS, {[Product].[Category].[Percent Bikes]} ON ROWS FROM [Step-by-Step]
首先,根据这个查询我们可以得到一个元组,假设只有这两个纬度或者层次结构,这就是一个完整的元组。
( [Measures].[Combined Sales Amount], [Product].[Category].[Percent Bikes] )
根据SOLVE_ORDER 的优先级别,数值越大的越优先处理,因此将先解析计算成员[Measures].[Combined Sales Amount]。
那么在计算成员[Measures].[Combined Sales Amount]中,表达式中的两个元组将被填充和变形为:
( [Measures].[Reseller Sales Amount], [Product].[Category].[Percent Bikes] -- 这是表达式中局部元组没有的引用,需要被填充
) + ( [Measures].[Internet Sales Amount], [Product].[Category].[Percent Bikes] -- 这是表达式中局部元组没有的引用,需要被填充
)
这个时候,由于[Product].[Category].[Percent Bikes] 也是一个计算成员,那么就在此时对这个计算成员在以上面两个元组为上下文的前提下进行解析。
对于操作符加号之前的第一个元组 -
( [Measures].[Reseller Sales Amount], [Product].[Category].[Percent Bikes] )
实际上就变成了
( [Product].[Category].[Bikes], [Measures].[Reseller Sales Amount] -- 上下文的引用继续使用
) / ( [Product].[Category].[All Products], [Measures].[Reseller Sales Amount] -- 上下文的引用继续使用
)
那么到这里就可以完全定位到这两个元组在空间上的位置并通过查询可以得到值
SELECT [Measures].[Reseller Sales Amount] ON COLUMNS, [Product].[Category].[Bikes] ON ROWS FROM [Step-by-Step]; /** $66,302,381.56 **/
SELECT [Measures].[Reseller Sales Amount] ON COLUMNS, [Product].[Category].[All Products] ON ROWS FROM [Step-by-Step]; /** $80,450,596.98 **/
那就是 66,302,381.56 /80,450,596.98 = 0.824137
同样的道理,对于操作符之后的一个元组
( [Measures].[Internet Sales Amount], [Product].[Category].[Percent Bikes] )
就等于
( [Product].[Category].[Bikes], [Measures].[ Internet Sales Amount] -- 上下文的引用继续使用
) / ( [Product].[Category].[All Products], [Measures].[ Internet Sales Amount] -- 上下文的引用继续使用
) -- 单独的组合查询一下结果 SELECT [Measures].[Internet Sales Amount] ON COLUMNS, [Product].[Category].[Bikes] ON ROWS FROM [Step-by-Step]; /** $28,318,144.65 **/
SELECT [Measures].[Internet Sales Amount] ON COLUMNS, [Product].[Category].[All Products] ON ROWS FROM [Step-by-Step]; /** $29,358,677.22 **/
那就是 28,318,144.65/29,358,677.22 = 0.964557
因此最后计算成员[Measures].[Combined Sales Amount] 最终的结果就应该是:
0.824137 + 0.964557 的百分比 178.87%
最后看这个元组
( [Measures].[Combined Sales Amount], [Product].[Category].[Percent Bikes] )
因为先解析的是 [Measures].[Combined Sales Amount],但是在解析这个成员的过程中就已经解析了[Product].[Category].[Percent Bikes],得到的最后的结果就是178.87%
如果把它们之间的SOLVE_ORDER 互换
WITH MEMBER [Measures].[Combined Sales Amount] AS ([Measures].[Reseller Sales Amount])+([Measures].[Internet Sales Amount]) ,SOLVE_ORDER = 1 MEMBER [Product].[Category].[All Products].[Percent Bikes] AS ([Product].[Category].[Bikes])/([Product].[Category].[All Products]) ,FORMAT_STRING="Percent" ,SOLVE_ORDER = 2
SELECT { ([Measures].[Combined Sales Amount]) } ON COLUMNS, {[Product].[Category].[Percent Bikes]} ON ROWS FROM [Step-by-Step]
很显然在解析元组的过程中,Percent Bikes 这个计算成员将首先被解析:
( [Measures].[Combined Sales Amount], [Product].[Category].[Percent Bikes] -- 首先被解析,可以理解为以它为入口去填充元组
)
跳跃一下省去与上面雷同的一些步骤,那就变成了要解析:
([Product].[Category].[Bikes], [Measures].[Combined Sales Amount]) / ([Product].[Category].[All Products], [Measures].[Combined Sales Amount])
再继续变:
( ([Product].[Category].[Bikes], [Measures].[ Reseller Sales Amount]) + ([Product].[Category].[Bikes], [Measures].[ Internet Sales Amount]) ) / ( ([Product].[Category].[ All Products], [Measures].[ Reseller Sales Amount]) + ([Product].[Category].[ All Products], [Measures].[ Internet Sales Amount]) )
= 28,318,144.65 + 66,302,381.56 /29,358,677.22 + 80,450,596.98
= 94629526.21 /109809274.2 = 0.86176 = 86.17%
写了这么多,也是为了验证一下 SOLVE_ORDER 起到的作用包括 MDX 大概是如何解析这些计算成员的。
通过这些分析相信可以更深的理解 SSAS 对局部元组的填充规则,只有理解SSAS对局部元组的填充规则包括在计算成员中对已存在的上下文的引用,才能最终定位真正参与运算的单元格的值。换句话说所有的数值的出现都离不开参与运算的成员在Cube空间上的坐标定位,而这种坐标定位是通过元组填充来实现的。所以了解SSAS在不同情况下对元组的填充规则是非常重要的!
最后总结一下 SOLVE_ORDER 我理解的和书上有点不一样,通过我上面的推理 SOLVE_ORDER 值越大的计算成员被处理的优先级别就越高。
它的范围应该是从1开始,最大值是65535。
写的很辛苦! 希望我的理解是正确的!
Formatting Calculated Members
之前的例子里在计算成员中使用了 FORMAT_STRING 属性来格式化最终在查询结果中返回的值。比如像之前显示的百分比,就是通过定义 FORMAT_STRING= "Percent" 来实现了,除了这个之外,格式化的结果还有其它更多的选择。
更多 BI 文章请参看 BI 系列随笔列表 (SSIS, SSRS, SSAS, MDX, SQL Server) 如果觉得这篇文章看了对您有帮助,请帮助推荐,以方便他人在 BIWORK 博客推荐栏中快速看到这些文章。