Question

I have the following MDX query for a SSAS cube

select
non empty { [Measures].[Deviation Percent] * [Date].[Hierarchy].[Month].Members } on columns,
non empty { [Veh].[Registration].[All] } on rows
from
(select { [Date].[Hierarchy].[Month].&[9]&[2013] : [Date].[Hierarchy].[Month].&[1]&[2014] }   on columns from [DB])

as you can see, [Veh],[Date] are dimensions

I have another table (ScoresTable) in the datasource, not included anywhere in the cube, which I use to assign scores based on Deviation Percent measure. The table has two columns, Score and From_Perc and rows are like this:

Score ----- From_Perc

0 ----- -5

1 ------ 0

2 ------- 3

... and so forth

so when deviation is -5 to 0 the score is 1, 0 to 3 the score is 2 and when <-5 the score is 0. I need to display this score instead of target deviation on columns but I can't seem to figure out how to do this. Any suggestions?

note: The scores table may change with time so the limits can not be considered stable (i.e. I can not use Case and Iif statements with hardcoded values to do calculations). Also, the Deviation member is calculated.

UPDATE: Thanks to FrankPI's suggestions the answer to the problem was creating a calculated member like this:

(
    [Measures].[Score],
    tail(
         filter(
                [Fuel Scores Percentage].[From Perc].[From Perc],
                ([Measures].[Target Deviation] * 100)>[Fuel Scores Percentage].[From Perc].CurrentMember.Member_Value
               )
        ).item(0).item(0)
)
Was it helpful?

Solution

I would make a dimension table from the scorestable: Add a primary key column to it if it does not already have one, and calculate in SQL - based on the deviation in the fact table - the entry in the scorestable that you need to reference. Then the Score from the scorestable gets an attribute of this dimension.

This SQL logic can be done either directly in the process filling the tables, or in views based on the tables that you then reference in the Data Source View, or in the Data Source View via calculated queries/calculated columns.

If you do not want to have this part of the logic on the SQL side, it gets a bit tricky: First, as MDX can only access cube objects and no tables, we must make the table available to the cube:

  • Add the table to the data source view.
  • Create a dimension from it, using from_perc as the only attribute. You can keep the names "Scores Table" for the dimension and "From Perc" generated by BIDS for this attribute, or change them, in my below MDX I will assume the automatic names.
  • Create a measure group from the same table, which only has one measure, based on column Score: let's call it "_Score". You can leave the aggregation as "Sum", this will actually never used at aggregated levels.
  • Add the dimension to the cube dimensions, and check that it is the one and only dimension related to the measure group you added on the "Dimension Usage" tab.
  • Set the storage mode of the only partition of this measure group to "ROLAP".

Now that we have the objects, we can use the following MDX to calculate the Score measure:

with member [Measures].[Score] as
     CASE
       WHEN [Scores Table].[From Perc].CurrentMember IS [Scores Table].[From Perc].[All] THEN
         CASE
           WHEN [Measures].[Deviation Percent] > Tail([Scores Table].[From Perc].[From Perc]).Item(0).Item(0).Properties('Key0', typed) THEN
               -- found it: it is the last entry from ScoresTable:
               (
                [Measures].[_Score],
                Tail([Scores Table].[From Perc].[From Perc]).Item(0).Item(0)
               )
           ELSE
               -- start recursing the table with the last but one member:
               (
                [Measures].[Score],
                Tail([Scores Table].[From Perc].[From Perc]).Item(0).Item(0).PrevMember
               )
         END
       ELSE
         CASE
           WHEN [Measures].[Deviation Percent] > [Scores Table].[From Perc].CurrentMember.Properties('Key0', typed) THEN
               -- found the correct table entry, end of recursion, return it:
               (
                [Measures].[_Score],
                [Scores Table].[From Perc].CurrentMember
                )
           ELSE 
               -- not found, recurse to the previous table entry:
               (
                [Measures].[Score],
                [Scores Table].[From Perc].CurrentMember.PrevMember
               ) 
         END
     END

select ...

You can also use this as a cube calculated measure with the same expression. After you checked it works, I would suggest to make both the attribute and the measure invisible.

How does it work?

The main looping construct in MDX is recursion, and that is used in the definition of the measure as well. At the starting point, [Scores Table].[From Perc].CurrentMember is the All member, as this attribute hierarchy is invisible, and hence we can assume that it is not used on columns, rows, or in a slicer. Then, we check if the deviation is greater than the score of the key of the last member of the From Perc attribute hierarchy. As we did not state any specific ordering for the attribute, it is ordered by key by Analysis Services, and hence the last member is the largest one. If the value is larger than the key value, we are done, and return the score value for that. (The Tail([set]).Item(0).Item(0) construct gets the last member of a one dimensional set.)

Otherwise, we start recursing, calling the member definition again, but this time with the last but one member as the CurrentMember. Then we get to the ELSE branch of the main CASE, and do a similar check here: If the deviation is greater than the key of the current member, we finish the recursion and return the value. Otherwise, we recurse again. Analysis Services automatically stops the recursion if no more PrevMembers exist.

Please note that - while you can avoid having to re-process the measure group if the data in the table changes by setting it to ROLAP, there is no such thing for the dimension: You always have to re-process the dimension if anything in the from_perc column changes, or if records are added or deleted. However, as the table is presumably small, this should be fast. And you even could leave the measure group set to the default setting of MOLAP, and include it in the re-processing as well without prolonging the processing time dramatically.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top