MDX Differing Daily\Weekly Aggregations - ssas

I have a fact table that records time transactions for employees each day (numerous transactions per employee per day). We also have a daily target for each worker per day.
What I'm trying to calculate is whether for each worker per day they have met their daily target (if they have = 1 if they haven't = 0) and then at a daily\weekly\monthly level we want a count of workers who have hit their target.
So in my SSAS Cube I have created a calculated measure scoped at a worker per day using the following logic, which displays correctly when you view it at a worker per day level. However I'm unsure how I would tackle getting the day level to sum up that sub scope. In the example screen shot below (PLEASE CLICK IMAGE LINK AS I CANT CURRENTLY EMBED IMAGES) I would like the value at the Day level to be 2 (as drawn on the picture) as there are 2 people who have met their target for that day.
I'm on a bit of a steep learning curve with MDX at the moment, so this is hopefully something simple for seasoned MDXers :)
TIA
CALCULATE;
CREATE MEMBER CURRENTCUBE.[Measures].[TimesheetTargetMet] AS NULL
, ASSOCIATED_MEASURE_GROUP = 'Worker Targets';
SCOPE([Measures].[TimesheetTargetMet]);
SCOPE([Ledger Date].[Financial Date].[Financial Date].MEMBERS,[Worker].[Personnel Number].[Personnel Number].MEMBERS);
THIS = IIF([Measures].[Target Timesheet Hours] > 0 AND [Measures].[Hours] > [Measures].[Target Timesheet Hours], 1,0);
END SCOPE;
END SCOPE;
Timesheet Target Met

If you wrap with the SUM function does that help?
SUM(
(
[Ledger Date].[Financial Date].currentmember
,[Worker].[Personnel Number].currentmember
)
,IIF(
[Measures].[Target Timesheet Hours] > 0
AND
[Measures].[Hours] > [Measures].[Target Timesheet Hours]
,1
,NULL
)
)

The problem with your code is the calculated measure defined in the cube script as NULL because it has no aggregation.
To resolve try adding a new named calculation in the fact table in your datasource view with the expression: NULL
In your cube structure add the named calculation column as a real measure to your fact and chose as aggregation method SUM.
SCOPE([Measures].[Your Measure]);
SCOPE([Ledger Date].[Financial Date].[Financial Date].MEMBERS);
SCOPE([Worker].[Personnel Number].[Personnel Number].MEMBERS);
THIS = IIF([Measures].[Target Timesheet Hours] > 0 AND [Measures].[Hours] > [Measures].[Target Timesheet Hours], 1,0);
END SCOPE;
END SCOPE;
END SCOPE;

My first guess is:
CALCULATE;
CREATE MEMBER CURRENTCUBE.[Measures].[TimesheetTargetMet] AS
SUM(
NonEmpty(
existing [Ledger Date].[Financial Date].[Financial Date].Members * [Worker].[Personnel Number].[Personnel Number].Members,
[Measures].[Target Timesheet Hours]
),
IIF(
[Measures].[Hours] > [Measures].[Target Timesheet Hours],
1,
NULL
)
), ASSOCIATED_MEASURE_GROUP = 'Worker Targets';
However, it may appear to work slow with drill through selects.

Related

MDX Dynamic dimension filter based on value of other dimension

How can I filter using values from two dimension in MDX?
The required result should include records where [Purchase Date].[Date] is before today minus number of years from [Accounting Date].[Year]. So, the result should include YoY records from today based on [Purchase Date].[Date] for each [Accounting Date].[Year] member.
I would like something like the following:
SELECT NON EMPTY [Measures].[Amount] ON 0,
NON EMPTY [Accounting Date].[Year].[Year].ALLMEMBERS ON 1
FROM [Tabular_Model]
WHERE (
NULL :
STRTOMEMBER("[Purchase Date].[Date].&["+ Format(DateAdd("YYYY", [Accounting Date].[Year].CURRENTMEMBER.MEMBER_VALUE - 2020, Now()),"yyyy-MM-ddT00:00:00") + "]")
)
But it fails with error: Execution of the managed stored procedure DateAdd failed with the following error: Microsoft::AnalysisServices::AdomdServer::AdomdException.
The syntax for 'All' is incorrect. (All).
Why CURRENTMEMBER.MEMBER_VALUE works for HAVING but not in my WHERE clause? What is the right way?
Try the following measure and query:
WITH
MEMBER [Measures].[Trailing Amount] as SUM({NULL :
STRTOMEMBER("[Purchase Date].[Date].&["+ Format(DateAdd("YYYY", [Accounting Date].[Year].CURRENTMEMBER.MEMBER_VALUE - 2020, Now()),"yyyy-MM-ddT00:00:00") + "]")}, [Measures].[Amount])
SELECT [Measures].[Trailing Amount] ON 0,
NON EMPTY [Accounting Date].[Year].[Year].MEMBERS ON 1
FROM [Tabular_Model]
If MDX doesn't perform as well as you hope, then you might consider adding the following DAX measure into your Tabular model. The following DAX query illustrates how to use it, but if you put this DAX measure into your model, you can query it with MDX queries and it should likely perform better than an MDX calculation:
define
measure 'Your Table Name Here'[Trailing Sales] =
VAR YearOffset = SELECTEDVALUE('Accounting Date'[Year]) - 2020
VAR NowDate = NOW()
VAR EndDate = DATE(YEAR(NowDate)+YearOffset,MONTH(NowDate),DAY(NowDate))
RETURN CALCULATE([Amount], 'Purchase Date'[Date] <= EndDate)
evaluate ADDCOLUMNS(ALL('Accounting Date'[Year]),"Trailing Sales",[Trailing Sales])

MDX - Running Sum over months limited to an interval

I have a query that after some sweating and some swearing works
WITH
MEMBER [Measures].[m_active] AS ([Measures].[CardCount], [Operation].[Code].[ACTIVATION])
MEMBER [Measures].[m_inactive] AS ([Measures].[CardCount], [Operation].[Code].[DEACTIVATION])
MEMBER [Measures].[p_active] AS
SUM(
[Calendar.YMD].[2016].[January]:[Calendar.YMD].CurrentMember,
[Measures].[m_active]
)
MEMBER [Measures].[p_inactive] AS
SUM(
[Calendar.YMD].[2016].[January]:[Calendar.YMD].CurrentMember,
[Measures].[m_inactive]
)
MEMBER [Measures].[tot_active] AS (
SUM({[Calendar.YMD].[2010].Children}.Item(0):[Calendar.YMD].CurrentMember, [Measures].[m_active]) -
SUM({[Calendar.YMD].[2010].Children}.Item(0):[Calendar.YMD].CurrentMember, [Measures].[m_inactive])
)
MEMBER [Measures].[p_tot_active] AS
SUM(
[Calendar.YMD].[2016].[January]:[Calendar.YMD].CurrentMember,
[Measures].[tot_active]
)
SELECT
{[Measures].[m_active], [Measures].[p_active], [Measures].[m_inactive], [Measures].[p_inactive], [Measures].[tot_active], [Measures].[p_tot_active]} ON COLUMNS,
NonEmptyCrossJoin(
{Descendants([Calendar.YMD].[2016].[January]:[Calendar.YMD].[2017].[August], [Calendar.YMD].[Month])},
{Descendants([CardStatus.Description].[All CardStatus.Descriptions], [CardStatus.Description].[Description])}
) on ROWS
FROM [Cube]
What I obtain is a table that for each months show the activation and deactivation relative to that month, the accumulated activations relative to the period considered (starting from 1 January 2016 and ending 1 August 2017) and the total active cards from the beginning of time (january 2010) until the end time interval.
This interval is parametrized and the day are to be considered, with this query all the activations made in august are considered even the ones made after the 1st.
I try to make some modifications like this.
WITH
MEMBER [Measures].[m_active] AS ([Measures].[CardCount], [Operation].[Code].[ACTIVATION])
MEMBER [Measures].[m_inactive] AS ([Measures].[CardCount], [Operation].[Code].[DEACTIVATION])
MEMBER [Measures].[p_active] AS
SUM(
[Calendar.YMD].[2016].[January].[1]:[Calendar.YMD].CurrentMember,
[Measures].[m_active]
)
MEMBER [Measures].[p_inactive] AS
SUM(
[Calendar.YMD].[2016].[January].[1]:[Calendar.YMD].CurrentMember,
[Measures].[m_inactive]
)
MEMBER [Measures].[tot_active] AS (
SUM({[Calendar.YMD].[2010].[January].Children}.Item(0):[Calendar.YMD].CurrentMember, [Measures].[m_active]) -
SUM({[Calendar.YMD].[2010].[January].Children}.Item(0):[Calendar.YMD].CurrentMember, [Measures].[m_inactive])
)
MEMBER [Measures].[p_tot_active] AS
SUM(
[Calendar.YMD].[2016].[January].[1]:[Calendar.YMD].CurrentMember,
[Measures].[tot_active]
)
SELECT
{[Measures].[m_active], [Measures].[p_active], [Measures].[m_inactive], [Measures].[p_inactive], [Measures].[tot_active], [Measures].[p_tot_active]} ON COLUMNS,
NonEmptyCrossJoin(
{Descendants([Calendar.YMD].[2016].[January]:[Calendar.YMD].[2017].[August], [Calendar.YMD].[Month])},
{Descendants([CardStatus.Description].[All CardStatus.Descriptions], [CardStatus.Description].[Description])}
) on ROWS
FROM [Cube]
But I get this error on the relative fields:
#ERR: mondrian.olap.fun.MondrianEvaluationException: Members must belong to the same level
How can i solve this? Thanks.

Using intersect with 2 large sets to get the distinct count - MDX

I have a calculated member which represents an active customer. That would be the following;
WITH MEMBER [Measures].[Active Customers] AS
Count ( nonempty( Filter (
( [Customer].[Customer Key].Members, [Measures].[Turnover] ),
[Measures].[Turnover] > 0
) ) )
This works great, when I want to get active customers in the current period and previous ones, as I get my time dimension, and use the CurrentMember, CurrentMember.PrevMember and CurrentMember with the Lag function in order to get customers who were active in previous periods.
My problem is when I want to get the count of customers, who are common in different members. Say I want to get customers who are active in the current period, and NOT in the previous period. Or another case, active in current, and active in previous. Because of this, I would need to use the INTERSECT function, and my customer dimension has 4 million records. This is already a subset of 9 million records.
So when checking for a customer who is active in 2 consecutive periods, I do this (The Active Previous Period, and Active Current Period is basically the calculated member above, however with CurrentMember and CurrentMember.PrevMember) :
set [Previous Active Customers Set] AS
Filter (
( [Customer].[Customer Key].Members, [Measures].[Active Previous Period] ),
[Measures].[Active Previous Period] > 0
)
set [Current Active Customers Set] AS
Filter (
( [Customer].[Customer Key].Members, [Measures].[Active Current Period] ),
[Measures].[Active Current Period] > 0
)
member [Measures].[Active 2 consecutive periods] as
count(INTERSECT([Current Active Customers Set],[Previous Active Customers Set]) )
This takes forever. Is there anyway to improve, or go around this performance problem of using the INTERSECT with large sets? Or maybe optimizations on the MDX query? I tried always using a subset of my customers dimension, but this only reduced the number of records to less than 4 million - so it's still large. Any help would be appreciated!
I would assume you can speed this up if you avoid using named sets and calculated members as far as possible.
One step towards this would be as follows: Create a new fact table with foreign keys just to your customer and time dimension, and add a record to it if a customer was active on that day. Build a measure group, let's say "activeCustomers" based on this table, just using "count" as the measure. But make this invisible, as we do not need it.
Then, you can replace
count( nonempty( Filter (
( [Customer].[Customer Key].Members, [Measures].[Turnover] ),
[Measures].[Turnover] > 0
) ) )
with
count( Exists(
[Customer].[Customer Key].Members,
<state your time selection here>,
"activeCustomers"
) )
Exists should be more efficient than Filter.
Another optimization approach could be the observation that instead of intersecting two sets generated via Filter, you could define one set with a more complex filter, avoiding that AS is looping along the customers twice, and then intersecting the results:
set [Active Customers Set] AS
Filter (
( [Customer].[Customer Key].Members, [Measures].[Active Previous Period] ),
[Measures].[Active Previous Period] > 0
AND
[Measures].[Active Current Period] > 0
)

MDX -No Sales over 30 days

I'd like to get the product with zero sales over 30 days. E.g. Below is my expected result:
Store,Product,Days
Store1, product1, 33
Store1, product2, 100
Store2, product5, 96
Store34, product14, 78
Store100, product9, 47
So I wrote below query:
WITH
MEMBER [Measures].[Zero Sales Days]
AS
COUNT(
FILTER(
NONEMPTY( [Calendar].[Date].[Day],[Measures].[POS Qty])
, ( [Measures].[POS Qty]=0)
)
)
SELECT
([Store].[Store].[Store],[product].[product].[product]) on 1,
([MEASURES].[Zero Sales Days]) ON 0
FROM [testcube]
The problem is: How to filter the case: days of zero sales<30
Thanks,
Nia
I did some change and then ran against my DB.
I got nothing if I added the where cause. If not, the result is '#Error'.
I need not select any time related dimension. What I want to do for the report is: select store and product dimension, and define a calculated measure to get the count. Boyan, I will be really appreciated it if you can need the detailed the query for it.
The function LastPeriods is what you're looking for:
WITH
MEMBER [Measures].[Zero Sales Days]
AS COUNT(
FILTER([Calendar].[Date].[Day],
SUM( LastPeriods(30, [Calendar].[Date].currentmember),[Measures].[POS Qty])
= 0 )
)
SELECT
([Store].[Store].[Store],[product].[product].[product]) on 1,
([MEASURES].[Zero Sales Days]) ON 0
FROM [testcube]
The following query works against Adventure Works and shows you the products with no sales for over 30 days from the date in the WHERE clause back:
WITH
MEMBER [Measures].[Number of Periods With No Sales] AS
Iif(([Date].[Date].CurrentMember, [Measures].[Internet Sales Amount])=0,
([Date].[Date].PrevMember, [Measures].[Number of Periods With No Sales])+1,
NULL
)
MEMBER [Measures].[Number of > 30 Periods With No Sales] AS
Sum(
Iif([Measures].[Number of Periods With No Sales] > 30,
[Measures].[Number of Periods With No Sales],
NULL
)
)
SELECT
{
[Measures].[Number of > 30 Periods With No Sales]
} ON 0,
NON EMPTY {
[Product].[Product Categories].[Product]
} ON 1
FROM [Adventure Works]
WHERE [Date].[Calendar].[Date].&[860]
You will need to re-work it (change the dimension/measure names) to get it to work against your db. Please let me know if you need a query which can give you all products regardless of the date, which have at least one period with more than 30 days with no sales (e.g. max period with no sales, or an arbitrary such period). This will require a few changes. Also, since the query is using recursion it may be slow - if it is too slow we can see how to improve its performance - something which may require changes to your data model to support this bit of analytics.

Check if level in any dimension hierarchy is [Foo]

In my Date Dimension I have 2 hierarchies:
[Year - Week - Date] and [Year - Month - Date]
I want to check in a Cube Calculation if the current level of the Date Dimension is [Date] (the lowest level) or something higher.
How can I achieve this?
Background: I need this to calculate how many working days there were in a period, for an employee.
I currently have this code (untested) that should do the trick for 1 hierarchy, but I guess this will fail when users are using the [Year - Week - Date] hierarchy.
CASE WHEN
[Date].[Year - Month - Date].CURRENTMEMBER.Level
IS
[Date].[Year - Month - Date].[Date]
THEN
//at day level,
//if there is any duration booked, this is a working day
IIF([Measures].[Duration] = 0,0,1)
ELSE
//at higher than day level,
//count days
COUNT(
// where duration > 0 (employee work day)
FILTER(
Descendants([Date].[Year - Month - Date].CURRENTMEMBER, [Date].[Year - Month - Date].[Date]),
[Measures].[Duration] > 0
)
)
END
tl;dr how do I make the above code also work for [Year - Week - Date] hierarchy in the cleanest way possible.
Let's assume the hierarchy (aka attribute) [Date].[Date] exists. If this is the case you can simplify :
COUNT(
FILTER( Existing [Date].[Date].members, [Measures].[Duration] > 0 )
)
The Existing will force to apply an autoexists on the [Date] dimension. This is mainly a performance improvement as it's avoid to evaluate (fact-vise) all tuples.
Once the former examples is clear, we can merge it with your version for the fastest solution (no need for the first iif) :
COUNT(
FILTER( Existing
Descendants([Date].[Year - Month - Date].CURRENTMEMBER, [Date].[Year - Month - Date].[Date],self)
, [Measures].[Duration] > 0 )
)
Further improvements may be possible adding a new measure with a different aggregation type or adding an iif to change which of the two hierarchies is used for descendants (e.g. the one where the currentmember and the defaulmember is not equal)
If you are looking for the cleanest way to calculate how many working days there were in a period, you have to emulate the behavior of regular measure. Otherwise you will get error or bad data in the case of a multiselect on [Date]. In addition, it is desirable to get rid of the Count(Filter(...)) expression to keep the block computation mode (see Optimizing Count(Filter(...)) expressions in MDX). To do this, follow these steps:
Go to the data source view.
Create a new named calculation next to the [Duration] column (in the same fact table).
Column name is "Working days", expression is "null".
Create a new regular measure based on "Working days" column. Aggregation function is Sum.
Write in the MDX script:
(
[Date].[Date].[Date].Members,
[Measures].[Working days]
) = Iif( [Measures].[Duration] > 0, 1, null );
IsLeaf() will tell you if a member is at the bottom level.
Look at SCOPE commands in your calculation script, I do something similar for my YMD calender and my YWD calendar:
CREATE MEMBER CurrentCube.Measures.Example AS NULL //Dummy will be calc'd later
SCOPE (DESCENDANTS([Date].[Calender Y M D],,AFTER));
Measures.Example = 1; //Fill in for monthly calcs
END SCOPE
SCOPE (DESCENDANTS([Date].[Calender Y W D],,AFTER));
Measures.Example = 2; //Fill in for weekly calcs
END SCOPE
The syntax with the ,,AFTER is to exclude the All member if I remember rightly, I'm trying to dif the link up but can't find it. Alternatively, if the calculation works well with the ALL member, just use SCOPE([Date].[Calender Y W D])