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

前端 未结 2 1419
不知归路
不知归路 2021-01-24 08:01

I\'ve got requirement to display top 5 customer sales by customer group, but with other customers sales within the group aggregated as \'Others\'. Something similar to this qu

相关标签:
2条回答
  • 2021-01-24 08:35

    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];
    
    0 讨论(0)
  • 2021-01-24 08:36

    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:

    TopCount_withGroups

    ..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:

    TopN_v2

    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]
    

    TopCount_withGroups_AdvWorks

    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:

    TopCount_withGroups_FinalResult

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

    0 讨论(0)
提交回复
热议问题