Search code examples
ssasmdx

Top X of Top Y with RestOf member where X and Y are hierarchies from different dimensions


This works fine:

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]
    * 
      {
        [State-Province].[All]
       ,[Top2States]
       ,[State-Province].[All].[RestOfCountry]
      }
  } ON ROWS
FROM [Adventure Works];

The EXISTING keyword helps a lot .

If the two hierarchies ON ROWS are not from the same dimension, in the way that countries and states are in the above, then we have something like the following:

WITH 
  SET [AllCountries] AS [Country].[Country].MEMBERS 
  SET [AllCats]      AS [Product].[Category].[Category].MEMBERS 
  SET [Top5Cats] AS 
    Generate
    (
      [AllCountries]
     ,TopCount
      (
        (EXISTING 
          [AllCats])
       ,5
       ,[Measures].[Internet Order Count]
      )
    ) 
  MEMBER [Product].[Category].[All].[RestOfProds] AS 
    Aggregate({(EXISTING {[AllCats]} - [Top5Cats])}) 
SELECT 
  {[Measures].[Internet Order Count]} ON COLUMNS
 ,{
      [AllCountries]
    * 
      {
        [Product].[Category].[All]
       ,[Top5Cats]
       ,[Product].[Category].[All].[RestOfCountry]

      }
  } ON ROWS
FROM [Adventure Works];

You can see in the results of the above that the same set of categories are repeated against each country, in the same order i.e. the engine is not finding the topCount per country. EXISTING is now redundant.

How can we adapt the second script above so that it has similar functionality as the top script?


Edit

A better example is the following using Product. It is as if the engine is finding the TopCount for All countries and then putting the same set against each country. I'd like the TopCount for each country:

WITH 
  SET [AllCountries] AS 
    [Country].[Country].MEMBERS 
  SET [AllProds] AS 
    [Product].[Product].[Product].MEMBERS 
  SET [Top5Prods] AS 
    Generate
    (
      [AllCountries]
     ,TopCount
      (
        (EXISTING 
          [AllProds])
       ,5
       ,[Measures].[Internet Order Count]
      )
    ) 
  MEMBER [Product].[Product].[All].[RestOfProds] AS 
    Aggregate({(EXISTING {[AllProds]} - [Top5Prods])}) 
SELECT 
  {[Measures].[Internet Order Count]} ON COLUMNS
 ,NON EMPTY 
    {
        [AllCountries]
      * 
        {
          [Product].[Product].[All]
         ,[Top5Prods]
         ,[Product].[Product].[All].[RestOfProds]
        }
    } ON ROWS
FROM [Adventure Works];

Edit2

This is the latest version via Sourav's ideas - unfortunately the RestOfProds members are not functioning correctly:

WITH 
  SET [AllCountries] AS 
    [Country].[Country].MEMBERS 
  SET [AllProds] AS 
    [Product].[Product].[Product].MEMBERS 
  SET [Top5Prods] AS 
    Generate
    (
      [AllCountries] AS a
     ,
        {
            (
              a.Current
             ,[Product].[Product].[All]
            )
          + 
            //The top x prods
            TopCount
            (
              NonEmpty
              (
                a.Current * [AllProds]
               ,[Measures].[Internet Sales Amount]
              )
             ,5
             ,[Measures].[Internet Sales Amount]
            )
        }
    ) 
  SET [RestOfProds] AS 
    Extract
    (
      {[AllCountries] * [AllProds]} - [Top5Prods]
     ,[Product].[Product]
    ) 
  MEMBER [Product].[Product].[All].[RestOfProds] AS 
    Aggregate([RestOfProds]) 
SELECT 
  {[Measures].[Internet Sales Amount]} ON COLUMNS
 ,{
    [Top5Prods]
   ,
    [AllCountries] * [Product].[Product].[All].[RestOfProds]
  } ON ROWS
FROM [Adventure Works];

Edit3

The following has the correct order so that the member RestOfProds always follows it's respective top 5

WITH 
  SET [AllCountries] AS 
    [Country].[Country].MEMBERS 
  SET [AllProds] AS 
    [Product].[Product].[Product].MEMBERS 
  SET [Top5Prods] AS 
    Generate
    (
      [AllCountries] AS a
     ,{
        //The top x prods
        TopCount
        (
          NonEmpty
          (
            a.Current * [AllProds]
           ,[Measures].[Internet Sales Amount]
          )
         ,5
         ,[Measures].[Internet Sales Amount]
        )
      }
    ) 
  MEMBER [Product].[Product].[All].[RestOfProds] AS 
    Aggregate([Country].CurrentMember * [AllProds] - [Top5Prods]) 
SELECT 
  {[Measures].[Internet Sales Amount]} ON COLUMNS
 ,Generate
  (
    [AllCountries] AS X
   ,
      Order
      (
        Intersect
        (
          X.CurrentMember * [AllProds]
         ,[Top5Prods]
        )
       ,[Measures].[Internet Sales Amount]
       ,bdesc
      )
    + 
      {X.CurrentMember * {[Product].[Product].[All].[RestOfProds]}}
  ) ON ROWS
FROM [Adventure Works];

Edit4

The following has the correct order so that the member RestOfProds always follows it's respective top 5 + I've added a further set on rows:

WITH 
  SET [2months] AS 
    {
      [Date].[Calendar].[Month].&[2007]&[9]
     ,[Date].[Calendar].[Month].&[2007]&[10]
    } 
  SET [AllCountries] AS 
    [Country].[Country].MEMBERS 
  SET [MthsCountries] AS 
    [2months] * [AllCountries] 
  SET [AllProds] AS 
    [Product].[Product].[Product].MEMBERS 
  SET [Top5Prods] AS 
    Generate
    (
      [MthsCountries] AS A
     ,{
        //The top x prods
        TopCount
        (
          NonEmpty
          (
            A.Current * [AllProds]
           ,[Measures].[Internet Sales Amount]
          )
         ,5
         ,[Measures].[Internet Sales Amount]
        )
      }
    ) 
  MEMBER [Product].[Product].[All].[RestOfProds] AS 
    Aggregate
    (
        ([Date].[Calendar].CurrentMember,[Country].CurrentMember) * [AllProds]
      - 
        [Top5Prods]
    ) 
SELECT 
  {[Measures].[Internet Sales Amount]} ON COLUMNS
 ,Generate
  (
    [MthsCountries] AS X
   ,
      Order
      (
        Intersect
        (
          X.Current * [AllProds]
         ,[Top5Prods]
        )
       ,[Measures].[Internet Sales Amount]
       ,bdesc
      )
    + 
      {X.Current * {[Product].[Product].[All].[RestOfProds]}}
  ) ON ROWS
FROM [Adventure Works];

Solution

  • EXISTING when used on named sets, doesn't really make a difference since named sets are created before the axes members are laid out. In your case, GENERATE is really just creating static sets and then on the axes everything is just cross joined.

    To get the TopCount script working, you need to handle all the cross joins inside one set so that everything is evaluated together. I am not sure, but you might try the below:

    WITH 
      SET [AllCountries] AS [Country].[Country].MEMBERS 
      SET [AllCats]    AS [Product].[Category].[Category].MEMBERS 
      SET [Top5Cats] AS 
        Generate
        (
          [AllCountries] as a
         ,
         {
         (a.current , [Product].[Category].[All] ) //The ALL member
         +   
         TopCount  //The top 2 categories
          (
            NonEmpty((a.current * [AllCats] ) , (a.CURRENT, [Measures].[Internet Order Count]))
           ,2
           ,[Measures].[Internet Order Count]      
          ) 
          }
        + 
        { //The rest of the members
        a.current * [AllCats] 
        - 
        a.current *{     
         TopCount
          (
            NonEmpty([AllCats] , (a.CURRENT, [Measures].[Internet Order Count]))
           ,2
           ,[Measures].[Internet Order Count]      
          ) 
          }
          }
        )
    
      MEMBER [Product].[Category].[All].[RestOfProds] AS 
        Aggregate({(EXISTING {[AllCats]} - [Top5Cats])}) 
    
    SELECT 
      {[Measures].[Internet Order Count]} ON COLUMNS,
    [Top5Cats] ON ROWS
    FROM [Adventure Works];
    

    EDIT: If you need the RestOfCats member, you can add this code.

    SET [RestOfCats] AS
    EXTRACT
        (
         {
         [AllCountries] * [AllCats] 
         - 
         [Top5Cats]
         },
         [Product].[Category]
        )
    
    MEMBER [Product].[Category].[All].[RestOfCats] AS
    Aggregate([RestOfCats])
    

    EDIT 2

    Continuing on your example, removing the 'All' member additionally in the definition.

      SET [RestOfProds] AS 
        Extract
        (
          {[AllCountries] * [AllProds]} - [Top5Prods] - [AllCountries]*[Product].[Product].[All]
         ,[Product].[Product]
        )