MDX Calculating Time Between Events - sql

I have a Cube which draws its data from 4 fact/dim tables.
FactCaseEvents (EventID,CaseID,TimeID)
DimEvents (EventID, EventName)
DimCases (CaseID,StateID,ClientID)
DimTime (TimeID,FullDate)
Events would be: CaseReceived,CaseOpened,CaseClientContacted,CaseClosed
DimTime holds an entry for every hour.
I would like to write an MDX statement that will get me 2 columns: "CaseRecievedToCaseOpenedOver5" and "CaseClientContactedToCaseClosedOver5"
CaseRecievedToCaseOpenedOver5 would hold the number of cases that had a time difference over 5 hours for the time between CaseReceived and CaseOpened.
I'm guessing that "CaseRecievedToCaseOpenedOver5" and "CaseClientContactedToCaseClosedOver5" would be calculated members, but I need some help figuring out how to create them.
Thanks in advance.

This looks like a good place to use an accumulating snapshot type fact table and calculate the time it takes to move from one stage of the pipeline to the next in the ETL process.

Query for AdventureWorks (DateDiff works in MDX):
WITH
MEMBER Measures.NumDays AS
'iif(ISEMPTY(([Delivery Date].[Date].CurrentMember
,[Ship Date].[Date].CurrentMember
,Measures.[Order Count]))
,null
, Datediff("d",[Ship Date].[Date].CurrentMember.Name
,[Delivery Date].[Date].CurrentMember.Name))'
SELECT
NON EMPTY {[Ship Date].[Date].&[63]
:[Ship Date].[Date].&[92]} ON COLUMNS,
NON EMPTY {[Delivery Date].[Date].&[63]
:[Delivery Date].[Date].&[92]}
* {[Measures].[NumDays]
, [Measures].[Order Count]} ON ROWS
FROM [Adventure Works]
Taken from: http://www.mombu.com/microsoft/sql-server-olap/t-can-i-have-datediff-in-mdx-265763.html
If you'll be using this member a lot, create it as a calculated member in the cube, on the Calculations tab if I remember right.

Related

Show items with no data on rows | SSAS , MDX

Working on a task that required to bring all a dimension’s data to the Pivot Table(Excel Sheet) even if they are not related to the fact.
first I was able to do it by using the option “PivotTable Options” -> “Display” -> “Show items with no data on rows” from Excel. The problem here is that using this option is going to affect the other dimensions and the requirement is to only this work for the Student dimension only and the user doesn’t like to keep changing the option back every time. then I found this solution using SCOPE, below, but just like above I could not find an away to just have the scope ignore the logic if any other dimension added so the data does not get duplicated.
SCOPE ([Program].[Program Hierarchy].MEMBERS, [Measures].[Number of Students]);
THIS = IIF(ISEMPTY([Measures].[Number of Students]), 0, ([Measures].[Number of Students]));
END SCOPE;
So is there something that I can add the SCOPE above to just work in the program dimension and get ignore/ skip and work as normal if any other dimension added to the pivot table?
Any suggestion will be appreciated.
Lets suppose that for Dimension1 ,attribute1, and the attribute "Value 1", you had nothing in Fact, so this is removed from the result, now you forced Excel to display it by selecting the option. When you add another dimension's attribute lets say Dimension2.Attribute1, since "Value 1" had nothing in Fact, therefore Cube will not understand which value of of Dimension2.Attribute1 is to be displayed in front of Dimension1.Attribute1, therefore it will display all its values. So if we have 3 values in Dimension2, attribute 1 then "Value 1" will be repeated three times. Now with Excel you cannot solve the issue, however it might just be possible to write an MDX query that works.
Edit: Query Added.
The below sample query is based on AdventureWorks, the first sample shows that the result has some nulls, if i un-comment the "non-empty" all null values will vanish, go ahead try it.
select [Measures].[Internet Sales Amount] on columns,
--non empty
[Product].[Subcategory].[Subcategory]
on rows
from
[Adventure Works]
Result without non empty
Now lets add another dimension to the query. Notice that the null value for the first row(Bib-shorts) is now repeated for all values of the second dimension, Since cube has no way to determine which value to display.
select [Measures].[Internet Sales Amount] on columns,
--non empty
([Product].[Subcategory].[Subcategory],[Date].[Calendar Quarter of Year].[Calendar Quarter of Year])
on rows
from
[Adventure Works]
Result
Now the above result shows the issue you are facing. What we now need to do is whenever there is a null value we dont need the individual members of the second dimension, rather a place holder to satisfy the tuple, will work.
In the query below I have two tuples
1) for the not null data-points. Here we display the actual member of the second dimension.
2) for the null data-points, here we use ".defaultmember" which basically means that the second dimension will behave as it was never selected. Have a close look at the second dimension it says "All Period"
select [Measures].[Internet Sales Amount] on columns,
--non empty
{filter(([Product].[Subcategory].[Subcategory],[Date].[Calendar Quarter of Year].[Calendar Quarter of Year]),[Measures].[Internet Sales Amount]>0),
filter(([Product].[Subcategory].[Subcategory],[Date].[Calendar Quarter of Year].defaultmember),[Measures].[Internet Sales Amount]=null)
}
on rows
from
[Adventure Works]
Result:

MDX OLAP Cube Query Optimization

Problem: I'm trying to write a MDX query that will show the first date a member has measure values.
Data obstacles:
1. I don't have access to the data warehouse/source data
2. I can't request any physical calcs or CUBE changes
Looking for: I know this goes against what a CUBE should be doing, but is there any way to achieve this result. I'm running into locking conflicts and general run time issues.
Background: After some trial and error. I have a working query but sadly it's only is practical when filtered for <10 employees. I've tried some looping but there are ~60k employee ids in the cube with each one having 10-20 emp keys (one for each change in their employee info).
//must have values for measure 1 or 2
WITH
set NE_measures as
{
[Measures].[measure1] ,
[Measures].[measure2]
}
//first date with measure values for each unique emp key
MEMBER [Measures].[changedate] AS
Head
(
NonEmpty([Dim Date].[Date].[Date].allMEMBERS, NE_measures)
).Item(0).Member_Name
SELECT non empty {[Measures].[changedate]} ON COLUMNS,
non empty [Dim Employee].[Emp Key].[Emp Key].allmembers ON ROWS
FROM [Cube]
Try this:
MEMBER [Measures].[changedate] AS
Min(
[Dim Date].[Date].[Date].allMEMBERS,
IIF(
NOT(ISEMPTY([Measures].[measure1]))
OR NOT(ISEMPTY([Measures].[measure2])),
[Dim Date].[Date].CurrentMember.MemberValue,
NULL
)
);
I’m assuming the KeyColumn or ValueColumn is more likely to sort properly than the name. So if MemberValue doesn’t work then try Member_Key.
The most efficient way of accomplishing this would be to add a date column in the fact table with measure 1 and measure 2 then create a AggregateFunction=Min measure on it. But you said you couldn’t change the cube so I didn’t propose that superior option.

The MDX function CURRENTMEMBER failed because the coordinate for the 'Date' attribute contains a set

I'm trying to run this query:
select [Dim Date].[Date] on ROWS,
{[Measures].[Available Time Net],[Measures].[Logged On Time Net]} on columns
from [OTS Agent Time Net Data]
where {[Dim Date].[Date].&[08/01/2014]:[Dim Date].[Date].&[12/31/2014]}
I want to get the measures that exist in the where clause, but I also want to show the dates on the rows. I keep getting an error.
Why not just add the set directly to the ROWS ?
SELECT
NON EMPTY
{
[Dim Date].[Date].&[08/01/2014] : [Dim Date].[Date].&[12/31/2014]
} ON ROWS
,{
[Measures].[Available Time Net]
,[Measures].[Logged On Time Net]
} ON COLUMNS
FROM [OTS Agent Time Net Data];
You cannot use the same hierarchy ([Dim Date].[Date]) both on one axis and in the slicer (MDX tutorial); I guess using a sub-query would be fine for your example:
select
[Dim Date].[Date] on ROWS,
{[Measures].[Available Time Net],[Measures].[Logged On Time Net]} on columns
from (
select {[Dim Date].[Date].&[08/01/2014]:[Dim Date].[Date].&[12/31/2014]} on 0
from [OTS Agent Time Net Data]
)
Hope that helps.
(edit: using a named-set or using the slicer content right in the axis - see other responses - is a bit different because the request is filtering on a range of days but select [Dim Date].[Date] does not necessarily displays days; that could for example be the 'All' of the hierarchy of [Dim Date].[Date])
I agree with Marc's answer and that's probably the best way to do it. But there is one more way to do it, which doesn't need subselect, by making use of named sets.
with set DateRange AS
{[Dim Date].[Date].&[08/01/2014]:[Dim Date].[Date].&[12/31/2014]}
select NON EMPTY DateRange on ROWS,
{[Measures].[Available Time Net],[Measures].[Logged On Time Net]} on COLUMNS
from [OTS Agent Time Net Data]
EDIT
As per your request, if you want to additionally filter by time, here's one way to proceed. Replace the defenition of set DateRange with the below:
with set DateRange AS
EXISTS({[Dim Date].[Date].&[08/01/2014]:[Dim Date].[Date].&[12/31/2014]}
,[Dim Time].[Time].&[1930]
, "Time Net"
)
Few things which you need to fill in here..
Assuming time dimension's hierarchy to be [Dim Time].[Time]. Please replace with the actual name along with the member's format(Assumed it to be &[1930]
Also, Time Net is the measure group name to which the measures [Measures].[Available Time Net] and [Measures].[Logged On Time Net] belong to. Again, replace with original name.

Last Available value MDX

I have a requirement where in i am to extract data from a cube, within the SSRS dataset using the query builder ,with the time dimension in the result set, across a range of dates. The conditions are
The measures are to be displayed for each day of the date range.
The sub total row should have the last available measures value for that time range.
There is a time filter (currently a single date filter with a multi select option).
my MDX is as below.
The measure has a 'Sum' as the aggregation type.
I have a calculated measure with the scope defined as below.
SCOPE([MEASURES].[Measure1]);
SCOPE([Date].[Date].MEMBERS);
THIS = TAIL(EXISTING ([Date].[Date].MEMBERS),1).ITEM(0) ;
END SCOPE;
END SCOPE;
This above scope statement works perfectly. however, when i select in more that one date member this query slows WAYYYYYYY down. Performance numbers are
Across 1 date - 4 seconds
Across 2 dates - 22 minutes
Across 3 dates - unknown (in Hours)
This drastic degradation in performance goes away if i remove the scope statement, which makes me thing that there should be a better way to do the same. the final report query is as below.
SELECT
NON EMPTY
{[Measures].[Measure1]} ON COLUMNS
,NON EMPTY
{ [Dimension1].[Dimension1].[Dimension1].ALLMEMBERS*
[Dimension2].[Dimension2].[Dimension2].ALLMEMBERS*
[Dimension3].[Dimension3].[Dimension3].ALLMEMBERS*
[Date].[Date].[Date].ALLMEMBERS
} ON ROWS
FROM (
SELECT {[Date].[Date].&[2014-06-13T00:00:00]
,[Date].[Date].&[2014-06-16T00:00:00] } ON COLUMNS
FROM [Cube]
)
So the question again is, Is there a way to do the last available value part of the scope statement so as to have a better performance? Also, if there is another way to write the final mdx that would help the performance?.
Please let me know if there are anythings unclear regarding the question.
Thanks
Srikanth
The first optimization step would be to change your query to
SELECT
NON EMPTY
{[Measures].[Measure1]} ON COLUMNS
,NON EMPTY
{ [Dimension1].[Dimension1].[Dimension1].ALLMEMBERS*
[Dimension2].[Dimension2].[Dimension2].ALLMEMBERS*
[Dimension3].[Dimension3].[Dimension3].ALLMEMBERS*
{[Date].[Date].&[2014-06-13T00:00:00], [Date].[Date].&[2014-06-16T00:00:00] }
} ON ROWS
FROM [Cube]
Furthermore, I am not sure why you added the SCOPE([Date].[Date].MEMEBER); (probably Date].[Date].MEMBERS, actually). Maybe it helps to omit it and the corresponding END SCOPE.

MDX - Count of Filtered CROSSJOIN - Performance Issues

BACKGROUND: I've been using MDX for a bit but I am by no means an expert at it - looking for some performance help. I'm working on a set of "Number of Stores Authorized / In-Stock / Selling / Etc" calculated measures (MDX) in a SQL Server Analysis Services 2012 Cube. I had these calculations performing well originally, but discovered that they weren't aggregating across my product hierarchy the way I needed them to. The two hierarchies predominantly used in this report are Business -> Item and Division -> Store.
For example, in the original MDX calcs the Stores In-Stock measure would perform correctly at the "Item" level but wouldn't roll up a proper sum to the "Business" level above it. At the business level, we want to see the total number of store/product combinations in-stock, not a distinct or MAX value as it appeared to do originally.
ORIGINAL QUERY RESULTS: Here's an example of it NOT working correctly (imagine this is an Excel Pivot Table):
[FILTER: CURRENT WEEK DAYS]
[BUSINESS] [AUTH. STORES] [STORES IN-STOCK] [% OF STORES IN STOCK]
[+] Business One 2,416 2,392 99.01%
[-] Business Two 2,377 2,108 93.39%
-Item 1 2,242 2,094 99.43%
-Item 2 2,234 1,878 84.06%
-Item 3 2,377 2,108 88.68%
-Item N ... ... ...
FIXED QUERY RESULTS: After much trial and error, I switched to using a filtered count of a CROSSJOIN() of the two hierarchies using the DESCENDANTS() function, which yielded the correct numbers (below):
[FILTER: CURRENT WEEK DAYS]
[BUSINESS] [AUTH. STORES] [STORES IN-STOCK] [% OF STORES IN STOCK]
[+] Business One 215,644 149,301 93.90%
[-] Business Two 86,898 55,532 83.02%
-Item 1 2,242 2,094 99.43%
-Item 2 2,234 1,878 99.31%
-Item 3 2,377 2,108 99.11%
-Item N ... ... ...
QUERY THAT NEEDS HELP: Here is the "new" query that yields the results above:
CREATE MEMBER CURRENTCUBE.[Measures].[Num Stores In-Stock]
AS COUNT(
FILTER(
CROSSJOIN(
DESCENDANTS(
[Product].[Item].CURRENTMEMBER,
[Product].[Item].[UPC]
),
DESCENDANTS(
[Division].[Store].CURRENTMEMBER,
[Division].[Store].[Store ID]
)
),
[Measures].[Inventory Qty] > 0
)
),
FORMAT_STRING = "#,#",
NON_EMPTY_BEHAVIOR = { [Inventory Qty] },
This query syntax is used in a bunch of other "Number of Stores Selling / Out of Stock / Etc."-type calculated measures in the cube, with only a variation to the [Inventory Qty] condition at the bottom or by chaining additional conditions.
In its current condition, this query can take 2-3 minutes to run which is way too long for the audience of this reporting. Can anyone think of a way to reduce the query load or help me rewrite this to be more efficient?
Thank you!
UPDATE 2/24/2014: We solved this issue by bypassing a lot of the MDX involved and adding flag values to our named query in the DSV.
For example, instead of doing a filter command in the MDX code for "number of stores selling" - we simply added this to the fact table named query...
CASE WHEN [Sales Qty] > 0
THEN 1
ELSE NULL
END AS [Flag_Selling]
...then we simply aggregated these measures as LastNonEmpty in the cube. They roll up much faster than the full-on MDX queries.
It should be much faster to model your conditions into the cube, avoiding the slow Filter function:
If there are just a handful of conditions, add an attribute for each of them with two values, one for condition fulfilled, say "cond: yes", and one for condition not fulfilled, say "cond: no". You can define this in a view on the physical fact table, or in the DSV, or you can model it physically. These attributes can be added to the fact table directly, defining a dimension on the same table, or more cleanly as a separate dimension table referenced from the fact table. Then define your measure as
CREATE MEMBER CURRENTCUBE.[Measures].[Num Stores In-Stock]
AS COUNT(
CROSSJOIN(
DESCENDANTS(
[Product].[Item].CURRENTMEMBER,
[Product].[Item].[UPC]
),
DESCENDANTS(
[Division].[Store].CURRENTMEMBER,
[Division].[Store].[Store ID]
),
{ [Flag dim].[cond].[cond: yes] }
)
)
Possibly, you even could define the measure as a standard count measure of the fact table.
In case there are many conditions, it might make sense to add just a single attribute with one value for each condition as a many-to-many relationship. This will be slightly slower, but still faster than the Filter call.
I believe you can avoid the cross join as well as filter completely. Try using this:
CREATE MEMBER CURRENTCUBE.[Measures].[Num Stores In-Stock]
AS
CASE WHEN [Product].[Item Name].CURRENTMEMBER IS [Product].[Item Name].[All]
THEN
SUM(EXISTS([Product].[Item Name].[Item Name].MEMBERS,[Business].[Business Name].CURRENTMEMBER),
COUNT(
EXISTS(
[Division].[Store].[Store].MEMBERS,
(
[Business].[Business Name].CURRENTMEMBER,
[Product].[Item Name].CURRENTMEMBER
),
"Measure Group Name"
)
))
ELSE
COUNT(
EXISTS(
[Division].[Store].[Store].MEMBERS,
(
[Business].[Business Name].CURRENTMEMBER,
[Product].[Item Name].CURRENTMEMBER
),
"Measure Group Name"
)
)
END
I tried it using a dimension in my cube and using Area-Subsidiary hierarchy.
The case statement handles the situation of viewing data at Business level. Basically, the SUM() across all members of Item Names used in CASE statement calculates values for individual Item Names and then sums up all the values. I believe this is what you needed.