www.odtug.com
Crisci
member formulas to calculate the scaled values. End users simply select the scale member in their page heading and any values returned from any dimension combination will be scaled properly. Refer to figure 1.3.
Creating an analytic dimension Creating the analytic dimension is a simple process. Figure 1.1 shows an example of aggregate storage outline without any analytic dimensions. Figure 1.2 shows the same outline with a [Scale] dimension added to it
Figure 1.1
Figure 1.2
Take note the analytic dimension, in this case, the [Scale] dimension, is a Dynamic hierarchy, which allows the developer to add member formulas and set the members to non-consolidating (~). For these members, the MDX formulas are simple.
[in_Thousands] = [Scale].[whole_units] / 1000 [in_Millions] = [Scale].[whole_units] / 1000000
In cases where you want to limit the scaling to certain me mbers, you can tag the members in your outline with a UDA and modify the formula to this
CASE WHEN IsUDA([Measures].CurrentMember, "NO_SCALE") THEN [Scale].[whole_units] ELSE [Scale].[whole_units] / 1000 END
www.odtug.com
Crisci
Figure 1.3
Time Functionality Another use for analytic dimensions revolves around the need to perform time -based calculations within the data model. Period to date calculations are required in most models with a time dimension. In block storage databases , this was achieved with dynamic time series. At this time, ASO models do not provide native period to date functionality. Another time-based issue faced in OLAP models is how to handle members that do not aggregate over time. Figure 1.4 shows how [Opening Inventory] for Qtr 1 is an erroneous number. The correct value should be 28,929, the last known inventory balance for the quarter.
Figure 1.4
In BSO models, this was handled using the Time balancing feature. In ASO models, time balancing is first supported in v9.3.1.
PERIOD TO DATE One approach to creating period to date functionality is to use alternate rollups. Figure 1.5 demonstrates an example of this method.
www.odtug.com
Crisci
Figure 1.5
A similar approach might involve using an MDX formula for a member
[Mar YTD] = [Jan] + [Feb] + [Mar]
While either of these methods works, it again involves the need to create many extra members with either formulas or shared member associations. In this case, we were only looking at a time dimension with twelve months resulting in twelve Year to Date members. If the users also wanted Quarter to Date members the developer would have to create another twelve members. Now lets assume our model goes down to the daily level and the end users wanted Year to Date, Quarter to Date, and Month to Date functionality. You would literally need hundreds of members to drive each calculation and finding those members would be a burdensome experience for the end users. Clearly, in this example, an Analytic dimension is a mo re feasible solution. Similar to the Scale dimension, the developer would add a new [View] dimension to the model as depicted in figure 1.6.
Figure 1.6
www.odtug.com
Crisci
The new [View] dimension has four members. The first member is the default-stored member [Per] (alias: Periodic). The calculated members are [TB] (alias: Time Balance), [QTD] (alias: Quarter to Date), and [YTD] (alias: Year to Date). The MDX formulas for QTD and YTD are fairly simple.
[QTD] = SUM( PeriodsToDate( [Year].Generations(2), [Year].CurrentMember ), [View].[Per] ) [YTD] = SUM( PeriodsToDate( [Year].Generations(1), [Year].CurrentMember ), [View].[Per] )
Figure 1.7
The calculation for Time Balancing is a bit more complicated and you will see once we add Time Balancing to the model; it also impacts our [QTD] and [YTD] formulas. TIME BALANCING As demonstrated in figure 1.4, there are cases when you do not want members to add over time. The solution to this problem is called time balancing. When there are time balancing members in an analytic model, you have to consider the results you desire. Primarily you need to think about how you want to handle missing or zero values. You also need to decide if you want to time balance based on the first or earliest member or on the last or latest member that you have values. Another option is to average over time. For purposes of this paper we will not go into all the possible variations, rather we will focus on the most common requests. Most often when utilizing time balancing, it is to do what is known as time balance last. Referring back to figure 1.4, time balance last would result in Qtr1 value as 28,929 or the last value for the quarter. To calculate this value is not too difficult. The MDX would like this
[TB] = CASE WHEN IsUDA([Measures].CurrentMember, "TB_Last") THEN IIF(IsLeaf([Year].CurrentMember), [View].[Per], (ClosingPeriod ([Year].Levels(0), [Year].CurrentMember), [View].[Per]) ELSE [View].[Per] END
www.odtug.com
Crisci
Figure 1.8
You can see the value for Qtr1 is now 28,929. Where it gets more complicated is when you have to deal with time periods that do not have any data. Figure 1.9 shows there is no data for Sep therefore Qtr3 is null. This might be acceptable based on end user requirements, but in some cases, users will require that Sep, and Qtr3, reflect the Aug (last known) value.
Figure 1.9
In order to work around this, you have to write your code to cycle through the time dimension and search for the last me mber with a non-missing value. There are a number of pitfalls you have to code for when doing this. If you search across the time dimension and get to the beginning of the dimension without encountering a value, you will get an error. To avoid these errors, you have to add conditional testing to your code to handle these situations. Ultimately, the finalized code looks like this :
[TB] = CASE WHEN IsUDA([Measures].CurrentMember, "TB_Last") THEN IIF( IsLeaf([Year].CurrentMember) AND Not IsEmpty([Year].CurrentMember), [View].[Per], IIF(IsLeaf([Year].CurrentMember), IIF (NonEmptyCount (MemberRange(Head( [Year].levels(0).members ).item(0).item(0), [Year].CurrentMember, LEVEL), [View].[Per])> 0, ([View].[Per], Tail (Filter (MemberRange(Head( [Year].levels(0).members ).item(0).item(0), [Year].CurrentMember, LEVEL), Not IsEmpty ([View].[Per]))).Item(0).Item(0)), MISSING), IIF (NonEmptyCount (DESCENDANTS([Year].CurrentMember,10,LEAVES), [View].[Per]) > 0, ([View].[Per], Tail (Filter (DESCENDANTS([Year].CurrentMember,10,LEAVES), Not IsEmpty ([View].[Per]))).Item(0).Item(0)), IIF (NonEmptyCount ([Year].levels(0).members, [View].[Per]) > 0, ([View].[Per], Tail (Filter ([Year].levels(0).members, Not IsEmpty ([View].[Per]))).Item(0).Item(0)),MISSING)))) ELSE [View].[Per] END
www.odtug.com
Crisci
Figure 1.10
Now that we have the time balance calculation working, we need to go back and update our QTD and YTD formulas to handle accounts that are tagged as time balance.
[QTD] CASE WHEN IsUDA([Measures].CurrentMember, "TB_Last") THEN IIF( IsLeaf([Year].CurrentMember) AND Not IsEmpty([Year].CurrentMember), [View].[Per], IIF(IsLeaf([Year].CurrentMember), IIF (NonEmptyCount (MemberRange(Head( [Year].levels(0).members ).item(0).item(0), [Year].CurrentMember, LEVEL), [View].[Per])> 0, ([View].[Per], Tail (Filter (MemberRange(Head( [Year].levels(0).members ).item(0).item(0), [Year].CurrentMember, LEVEL), Not IsEmpty ([View].[Per]))).Item(0).Item(0)), MISSING), IIF (NonEmptyCount (DESCENDANTS([Year].CurrentMember,10,LEAVES), [View].[Per]) > 0, ([View].[Per], Tail (Filter (DESCENDANTS([Year].CurrentMember,10,LEAVES), Not IsEmpty ([View].[Per]))).Item(0).Item(0)), IIF (NonEmptyCount ([Year].levels(0).members, [View].[Per]) > 0, ([View].[Per], Tail (Filter ([Year].levels(0).members, Not IsEmpty ([View].[Per]))).Item(0).Item(0)),MISSING)))) ELSE SUM(PeriodsToDate([Year].Generations(2), [Year].CurrentMember), [View].[Per] ) END [YTD] CASE WHEN IsUDA([Measures].CurrentMember, "TB_Last") THEN IIF( IsLeaf([Year].CurrentMember) AND Not IsEmpty([Year].CurrentMember), [View].[Per], IIF(IsLeaf([Year].CurrentMember), IIF (NonEmptyCount (MemberRange(Head( [Year].levels(0).members ).item(0).item(0), [Year].CurrentMember, LEVEL), [View].[Per])> 0, ([View].[Per], Tail (Filter (MemberRange(Head( [Year].levels(0).members ).item(0).item(0), [Year].CurrentMember, LEVEL), Not IsEmpty ([View].[Per]))).Item(0).Item(0)), MISSING), IIF (NonEmptyCount (DESCENDANTS([Year].CurrentMember,10,LEAVES), [View].[Per]) > 0, ([View].[Per], Tail (Filter (DESCENDANTS([Year].CurrentMember,10,LEAVES), Not IsEmpty ([View].[Per]))).Item(0).Item(0)), IIF (NonEmptyCount ([Year].levels(0).members, [View].[Per]) > 0, ([View].[Per], Tail (Filter ([Year].levels(0).members, Not IsEmpty ([View].[Per]))).Item(0).Item(0)),MISSING)))) ELSE SUM(PeriodsToDate([Year].Generations(1), [Year].CurrentMember), [View].[Per] ) END
www.odtug.com
Crisci
Figure 1.11 shows the full retrieve with correct values. Take note however that Periodic values for Opening Inventory will always be inaccurate because Periodic is the stored value. Users need to be properly trained on how to pull the correct values.
Figure 1.11
Statistical accounts Another useful way to utilize analytic dimensions is to create built in statistical accounts to your model. Figure 1.12 shows what an analytic dimension with statistical calculations might look like.
Figure 1.12
www.odtug.com
Crisci
(The formulas for Market would be the same, just substitute Product with Market.)
Figure 1.13
Looking at the calculation results, an analyst could quickly determine that for the East region total sales was $58,921. The company currently sells 15 different products in the East region, the average sale for each product is 4,290, the maximum sales for a product in the East was 15,888, and the minimum was 839. Figure 1.14 demonstrates the percentage of the total sales each product made up in the East region.
Figure 1.14
www.odtug.com 9 ODTUG Kaleidoscope 2008
Crisci
METRICS The last analytic dimension we will discuss looks at how metrics and key performance indicators (KPIs) are calculated. Almost all analytic models have calculated members beyond the basic roll up that consolidates the outline based on hierarchical relationships. In a financial application these might be members like EBIT, EBIT % , Margin %, etc. In a human resources application you might have full time equivalent or utilization calculations. Using the finance model as an example , a calculation like Margin % is simple to implement. You would simply create the member [Margin %] as a member of the Measures dimension with the formula
[Margin %] = [Margin] / [Sales]
This is perfectly acceptable and the common practice among most developers. The problem with this approach is if you create [Margin %] chances are, someone is going to ask you to give them [EBIT %]. Then someone will probably ask for [COGS %] and while were at it, we should probably create [G&A %] and [Selling %] and [SG&A %] and if we are going to create those, then we better create [Travel %], [Entertainment %], [T&E %], etc, etc, etc. You can see how this can quickly turn into a lot of work and many different calculations with different formulas. If you have a chart of accounts with thousands of members, users can come up with many requests , and those are for the individuals that ask. Many will not find the metric they are looking for and instead they will calculate it themselves in their spreadsheets. A better design approach might be to create a single member and write one formu la to calculate every member in the chart of accounts as a percentage of Sales. Figure 1.15 demonstrates how we can do this with an analytic dimension. Figure 1.16 shows the results.
Figure 1.15
Figure 1.16
www.odtug.com 10 ODTUG Kaleidoscope 2008
Crisci
The example demonstrates that with a single member we were able to provide end users with many reportable metrics. This order of magnitude demonstrates why analytic dimensions are a powerful design technique for developers to leverage.
Things to consider There are a number of things to consider when adding analytic dimensions to your data model. The first is that they can be slightly complicated and less intuitive to end users, especially those with limited multi-dimensional experience. Novice or casual users will generally not utilize analytic dimensions beyond scaling or perhaps time functionality. Statistical and metrics are something a power user would generally utilize. Another very important thing to consider when using analytic dimensions is solve order. Solve order is something we did not get into, but it is crucial when implementing analytic dimensions to ensure calculation accuracy. Solve order, when tuned properly, will also yield much better performance from an MDX calculation. The general rule with solve order is to test, test, test. MDX formulas can range from the simple, such as scaling a value, to extremely complex, as demonstrated with time balancing. It is a robust language, which when used properly can leverage advanced analytic results. When you use MDX with analytic dimensions you can increase the scope of your calculations and build more efficient models. This paper has introduced analytic dimensions as a design technique to augment analytic models . I recommend experimenting and testing with new and existing models to see how you can leverage this methodology in your environment.
www.odtug.com
11