THE SQL Server Blog Spot on the Web

Welcome to SQLblog.com - The SQL Server blog spot on the web Sign in | Join | Help
in Search

Microsoft OLAP by Mosha Pasumansky

Ratio to “Parent on Rows” in MDX

Every now and then the issue of computing a generic “ratio to parent” calculation comes up. There is a good overview of the problem in Darren Gosbell’s blog “MDX ratio of current parent issue”. I want to pick up the conversation where Darren left it:

It is technically possible to get "kind of" close doing something like the following using the Axis() function: <MDX fragment skipped> which sort of gives us a  "Percent of Row parent" calculation and this is probably the best you can do, but if you crossjoin multiple hierarchies on the row axis we are in trouble again.

I am not big fan of calculation which depends on the content of axis, mostly because when result of the same expression at the same coordinates differ from query to query (and this happens when expression uses Axis function), the whole caching is destroyed, and as a result performance suffers. However, if there is a real business requirement to build calculated member like that – it is certainly possible, and in most generic way, which will work no matter how many hierarchies are in the row axis. What’s more interesting, it is even possible to do it in pure MDX, and this MSDN forum thread provides different approaches. However, none of these approaches is practical one. The practical solution is to use the following stored procedure:

public decimal RatioToParent(Set axis, Expression exp)
{
    Hierarchy h = null;

    // Iterate over all hierarchies in the set
    int cHier = axis.Hierarchies.Count;
    int iHier;
    for (iHier = cHier-1; iHier >= 0; iHier--)
    {
        h = axis.Hierarchies[iHier];
        // and find the hierarchy where the current member is not yet at the highest possible level
        if (h.CurrentMember.ParentLevel.LevelNumber > 0)
            break;
    }

    // If there were no such hierarchy found - report ratio of 100%
    if (h == null || iHier < 0)
        return 1;

    // Since current member in this hierarchy is not yet at the highest level, we can safely call .Parent
    TupleBuilder tb = new TupleBuilder(h.CurrentMember.Parent);
    // and divide value at current cell by the value of its parent
    return (decimal)exp.Calculate(null) / (decimal)exp.Calculate(tb.ToTuple());
}

The way to use this stored procedure in the definition of calculated member is as in the following query – we have three different hierarchies on rows, and all the ratios come back correctly

WITH 
  MEMBER Measures.SalesRatioToParent AS 
    IIF
    (
      IsEmpty([Measures].[Sales Amount])
     ,null
     ,ASSP.ASStoredProcs.SetOperations.RatioToParent
      (
        Axis(1).Item(0)
       ,[Measures].[Sales Amount]
      )
    )
   ,FORMAT_STRING = 'Percent'
   ,NON_EMPTY_BEHAVIOR = [Measures].[Sales Amount]
SELECT 
  {
    [Measures].[Sales Amount]
   ,[Measures].[SalesRatioToParent]
  } ON 0
 ,NON EMPTY 
    (
      [Promotion].[Promotion Category].MEMBERS
     ,[Product].[Category].MEMBERS
     ,[Sales Territory].[Sales Territory].MEMBERS
    ) ON 1
FROM [Adventure Works];

Note the careful combination of IsEmpty([Sales Amount], NULL, …) construct and NON_EMPTY_BEHAVIOR. The condition of IIF ensures that NON_EMPTY_BEHAVOIR is set correctly in this scenario

I added RatioToParent function to my local copy of Analysis Services Stored Procedures project, I hope that it will get approved and become part of the official ASSP release.


Published Thursday, September 11, 2008 11:15 PM by mosha
Filed under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

amosquera said:

Hi Mosha,

I've tested this approach and I've find that in querys like this one ( where the crossjoin is made only with levels of a hierarchy) the RatioToParent works incorrect.

Sample Query:

WITH

 MEMBER Measures.SalesRatioToParent AS

   IIF

   (

     IsEmpty([Measures].[Sales Amount])

    ,null

    ,ASSP.ASStoredProcs.SetOperations.Ratiotoparent

     (

       Axis(1).Item(0)

      ,[Measures].[Sales Amount]

     )

   )

  ,FORMAT_STRING = 'Percent'

  ,NON_EMPTY_BEHAVIOR =

   [Measures].[Sales Amount]

SELECT

 {

   [Measures].[Sales Amount]

  ,[Measures].[SalesRatioToParent]

 } ON 0

,NON EMPTY

   (

    [Product].[Category].MEMBERS

    ,[Product].[subCategory].MEMBERS

   ) ON 1

FROM [Adventure Works];

September 25, 2008 5:03 AM
 

mosha said:

Amosquera - I ran your query, and the results appear to be correct to me. Can you please tell me which row you think you get wrong result, and what is the result you expected to see in that row. Thanks.

September 25, 2008 10:47 AM
 

amosquera said:

Hi Mosha,

I've the descriptions in spanish

for instance the rows under "Prenda" are:

Prenda Todos los Productos 2117613.45 1.93%

Prenda Culote corto         166739.71 0.15%

Prenda Gorra             51229.45 0.05%

Prenda Guantes            242795.87 0.22%

Prenda Jersey            752259.39 0.69%

Prenda Pantalones cortos   413522.53 0.38%

Prenda Calcetines    29745.13 0.03%

Prenda Mallas            201833.01 0.18%

Prenda Camiseta            259488.37 0.24%

and I expect :

Prenda Todos los Productos 2117613.45    1.93%

Prenda Culote corto         166739.71    7.87%=(166739/2117613)*100

Prenda Gorra             51229.45    2.42%

Prenda Guantes            242795.87   11.47%

Prenda Jersey            752259.39   35.52%

Prenda Pantalones cortos   413522.53   19.53%

Prenda Calcetines    29745.13    1.40%

Prenda Mallas            201833.01   19.53%

Prenda Camiseta            259488.37   12.25%

All results are here:

Suma de Venta SalesRatioToParent

Todos los Productos Todos los Productos $109,809,274.20 100.00%

Todos los Productos Culote corto $166,739.71 0.15%

Todos los Productos Portabicicletas $237,096.16 0.22%

Todos los Productos Soporte para bicicletas $39,591.00 0.04%

Todos los Productos Portabotellas y botella $64,274.79 0.06%

Todos los Productos Eje de pedalier $51,826.37 0.05%

Todos los Productos Frenos $66,018.71 0.06%

Todos los Productos Gorra $51,229.45 0.05%

Todos los Productos Cadena $9,377.71 0.01%

Todos los Productos Limpiador $18,406.97 0.02%

Todos los Productos Bielas $203,942.62 0.19%

Todos los Productos Desviador $70,209.50 0.06%

Todos los Productos Guardabarros $46,619.58 0.04%

Todos los Productos Horquilla $77,931.69 0.07%

Todos los Productos Guantes $242,795.87 0.22%

Todos los Productos Barra $170,591.32 0.16%

Todos los Productos Dirección $60,942.20 0.06%

Todos los Productos Casco $484,048.53 0.44%

Todos los Productos Sistema de hidratación $105,826.42 0.10%

Todos los Productos Jersey $752,259.39 0.69%

Todos los Productos Candado $16,225.22 0.01%

Todos los Productos Bicicleta de montaña $36,445,443.94 33.19%

Todos los Productos Cuadro de montaña $4,713,672.15 4.29%

Todos los Productos Pedal $147,483.91 0.13%

Todos los Productos Bomba $13,514.69 0.01%

Todos los Productos Bicicleta de carretera $43,878,791.00 39.96%

Todos los Productos Cuadro de carretera $3,849,853.34 3.51%

Todos los Productos Sillín $55,829.39 0.05%

Todos los Productos Pantalones cortos $413,522.53 0.38%

Todos los Productos Calcetines $29,745.13 0.03%

Todos los Productos Mallas $201,833.01 0.18%

Todos los Productos Cubierta y cámara $246,454.53 0.22%

Todos los Productos Bicicleta de paseo $14,296,291.27 13.02%

Todos los Productos Cuadro de paseo $1,642,327.69 1.50%

Todos los Productos Camiseta $259,488.37 0.24%

Todos los Productos Rueda $679,070.07 0.62%

Accesorio Todos los Productos $1,272,057.89 1.16%

Accesorio Portabicicletas $237,096.16 0.22%

Accesorio Soporte para bicicletas $39,591.00 0.04%

Accesorio Portabotellas y botella $64,274.79 0.06%

Accesorio Limpiador $18,406.97 0.02%

Accesorio Guardabarros $46,619.58 0.04%

Accesorio Casco $484,048.53 0.44%

Accesorio Sistema de hidratación $105,826.42 0.10%

Accesorio Candado $16,225.22 0.01%

Accesorio Bomba $13,514.69 0.01%

Accesorio Cubierta y cámara $246,454.53 0.22%

Bicicleta Todos los Productos $94,620,526.21 86.17%

Bicicleta Bicicleta de montaña $36,445,443.94 33.19%

Bicicleta Bicicleta de carretera $43,878,791.00 39.96%

Bicicleta Bicicleta de paseo $14,296,291.27 13.02%

Prenda Todos los Productos $2,117,613.45 1.93%

Prenda Culote corto $166,739.71 0.15%

Prenda Gorra $51,229.45 0.05%

Prenda Guantes $242,795.87 0.22%

Prenda Jersey $752,259.39 0.69%

Prenda Pantalones cortos $413,522.53 0.38%

Prenda Calcetines $29,745.13 0.03%

Prenda Mallas $201,833.01 0.18%

Prenda Camiseta $259,488.37 0.24%

Componente Todos los Productos $11,799,076.66 10.75%

Componente Eje de pedalier $51,826.37 0.05%

Componente Frenos $66,018.71 0.06%

Componente Cadena $9,377.71 0.01%

Componente Bielas $203,942.62 0.19%

Componente Desviador $70,209.50 0.06%

Componente Horquilla $77,931.69 0.07%

Componente Barra $170,591.32 0.16%

Componente Dirección $60,942.20 0.06%

Componente Cuadro de montaña $4,713,672.15 4.29%

Componente Pedal $147,483.91 0.13%

Componente Cuadro de carretera $3,849,853.34 3.51%

Componente Sillín $55,829.39 0.05%

Componente Cuadro de paseo $1,642,327.69 1.50%

Componente Rueda $679,070.07 0.62%

September 26, 2008 3:10 AM
 

amosquera said:

Hi Mosha,

I'm asking myself if you don´t read my last answer.

Am I doing anything wrong with the mdx query?.

October 15, 2008 6:38 AM
 

Robert Harmon said:

Hi Mosha,

I think that is solution is great and I certainly plan on implementing this into our solution.  However, I find it odd that this type of functionality is native to browsing a cube in Visual Studio but, is lacking or is lacking to my findings in a few OLAP display clients, specifically Dundas OLAP Grid.

Any thought?

Robert

October 16, 2008 2:26 PM
 

Robert Harmon said:

I can successfully run this stored procedure in the same format as you have layed out above for our dev cube.  My desire now is to put this stored procedure to use as a calculated member inside the cube browser and pass into it the dimensions the user is showing in the cube browser.

It seems that the calculated member written as

WITH

 MEMBER Measures.SalesRatioToParent AS

   IIF

   (

     IsEmpty([Measures].[Sales Amount])

    ,null

    ,ASSP.ASStoredProcs.SetOperations.RatioToParent

     (

       Axis(1).Item(0)

      ,[Measures].[Sales Amount]

     )

   )

  ,FORMAT_STRING = 'Percent'

  ,NON_EMPTY_BEHAVIOR = [Measures].[Sales Amount]

without the bottom select will not produce any results because the Axis(1).Item(0) is passed in as no hierarchy value.  How can I pass in the current set of dimensions being presented?

Robert

October 24, 2008 3:20 PM
 

Robert Harmon said:

I was able to get ASSP.ASStoredProcs.SetOperations.RatioToParent to return the hierarchy/dimension information when run inside the Visual Studio Cube Designer as a calculated measure.  If one changes the Axis(1).Item(0) to Axis(0).Item(0) when creating the calculated measure it returns to dimensions being analzyed on that correct axis.

Robert

October 31, 2008 8:23 AM
 

Greg Gu said:

Hi Mosha, I have tried you stored procedure, It works in most cases. except in the following case, it fails and says "Execution of the managed stored procedure RatioToParent failed with the following error: Microsoft::AnalysisServices::AdomdServer::AdomdException.

Query (27, 4) The definition of the Listda40e799 set contains a circular reference."

The MDX I used as follow which is generated in sharepoint2010:

WITH

 MEMBER Measures.SalesRatioToParent AS

   IIF

   (

     IsEmpty([Measures].[Sales Amount])

    ,null

    ,ASSP.ASStoredProcs.SetOperations.RatioToParent

     (

       Axis(1).Item(0)

      ,[Measures].[Fact Machine Count]

     )

   )

  ,FORMAT_STRING = 'Percent'

  ,NON_EMPTY_BEHAVIOR = [Measures].[Fact Machine Count]

SET [Listda40e799] AS

ORDER(

{

 FILTER(DESCENDANTS( [Dim Locale].[Locale Name].[All], [Dim Locale].[Locale Name].[Locale Name] ) ,  

 NOT ISEMPTY([Measures].[SalesRatioToParent]))

},

( [Measures].[SalesRatioToParent] ),

 BDESC )

  select SalesRatioToParent on 0,

  [Listda40e799] on 1 from dww7

Thanks,

Greg

November 14, 2009 1:00 PM

Leave a Comment

(required) 
(optional)
(required) 
Submit
Powered by Community Server (Commercial Edition), by Telligent Systems
  Privacy Statement