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 PrevMember
s 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.