代码改变世界

MDX Step by Step 读书笔记(八) - Navigating Hierarchies 层次结构导航

2013-11-22 17:13  BIWORK  阅读(3203)  评论(0编辑  收藏  举报

开篇介绍

本章主要内容包括:

  1. 解释各种不同的 MDX 导航函数的使用: Parent, Children, FirstChild, LastChild, Siblings, FirstSibling, LastSibling, RANK
  2. 通过导航函数定位层次结构中的成员: Ancestor, Ancestors, Ascendants, Decendants, Cousin
  3. 在层次结构中估算成员的位置: PrevMember, NextMember, Lag, Lead

分析服务中的一个显著的特征就是在层次结构中组织成员。在一个给定的层次结构中,无论是属性层次结构还是用户自定义层次结构,每一个成员在特定的级别下都有一个精确的位置。它的位置主要由它与更高一级成员的关系和在同级别成员的顺序来决定的, 在层次结构中成员的这种位置的性质使得成员之间有了么某种关系,因此在 MDX 函数中可以通过导航很容易的定位到指定的成员。

Accessing Immediate Relatives 访问直属关系的成员

层次结构中的成员关系经常被形容为家族关系,一个指定的成员,它的直接上层被称为父类成员,相对于这个父类成员来说当前成员是它的子类成员。一个成员作为父类可能有多个子类成员,子类成员之间就是这种 Sibling 兄弟的关系。

MDX 中提供了很多函数可以定位和访问到给定成员的其它直属亲戚 。

通过在指定成员后跟上 Parent 函数来定位到父类成员。

[Product].[Product Categories].[Subcategory].[Road Bikes].Parent

由于父类函数本身就返回一个成员,因此可以在此基础上再继续使用其它的导航函数。这个例子中首先通过 Parent 函数定位到了 Road Bikes 的父类成员,接着通过 Children 函数定位到了这个父类成员下的所有子成员,这时返回的是一个集合。

[Product].[Product Categories].[Subcategory].[Road Bikes].Parent.Children

同样的子成员集合也可以通过 Siblings 函数来搞定,与上面的这种访问方式的效果都是一样的,但是应该采用这种更直接和间接的方法。

[Product].[Product Categories].[Subcategory].[Road Bikes].Siblings

直接看下面的这些例子,先查询出 Product Categories 下所有成员的 Reseller Sales Amount 的情况。

这个查询非常简单,Product Categories 层次结构中的成员沿着 ROWS 轴从层次结构中的多个 Level 级别中被抽取并呈现出来,Reseller Sales Amount 与 ROWS 轴上的每一个成员都进行了关联并展示着不同的 Sales Amount 值。

先来查看一下各个成员的父成员信息 - 使用 Parent 函数定位到父成员,通过 .Name 属性获取成员的名称。

 

查看一下各个成员与父成员的 Reseller Sales Amount 比 -

WITH
MEMBER [Measures].[Parent Member] AS
  [Product].[Product Categories].CurrentMember.Parent.Name
MEMBER [Measures].[Parent Sales Amount] AS
  ([Product].[Product Categories].CurrentMember.Parent,[Measures].  [Reseller Sales Amount])
MEMBER [Measures].[Percent of Parent] AS
  ([Measures].[Reseller Sales Amount])
  /
  (
   [Product].[Product Categories].CurrentMember.Parent,
   [Measures].[Reseller Sales Amount]
  )
,FORMAT="Percent"
SELECT
{
   ([Measures].[Reseller Sales Amount]),
   ([Measures].[Parent Member]),
   ([Measures].[Parent Sales Amount]),
   ([Measures].[Percent of Parent])
} ON COLUMNS,
{[Product].[Product Categories].Members} ON ROWS
FROM [Step-by-Step]

其中有出现类似于 1. #INF 情况,因为像  All Products 这个成员没有父成员,因此相当于除零。

应该加上 if 判断来解决这个问题 - 

WITH
MEMBER [Measures].[Parent Member] AS
  [Product].[Product Categories].CurrentMember.Parent.Name
MEMBER [Measures].[Parent Sales Amount] AS
  ([Product].[Product Categories].CurrentMember.Parent,[Measures].  [Reseller Sales Amount])
MEMBER [Measures].[Percent of Parent] AS
   IIF(
    [Product].[Product Categories].CurrentMember.Parent IS NULL,
    NULL,
    ([Measures].[Reseller Sales Amount])
    /
    (   
     [Product].[Product Categories].CurrentMember.Parent,
     [Measures].[Reseller Sales Amount]
    )
)
,FORMAT="Percent"
SELECT
{
  ([Measures].[Reseller Sales Amount]),
  ([Measures].[Parent Member]),
  ([Measures].[Parent Sales Amount]),
  ([Measures].[Percent of Parent])
} ON COLUMNS,
{[Product].[Product Categories].Members} ON ROWS
FROM [Step-by-Step]

NULL 值是允许的,但是出现 Infinity Error 这种应该是要被避免的。

 

RANK 函数

在之前的练习中,层次结构中的每一个成员都参与了父类成员度量值总和的统计。这种与父类成员百分比的比较非常直观,这种比较也经常会出现了成员与兄弟成员之间。

兄弟成员之间的比较通常会比较成员之间的排名,可以通过 MDX 的排名函数来实现:

Rank( Tuple, {Set} [, Expression] )

Rank 排名函数可以确定在元组集合中单个元组的位置,元组和集合分别是 RANK 函数中的前两个参数。第三个参数是可选的,提供了一个表达式主要是用来说明在执行排名搜索之前哪一个指定的集合是排序的(降序)。

  • 如果能够在集合中找到指定的元组,RANK 函数 将返回以 1 开始的这个元组在集合中的位置。如果没有找到指定的元组,那么就返回一个 0。
  • 如果集合中的元组通过表达式参数实现了排序,并且有 多个元组通过计算得到同样的值的话,那么这些元组将被认定在集合中绑定在同一个位置,因此将得到同样的排名。在集合中随后的元组根据在它之前元组的序号计算并返回一个排名,这样可能因为有重复的排名而导致最终排名出现断序。

下面的这个例子中, RANK 函数将用来返回在集合中的当前 Product Categories 成员在它所在集合中兄弟成员组成的集合中的位置,一定要记住的是兄弟成员函数包括了当前指定的成员。

在这个集合被搜索之前,分析服务首先根据 RANK 函数中的第三个参数也就是 Reseller Sales Amount 对集合中的每一个元组按照降序排序。

查询的结果可能看起来有点别扭,因为这个排序只是反映出当前成员在它的兄弟成员集合中的排名情况。如果需要从高到低进行排序,还需要修改一下 MDX 语句。

WITH
MEMBER [Measures].[Sibling Rank] AS
  RANK(
    [Product].[Product Categories].CurrentMember,
    [Product].[Product Categories].CurrentMember.Siblings,
    ([Measures].[Reseller Sales Amount])
   )
SELECT
{
   ([Measures].[Reseller Sales Amount]),
   ([Measures].[Sibling Rank])
} ON COLUMNS,
{[Product].[Product Categories].Members} ON ROWS
FROM [Step-by-Step]

 

WITH
MEMBER [Measures].[Sibling Rank] AS
Rank(
  [Product].[Product Categories].CurrentMember,
  [Product].[Product Categories].CurrentMember.Siblings,
  ([Measures].[Reseller Sales Amount])
)
SELECT
{
  ([Measures].[Reseller Sales Amount]),
  ([Measures].[Sibling Rank])
} ON COLUMNS,
{
Order(
          {[Product].[Product Categories].Members},
          ([Measures].[Sibling Rank]),
          ASC
        )
} ON ROWS
FROM [Step-by-Step]

从这个查询结果来看,成员 All Products 在它的兄弟集合中排名为 1,因为它之后一个成员。并且在 Category 这一个层级上,Bikes 也是排名为 1 ,它有几个其它的兄弟成员,比如说 Mountain Bikes 或者 Touring Bikes。 

访问旁系亲属 (Accessing Extended Relatives)

就如同一个家族不仅仅有直系亲属,它也有很多旁系亲属,那么在 MDX导航中也并不局限于直接亲属。分析服务将家族式元素访问扩展到跨层次结构访问。一个给定成员的父类是许多其它向上扩展的祖先的根成员之一。相对于祖先来说,一个给定成员的后代和它自己更多个后代同时同一条线上的不同延伸。

对应的有如下这些 MDX 函数用来访问一些旁系亲属。

因为这几个导航的灵活程度更高一些,因此这几个导航函数理解起来也比较复杂一些。 

Ancestor (Member, Level | Distance) 如果指定了级别表达式,则 Ancestor 返回指定成员在指定级别处的祖先。如果指定成员与指定级别不在同一个层次结构中,就返回一个错误。 如果指定了距离,则 Ancestor 返回层次结构中比成员表达式高出指定步骤数的指定成员的祖先。可以将成员指定为属性层次结构的成员或用户定义层次结构的成员,有时还可以指定为父子层次结构的成员。数值 1 返回成员的父成员,数值 2 返回成员的祖父成员 (如果存在),数值 0 返回成员本身。

Ancestors  和 Ancestor 的概念类似,只不过 在 SSAS 中 Ancestors 返回的是一个由单个成员构成的集合,因为在 SSAS 中不支持一个成员有多个父成员。

Ascendants (Member) - 返回从某一成员本身向上直到该成员的层次结构顶层的该成员的所有祖先;更具体来讲,它对指定成员的层次结构执行后序遍历,然后在集中返回与该成员相关的所有祖先成员,其中包括该成员本身。

Descendants( Member | {Set} [,Level | Distance [, Flag]])  - 返回在指定级别或距离上的后代集,可以选择包或不包括其他级别的后代。

  • 如果指定了级别, Descendants 函数将返回一个后代集,其中包含指定成员的后代或指定集成员的后代,这些后代处于所指定的级别,并且可以通过 Desc_Flag 中指定的标志来修改。
  • 如果指定了 Distance,Descendants 函数将返回一个集,其中包含指定成员的后代或者指定集成员的后代,这些后代在指定成员的层次结构中与指定成员之间相距指定的级别数,并且可以通过  Desc_Flag 中指定的标志来修改。通常情况下,此函数与 Distance 参数一同用于处理不规则的层次结构。如果指定距离为零 0 ,该函数将返回仅由指定的成员或指定的成员集组成的集。
  • 如果指定了集表达式,将针对该集中的每个成员分别解析 Descendants 函数,并重新创建该集。也就是说,Descendants 函数所使用的语法功能与 MDX Generate 函数相同。
  • 如果未指定级别或距离,则通过为指定的成员(如果指定了成员)调用 level 函数 (<<Member>>.Level),或通过为指定的集(如果指定了集)中的每个成员调用 Level 函数来确定该函数使用的级别默认值。 如果未指定级别表达式、距离或标志,此函数将在假定使用了以下语法的情况下执行操作:
Descendants
(
 Member_Expression ,
 Member_Expression.Level ,
 SELF_BEFORE_AFTER
)

如果指定了级别但未指定说明标志,此函数将在假定使用了以下语法的情况下执行操作:

Descendants
(
  Member_Expression ,
  Level_Expression,
  SELF
)

Cousin (Member1, Ancestor Member2) - 返回在父成员下方与指定子成员具有相同的相对位置的子成员。

 

这个是成员,级别以及一些标志的关系图。

前面看了那么多,后面直接来看一些例子吧!

WITH
MEMBER [Measures].[Percent of Parent] AS
IIF(
   [Product].[Product Categories].CurrentMember.Parent IS NULL,
   NULL,
   ([Measures].[Reseller Sales Amount])/
   (
      [Product].[Product Categories].CurrentMember.Parent,
      [Measures].[Reseller Sales Amount]
   )
)
,FORMAT="Percent"
SELECT
{
   ([Measures].[Reseller Sales Amount]),
   ([Measures].[Percent of Parent])
} ON COLUMNS,
{
   [Product].[Product Categories].Members
} ON ROWS
FROM [Step-by-Step]

 这个例子查询 Product Category 层次结构中的成员与父类成员的  Reseller Sales Amount 比。

 

上面的这种写法并没有什么问题,但是像 Accessories 这种属于 Category 级别的如果不需要去看它占父类的比例的话,我们应该限定最高的一个级别就是 Category。

WITH
MEMBER [Measures].[Percent of Parent] AS
IIF(
     [Product].[Product Categories].CurrentMember.Parent Is Null,
     Null,
     ([Measures].[Reseller Sales Amount])/
     (
       [Product].[Product Categories].CurrentMember.Parent,
       [Measures].[Reseller Sales Amount]
     )
)
,FORMAT="Percent"
MEMBER [Measures].[Percent of Category] AS
IIF(
     Ancestor(
         [Product].[Product Categories].CurrentMember,
         [Product].[Product Categories].[Category]
     ) Is Null,
     Null,
    ([Measures].[Reseller Sales Amount])/
    (
       Ancestor(
                    [Product].[Product Categories].CurrentMember,
                    [Product].[Product Categories].[Category]
                  ),
                [Measures].[Reseller Sales Amount]
     )
)
,FORMAT="Percent"
SELECT
{
   ([Measures].[Reseller Sales Amount]),
   ([Measures].[Percent of Parent]),
   ([Measures].[Percent of Category])
} ON COLUMNS,
{[Product].[Product Categories].Members} ON ROWS
FROM [Step-by-Step]

可以看到 Category 这个级别就是最高级别了,因此 Accessories 这个就是 100%了。

 

Calculate a product’s percent contribution across its lineage 沿着血缘关系计算 Product 百分比贡献

先查询一下 Product 级别下 [Mountain-200 Black, 42] 成员的 Reseller Sales Amount 值。

SELECT
{([Measures].[Reseller Sales Amount])} ON COLUMNS,
{([Product].[Product Categories].[Product].[Mountain-200 Black, 42])} ON ROWS
FROM [Step-by-Step]

但是有时也很喜欢不光看到这个具体的产品,而且还喜欢看到这个产品属于哪一个子类,哪一个大类,这些信息都需要。

SELECT
{([Measures].[Reseller Sales Amount])} ON COLUMNS,
HIERARCHIZE({
   Ascendants(
    [Product].[Product Categories].[Product].[Mountain-200 Black, 42]
   )
}) ON ROWS
FROM [Step-by-Step]

因此通过 Ascendants 可以看到这个产品及它所有父类成员的 Reseller Sales Amount。

还可以计算一下这个产品在各个父类层级所占有的百分比。

WITH MEMBER [Measures].[Mountain-200 Black 42 in Parent]
AS
([Product].[Product Categories].[Product].[Mountain-200 Black, 42],
 [Measures].[Reseller Sales Amount])
 /
 ([Product].[Product Categories].CurrentMember,[Measures].[Reseller Sales Amount])
,FORMAT = "Percent"
SELECT
{([Measures].[Reseller Sales Amount]),
([Measures].[Mountain-200 Black 42 in Parent])}
ON COLUMNS,
   HIERARCHIZE({
           Ascendants(
              [Product].[Product Categories].[Product].[Mountain-200 Black, 42]
           )
}) ON ROWS
FROM [Step-by-Step]

 

Assemble the set of descendants for a given category 搞定指定 Category 下的子孙们

先查询一下 Bikes 这个分类上的  Reseller Sales Amount 值。

SELECT
{([Measures].[Reseller Sales Amount])} ON COLUMNS,
{[Product].[Product Categories].[Category].[Bikes]} ON ROWS
FROM [Step-by-Step]

如果想查看 [Product].[Product Categories].[Category].[Bikes] 这个成员在 [Product].[Product Categories].[Subcategory] 级别上有哪些子成员的话,可以使用 Descendants 函数来查询。 比如说 [Product].[Product Categories].[Category].[Bikes] 是张三的话, [Product].[Product Categories].[Subcategory] 相当于指定了一个级别,比如说是儿子,那么就是把张三的儿子这个级别上的成员全部提取出来形成一个集合,说简单一点就是把张三的儿子们全都查出来。

SELECT
{([Measures].[Reseller Sales Amount])} ON COLUMNS,
Descendants(
   {[Product].[Product Categories].[Category].[Bikes]},
   [Product].[Product Categories].[Subcategory]
) ON ROWS
FROM [Step-by-Step]

把这个例子修改一下,这次变成查询 Product 了,也就是查询张三的孙子们了。

SELECT
{([Measures].[Reseller Sales Amount])} ON COLUMNS,
Descendants(
  {[Product].[Product Categories].[Category].[Bikes]},
  [Product].[Product Categories].[Product],
) ON ROWS
FROM [Step-by-Step]

那么有一个问题,能不能在只定义 儿子的级别上去查查张三的孙子,或者在只定义孙子的级别上去查查张三的儿子们? 还是回头再看看这幅图 -

Descendants (Member, Level, Flag) 中,Member 是张三,指定的级别默认情况下并没有添加 Flag 标志,但是在添加了 Flag 标志之后就可以上下移动了。

我们来看,在张三的儿子这个级别上去查查张三的孙子,自然 Level 应该在儿子的级别上往下移动,添加一个 After 标志即可。

SELECT
{([Measures].[Reseller Sales Amount])} ON COLUMNS,
Descendants(
   {[Product].[Product Categories].[Category].[Bikes]},
   [Product].[Product Categories].[Subcategory],
   AFTER
) ON ROWS
FROM [Step-by-Step]

那么在张三孙子的级别上来看看儿子们,就需要往上移动,使用 BEFORE 即可。当然要注意这里 Bikes 本身也可以被查询出来,因为这里的 Descendants 表明的是一个范围,从哪里到哪里的一个范围并不是只在一个级别上查询。

SELECT
{([Measures].[Reseller Sales Amount])} ON COLUMNS,
Descendants(
   {[Product].[Product Categories].[Category].[Bikes]},
   [Product].[Product Categories].[Product],
   BEFORE
) ON ROWS
FROM [Step-by-Step]

比如在儿子的级别上去查询孙子,但是又想查询自己,这时就可以使用 BEFORE_AND_AFTER 。先定位到级别,然后从级别这个角度去看 Before 和 After。

SELECT
{([Measures].[Reseller Sales Amount])} ON COLUMNS,
Descendants(
  {[Product].[Product Categories].[Category].[Bikes]},
  [Product].[Product Categories].[Subcategory],
  BEFORE_AND_AFTER
) ON ROWS
FROM [Step-by-Step]

 

调查层次结构中的成员 Investigating Members in a Hierarchy 

可以通过下面的几个小函数来判断在层次结构上某些成员的位置特征。

IsSibling(Member_Expression1, Member_Expression2) - 返回一个指定成员是否是另一个指定成员的同级。

IsAncestor(Member_Expression1, Member_Expression2) - 返回一个指定成员是否是另一个指定成员的祖先。

IsLeaf(Member_Expression) - 判断指定成员是否是叶成员。

WITH
MEMBER [Measures].[Number of Children] AS
IIF(
  IsLeaf([Product].[Product Categories].CurrentMember),
   "N/A",
   COUNT(
   [Product].[Product Categories].CurrentMember.Children
   )
)
SELECT
{[Measures].[Number of Children]} ON COLUMNS,
{[Product].[Product Categories].Members} ON ROWS
FROM [Step-by-Step]

这个例子判断当前成员是否是叶成员,如果不是就计算它的子成员的数量。

 

Navigating within a Level 级别内导航

  1. PrevMember - 返回指定成员所在级别的上一个成员。
  2. NextMember - 返回指定成员所在级别上的下一个成员。
  3. Lag - 返回在成员级别中比指定成员位置靠前且靠前位数为指定位数的成员。
  4. Lead - 返回在成员级别中比指定成员位置靠后且靠后位数为指定位数的成员。
SELECT
{([Measures].[Reseller Sales Amount])} ON COLUMNS,
{[Date].[Calendar].[Month].Members} ON ROWS
FROM [Step-by-Step]

新增加一列,当前成员的前一个成员的 Reseller Sales Amount 以及它们之间的差额。

WITH
MEMBER [Measures].[Prior Period Reseller Sales] AS
(
   [Date].[Calendar].CurrentMember.PrevMember,
   [Measures].[Reseller Sales Amount]
)
,FORMAT="Currency"
MEMBER [Measures].[Change in Reseller Sales] AS
   ([Measures].[Reseller Sales Amount]) - ([Measures].[Prior Period Reseller Sales])
,FORMAT="Currency"
SELECT
{
   ([Measures].[Reseller Sales Amount]),
   ([Measures].[Prior Period Reseller Sales]),
   ([Measures].[Change in Reseller Sales])
} ON COLUMNS,
{[Date].[Calendar].[Month].Members} ON ROWS
FROM [Step-by-Step]

更多 BI 文章请参看 BI 系列随笔列表 (SSIS, SSRS, SSAS, MDX, SQL Server)  如果觉得这篇文章看了对您有帮助,请帮助推荐,以方便他人在 BIWORK 博客推荐栏中快速看到这些文章。