MDX - TopCount plus 'Other' or 'The Rest' by group (over a set of members)

别来无恙 提交于 2019-12-02 05:10:55

The following is against AdvWrks and uses a technique I saw on Chris Webb's blog which he outlines here:
https://cwebbbi.wordpress.com/2007/06/25/advanced-ranking-and-dynamically-generated-named-sets-in-mdx/

The section of the script that creates the set MyMonthsWithEmployeesSets I find very difficult to get my head around - maybe @AlexPeshik could shed a little more light on what is happening in the following script.

WITH 
  SET MyMonths AS 
    TopPercent
    (
      [Date].[Calendar].[Month].MEMBERS
     ,20
     ,[Measures].[Reseller Sales Amount]
    ) 
  SET MyEmployees AS 
    [Employee].[Employee].[Employee].MEMBERS 
  SET MyMonthsWithEmployeesSets AS 
    Generate
    (
      MyMonths
     ,Union
      (
        {[Date].[Calendar].CurrentMember}
       ,StrToSet
        ("
             Intersect({}, 
             {TopCount(MyEmployees, 10, ([Measures].[Reseller Sales Amount],[Date].[Calendar].CurrentMember))
             as EmployeeSet"
            + 
              Cstr(MyMonths.CurrentOrdinal)
          + "})"
        )
      )
    ) 
  MEMBER [Employee].[Employee].[RestOfEmployees] AS 
    Aggregate
    (
      Except
      (
        MyEmployees
       ,StrToSet
        (
          "EmployeeSet" + Cstr(Rank([Date].[Calendar].CurrentMember,MyMonths))
        )
      )
    ) 
  MEMBER [Measures].[EmployeeRank] AS 
    Rank
    (
      [Employee].[Employee].CurrentMember
     ,StrToSet
      (
        "EmployeeSet" + Cstr(Rank([Date].[Calendar].CurrentMember,MyMonths))
      )
    ) 
SELECT 
  {
    [Measures].[EmployeeRank]
   ,[Measures].[Reseller Sales Amount]
  } ON 0
 ,Generate
  (
    Hierarchize(MyMonthsWithEmployeesSets)
   ,
      [Date].[Calendar].CurrentMember
    * 
      {
        Order
        (
          Filter
          (
            MyEmployees
           ,
            [Measures].[EmployeeRank] > 0
          )
         ,[Measures].[Reseller Sales Amount]
         ,BDESC
        )
       ,[Employee].[Employee].[RestOfEmployees]
      }
  ) ON 1
FROM [Adventure Works];

Edit - solution for Alex's third attempt:

WITH 
  SET [AllCountries] AS [Country].[Country].MEMBERS 
  SET [AllStates]    AS [State-Province].[State-Province].MEMBERS 
  SET [Top2States] AS 
    Generate
    (
      [AllCountries]
     ,TopCount
      (
        (EXISTING 
          [AllStates])
       ,3
       ,[Measures].[Internet Order Count]
      )
    ) 
  MEMBER [State-Province].[All].[RestOfCountry] AS 
    Aggregate({(EXISTING {[AllStates]} - [Top2States])}) 
SELECT 
  {[Measures].[Internet Order Count]} ON COLUMNS
 ,{
      [AllCountries]
    * 
      {
        [Top2States]
       ,[State-Province].[All].[RestOfCountry]
       ,[State-Province].[All]
      }
  } ON ROWS
FROM [Adventure Works];

Yes, you've got the main idea by using SET for Others, but several minor additions are required to complete the task.

I'll use my test DBs, but this can easily be transformed to yours.

  • [Report Date] - date dimension ([Klient] analogue)
  • [REPORT DATE Y] - years hierarchy ([Grupa Klientow])
  • [REPORT DATE YM] - months hierarchy ([Klient].[Klient])
  • [Measures].[Count] - measure for TopCount ([Measures].[Przychody ze sprzedazy rzeczywiste wartosc])

I also used top 3 just to show result image here.

And here's the code:

with

/* first, add empty [Other] member to the group level */
member [Report Date].[REPORT DATE Y].[Other] as null

/* second, copy measure by fixing the lowest level */
member [Measures].[Count with Other Groups] as ([Report Date].[REPORT DATE YM],[Measures].[Count])

/* third, create top 10 by group */
set [Report Date Top 10 Groups] as
Generate([Report Date].[REPORT DATE Y].Children
,TopCount([Report Date].[REPORT DATE Y].CurrentMember
 * [Report Date].[REPORT DATE YM].Children,3,[Measures].[Count with Other Groups]))

/* this is the part for Other group mapping */
set [Report Date Other Groups] as
[Report Date].[REPORT DATE Y].[Other]
 * ([Report Date].[REPORT DATE YM].Children
    - Extract([Report Date Top 10 Groups],[Report Date].[REPORT DATE YM]))

select {[Measures].[Count],[Measures].[Count with Other Groups]} on 0
,
{
[Report Date Top 10 Groups],[Report Date Other Groups]}
on 1
from 
[DATA]

And here is the result:

..all members till the last one (which is 201606) are on the Other group.

Hope this helps, bardzo dziękuję!

Update: code was optimized by removing one multiplying in Report Date Other Groups calculation.

Update-2: (not solved yet, but in progress)

(use 'Other' member under each group)

IMPORTANT! We need additional hierarchy: Group->Client ([Report Date].[REPORT DATE] with Year->Month is my case) to be able to determine parent for each low level member.

with

/* create top 10 by group */
set [Report Date Top 10 Groups] as
Generate([Report Date].[REPORT DATE Y].Children
,TopCount([Report Date].[REPORT DATE Y].CurrentMember
 * [Report Date].[REPORT DATE].Children,3,[Measures].[Count]))

/* this is the part for Other group the lowest level non-aggregated members */
set [Report Date Other Members] as
[Report Date].[REPORT DATE Y].Children
* ([Report Date].[REPORT DATE].[Month].AllMembers
    - [Report Date].[REPORT DATE].[All])
- [Report Date Top 10 Groups]

/* add empty [Other] member to the group level, HERE IS AN ISSUE */
member [Report Date].[REPORT DATE].[All].[Other] as null

set [Report Date Other Groups] as
[Report Date].[REPORT DATE Y].[All].Children
* [Report Date].[REPORT DATE].[Other]

member [Measures].[Sum of Top] as
IIF([Report Date].[Report Date].CurrentMember is [Report Date].[REPORT DATE].[Other]
,null /* HERE SHOULD BE CALCULATION, but only
 {[Report Date].[Report Date Y].[All].[Other]}
 is shown, because 'Other' is added to the entire hierarchy */
,SUM([Report Date].[REPORT DATE Y].CurrentMember
        * ([Report Date].[Report Date].CurrentMember.Parent.Children
            - Extract([Report Date Other Members],[Report Date].[REPORT DATE]))
    ,[Measures].[Count]))

member [Measures].[Sum of Group] as
([Report Date].[Report Date].CurrentMember.Parent,[Measures].[Count])

select {[Measures].[Count],[Measures].[Sum of Group],[Measures].[Sum of Top]} on 0
,
Order(Hierarchize({[Report Date Top 10 Groups]
,[Report Date Other Groups]}),[Measures].[Count],DESC)

on 1
from 
[DATA]

And here is the intermediate result:

I need to move this result here, but have no idea how to do it.

I also tried using flat hierarchies of each level. Other member is shown correctly, but not able to calculate SUM, because both levels are independent. Maybe we can add a property like 'Group_Name' and use unlinked levels, but again - it decreases performance drastically. All this IIF([bla-bla-bla low level member].Properties("Group_Name")=[bla-bla-bla group level].Member_Name are extremely slow.

Update-3 (AdvWorks version of code above)

with

/* create top 10 by group */
set [Top 10 Groups] as
Generate([Customer].[Country].Children
,TopCount([Customer].[Country].CurrentMember
 * [Customer].[Customer Geography].Children,3,[Measures].[Internet Order Count]))

/* this is the part for Other group the lowest level non-aggregated members */
set [Other Members] as
[Customer].[Country].Children
* ([Customer].[Customer Geography].[State-Province].AllMembers
    - [Customer].[Customer Geography].[All])
- [Top 10 Groups]

/* add empty [Other] member to the group level */
member [Customer].[Customer Geography].[All].[Other] as
([Customer].[Country],[Measures].[Internet Order Count])

set [Other Groups] as
[Customer].[Country].[All].Children
* [Customer].[Customer Geography].[Other]

member [Measures].[Sum of Top] as
IIF([Customer].[Customer Geography].CurrentMember is [Customer].[Customer Geography].[Other]
,null
,SUM([Customer].[Country].CurrentMember
        * ([Customer].[Customer Geography].CurrentMember.Parent.Children
            - Extract([Other Members],[Customer].[Customer Geography]))
    ,[Measures].[Internet Order Count]))

member [Measures].[Sum of Group] as
([Customer].[Customer Geography].CurrentMember.Parent,[Measures].[Internet Order Count])

select {[Measures].[Internet Order Count],[Measures].[Sum of Group],[Measures].[Sum of Top]} on 0
,
Order(Hierarchize({[Top 10 Groups],[Other Groups]}),[Measures].[Internet Order Count],DESC) on 1
from [Adventure Works]

Update-4 (with a solution in year/month example)

Amazing solution of @whytheq helped to do what I want:

WITH 
  SET [All Grupa Klientow]  AS ([Report Date].[Report Date Y].Children) 
  SET [All Klient] AS ([Report Date].[Report Date YM].Children)
  SET [Top N Members] AS 
    Generate
    (
      [All Grupa Klientow]
     ,TopCount
      (
        (EXISTING 
          [All Klient])
       ,3
       ,[Measures].[Count]
      )
    ) 
  MEMBER [Report Date].[Report Date YM].[Other] AS 
    Aggregate({(EXISTING {[All Klient]} - [Top N Members])}) 
SELECT 
  {[Measures].[Count]} ON 0
 ,{
      [All Grupa Klientow]
    * 
      {
        [Top N Members]
       ,[Report Date].[Report Date YM].[Other]
      }
  } ON 1
FROM [DATA];

And the image:

Task is solved, but please mark not this answer, but @whytheq's!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!