Mondrian: How to turn a continuous value into N bands - mdx

So I have a value that represents the length in days on a fact table. I'd like to create queries that break that value out into N number of bands (say 4 bands) by year. Is there a way to do that with CalculatedMembers? For example, I'd like to have bands for: < 1 year, 1-3 years, 3-5 years, 5+ years. I could do it with days like:
0 - 365
365 - 1095
1096 - 1825
1826 - infinity
Any idea how to do this? I'm using Mondrian. I'd like to calculate it on the fly rather than adding a field and changing ETL scripts, etc.
I have a measure defined that represents the average length in days (displayed as 2.4 years) using the aggregation function. But really I want to define a completely new measure that is a calculated measure on that same column where a function returns which band it belongs in as above, then rolls up how many were in each band.
I'm beginning to suspect I have to do this in ETL and create a new column that places them in a band. This is really a new dimension I suspect (not so much a calculated measure).

I understand from your question that you want to have the bands as categories, which would mean you need them as members of some hierarchy. You can dynamically create members in MDX, but only for existing hierarchies. You did not state one, hence I just assume you have a hierarchy of events for which you want to seed cluster the duration, furthermore I assume the hierarchy containing the dimension key is named [Event].[Event Id]. You also dis not state the name of the "average duration" measure. hence I just assume it is called Duration.
Then you could use the following MDX:
WITH Member [Event].[Event Id].[< 1 year] AS
Aggregate(Filter([Event].[Event Id].[Event Id].Members,
[Measures].[Duration] < 1.0
)
)
Member [Event].[Event Id].[1-3 years] AS
Aggregate(Filter([Event].[Event Id].[Event Id].Members,
[Measures].[Duration] >= 1.0 AND [Measures].[Duration] < 3.0
)
)
Member [Event].[Event Id].[3-5 years] AS
Aggregate(Filter([Event].[Event Id].[Event Id].Members,
[Measures].[Duration] >= 3.0 AND [Measures].[Duration] < 5.0
)
)
Member [Event].[Event Id].[5+ years] AS
Aggregate(Filter([Event].[Event Id].[Event Id].Members,
[Measures].[Duration] >= 5.0
)
)
SELECT ... // whatever you want to see
ON COLUMNS,
{
[Event].[Event Id].[< 1 year],
[Event].[Event Id].[1-3 years],
[Event].[Event Id].[3-5 years],
[Event].[Event Id].[5+ years]
}
ON ROWS
FROM [YourCube]
Add WHERE conditions, other hierarchies to the rows or columns, subselects as you like.
I am not sure if this is really fast, but it solves the question.

Related

Distinctcount - suppliers for departments over a period of time - slow performance

In a model that contains the following dimensions:
- Time - granularity month - 5 years - 20 quarters - 60 months
- Suppliers- 6000 suppliers at lowest level
- departments - 500 departments on lowest level
I need to have the distinct count of the suppliers for each department.
I use the function:
with member [measures].[#suppliers] as
distinctcount(([Supplier].[Supplier].[supplier].members
,[Measures].[amount]))
)
select [Measures].[#suppliers] on 0
, order([Department].[Department].[department].members, [#suppliers], BDESC) on 1
from [cube]
where [Time].[Time].[2017 10]:[Time].[Time].[2018 01]
The time component may vary, as the dashboard user is free to choose a reporting period.
But the MDX is very slow. It takes about 38ms to calculate the measure for each row. I want to use this measure to rank the departments and to calculate a cumulative % and assign scores to these values. As you can imagine performance will not improve.
I have tried to use functions and cache the result, but results - for me - got worse (according to the log 2x as bad).
What can I do to improve the performance?
To go fast adding a measure that calculates de Distinct Count on the Supplier ID of the table associated to[Measures].[Amount] will help. In the Schema definition.
The other ones are not scalable as Supplier is growing.
Nonetheless, why did you use DistinctCount instead of Count(NonEmpty())) ?
DistinctCount is mainly for calculating the number of members/tuples that are different in a set. It only makes sense if it's possible to have two same members in a set. As our initial members have no duplicated, it's useless.
Count(NonEmpty()) filters the set whith the nonempty and counts the number of items in the set. This can be easily calculated in parallel

Order hierarchy members by natural order

I have a SSAS cube with the following dimensions:
Time
DayName <- Hierarchy
Item
Description <- Hierarchy
In the Measures dimension I have a measure for the LostAmount.
I want to query the cube so that I can get the sum of lost amount per week day. The following query works:
select
except(Time.DayName.members, {Time.DayName.[All]}) on 0,
except(Item.Description.members, {Item.Description.All}) on 1
from Cube
But when returning the results the order of columns is not the natural order of week days (i.e. the order of columns is Friday, Monday, Sunday etc).
How can I have the result columns in the natural order of week days i.e. Monday, Tuesday etc.?
If you look at your screenshot, the days in the Time.DayName level are being sorted in natural order: it's the alphabetical order of the member names.
There's no way that SSAS can "know" what the order you want is. I'd suggest adding a Key column to this dimension, numbering the days in the order you want, and changing the dimension attribute's OrderBy property to Key rather than Name. (Perhaps you already do have a key: but OrderBy=Name will naturally order the members by their name).
Addition: There is a dimension attribute Type property in SSAS, which can be set to "Day of Week" - but I've never used it, and don't know whether it would correctly detect that your member names are the names of weekdays. Or whether it will order your week starting on the "right" day that you want to start your week from. You could try setting it and seeing what happens.
While it's definitely better to change the cube structure, sometimes it's not a feasible option. In such cases, it has to be handled inside MDX code(which is slower). Below is one way.
WITH
MEMBER measures.weekdaynumber AS
CASE Time.DayName.CURRENTMEMBER
WHEN "Monday" THEN 1
WHEN "Tuesday" THEN 2
WHEN "Wednesday" THEN 3
WHEN "Thursday" THEN 4
WHEN "Friday" THEN 5
WHEN "Saturday" THEN 6
WHEN "Sunday" THEN 7
END
SELECT
Order
(
Except
(
Time.DayName.MEMBERS
,{Time.DayName.[All]}
)
,Measures.weekdaynumber
) ON 0
,Except
(
[Item].[Description].MEMBERS
,{[Item].[Description].[All]}
) ON 1
FROM [Cube];
I have found a workaround for the ordering issue by defining additional members on the Measures dimension and outputting them in the desired order:
with
member Measures.Monday as
sum(Time.DayName.&[Monday], Measures.LostValue)
member Measures.Tuesday as
sum(Time.DayName.&[Tuesday], Measures.LostValue)
member Measures.Wednesday as
sum(Time.DayName.&[Wednesday], Measures.LostValue)
member Measures.Thursday as
sum(Time.DayName.&[Thursday], Measures.LostValue)
member Measures.Friday as
sum(Time.DayName.&[Friday], Measures.LostValue)
member Measures.Saturday as
sum(Time.DayName.&[Saturday], Measures.LostValue)
member Measures.Sunday as
sum(Time.DayName.&[Sunday], Measures.LostValue)
select
{
Measures.Monday,
Measures.Tuesday,
Measures.Wednesday,
Measures.Thursday,
Measures.Friday,
Measures.Saturday,
Measures.Sunday
} on 0,
except(Item.Description.members, {Item.Description.[All]}) on 1
from [Cube]

Count maximum sequel of null values - mdx query

I want to create a member based on this problem
I have a Product A being sold
I want to find the largest range of consecutive days without sale
example:
days 1,2,3 the product not sale, after that,it sold for 15 consecutive days, at 19th day it didnt sell for 2 days and after that it sold every day until the end of the month
so my maximum days without sale was 3
The following query delivers in the Microsoft sample cube Adventure Works what you want:
WITH Member Measures.[days without sales] AS
IIf( [Measures].[Internet Sales Amount] > 0
, 0
,(Measures.[days without sales], [Date].[Calendar].CurrentMember.PrevMember) + 1
)
Member Measures.[Max days without sales] AS
Max( [Date].[Calendar].[Date].Members
,Measures.[days without sales]
)
SELECT { [Measures].[Max days without sales] }
ON COLUMNS
FROM [Adventure Works]
WHERE [Product].[Product].&[486]
The measure days without sales is defined recursively, and returns how many days up to and including the current member of the [Date].[Calendar] hierarchy there was no sales. You may need to adapt the criteria for "without sale", bearing in mind that in MDX, numerical comparisons treat NULL as 0 - which is different from SQL.
This measure only works correctly if there is a member in this hierarchy for each day, i. e. there are no gaps in this hierarchy. And actually, the definition is more general than just working for days: If you use months for the [Date].[Calendar].CurrentMember, it would give you the number of months without sales, etc. It works with each level of the hierarchy.
The measure Max days without sales does not contain the product in its definition, it delivers the maximum days for whatever is in context (in this case the product in the WHERE clause).
Please note that - as actually there is a loop over all days in the [Date].[Calendar] hierarchy when calculating Measures.[Max days without sales], and within that the recursion again iterates along the previous days, and all this for each cell in the result set - this may be slow for large reports.

Filtering a Dimension Relative to a CurrentMember in MDX

I'm having a bit of trouble accomplishing something that I think should be relatively straightforward in MDX. I would like to create a calculated member that provides a sum of one of my measures over the previous two weeks at a given point in time. My time dimension looks like:
TimeId TradingDate Day of Week
-----------------------------------
1000 11/1/2012 Thursday
1001 11/2/2012 Friday
1002 11/5/2012 Monday
1003 11/6/2012 Tuesday
... ...
What makes this particularly difficult is that my Time dimension is not quite complete. The members of my Time dimension only correspond to trading days in the stock market, and not all time. This means that weekends, holidays, or any other day in which the stock market is closed are excluded. This also means the normal methods of traversing time such as LAG or PARALLELPERIOD will not work quite right here. LAG(14), for example, means "14 trading days", which at any given point could represent a variable length of actual time.
Inside my calculated member, I'm attempting to use FILTER in order to get only time members that are within the previous two weeks of the CurrentMember. However, I can't seem to figure out the proper syntax (if there is one) to accomplish this. I imagine it would be something like:
WITH MEMBER [Sum of Price Previous 2 Weeks] AS
SUM(
FILTER(
[Time].[TimeId].Children
, [Time].[TradingDate].MemberValue
>= VBA!DATEADD("ww", -2, [Time].[TradingDate].CurrentMember.MemberValue)
)
, [Price]
)
However, this doesn't quite work. I can't seem to separate the context of the calculated members current iteration from what would be a separate context inside of the FILTER function. In other words, I'm not sure how to say:
"When iterating over the set inside of FILTER, compare the current
member of each iteration against the value of the CurrentMember in
the scope of the calculated member"
Is what I'm trying to accomplish even possible? Is there a different approach I could be taking to accomplish what I'm after?
The result you'll get from a calculated member will depend on the axis of your query. So first, make sure you have [Time].[TradingDate] in your axis.
Second, your [Time].[TradingDate] hierarchy should be ordered by Key (I assume TradingDate is the key).
Now you can use this member definition:
WITH MEMBER [Sum of Price Previous 2 Weeks] AS
SUM(
[Time].[TradingDate].CurrentMember.Lag(14):[Time].[TradingDate].CurrentMember, [Price]
)
You can use set aliases to refer to the outer CurrentMember in the Filter context:
WITH MEMBER [Sum of Price Previous 2 Weeks] AS
SUM(
GENERATE([Time].[TradingDate].CurrentMember AS CurrentDateAlias,
FILTER(
[Time].[TimeId].Children
, [Time].[TradingDate].MemberValue
>= VBA!DATEADD("ww", -2, CurrentDateAlias.Item(0).MemberValue)
)
)
, [Price]
)
GENERATE is used just to define the alias somewhere.

MDX named set with non empty last child

I have a "Trend" dimension. The hierarchy is, Trend->Week->Day. The fact is linked to the "Day" key. There is no guarantee that measure value will exist for all days in a given week.
When the user wants to see the measure at "Week" level, I need to show only the last empty day value for that Week. I have multiple measures and hence I ain't interested in creating a new calculated measure for each one of them (like How to display the total of a level as the value of its last child in MDX)
Instead, is there any way to create a named set with which I can achieve the functionality as below?
Example
Week Day Measure
1 1 4
1 2 5
2 3 7
2 7 9
3 5 10
Should get at "Week" level as
Week Day Measure
1 2 5
2 7 9
3 5 10
Thanks! :)
If you want to create a calculated member that can be reused for several measures, you can create a utility dimension that will contain a bunch of calculated member only. Dunno how to do that in SSAS (I'm familiar with icCube). Then you can use this hierarchy in your requests to apply the calculated member.
Let's take your example. I've called the utility dimension [Stats]. Its default member is a calculated member returning the value of the current measure. And it contains the [Last Day] calc. member.
WITH MEMBER [Stats].[Stats].[Last Day] AS (
NonEmpty(
Order( [Trend].[Trend].currentMember.children,
[Trend].[Trend].currentMember.properties( 'key', TYPED ),
BDESC
),
[Stats].[Stats].defaultMember
).item(0),
[Stats].[Stats].defaultMember )
SELECT
[Measures].members on 0,
[Trend].[Trend].[Week].members on 1
FROM [your-cube]
WHERE [Stats].[Stats].[Last Month]
You can see the trick with [Last Month] in the slicer that is applied to each [MEasures] of the SELECT. Then its formula is using a NonEmpty of the [Days] (reversed with the order() based on the key - you might need to adjust) for the current [MEasures].