I am new to MDX queries. I have written a query which uses lead function to get values for (Current Week plus 1) as a new column for each of the metrics. Basically comparing current week value with last week's value. Without the new members the query runs within seconds. After adding the new members it runs forever. Pls suggest ways to optimize this query
Thanks for help.
WITH
SET [Range] as strtomember
(" [Time].[Week].&["+ Format(DateAdd('d', - DatePart('w', Now(), 2), Now()+7), "yyyy-MM-ddT00:00:00")
+"]")
:strtomember
(" [Time].[Week].&["+ Format(DateAdd('d', - DatePart('w', Now(), 2), Now()+14), "yyyy-MM-ddT00:00:00")
+"]")
MEMBER [Measures].[SalesNew] as
CASE WHEN [Time].[Year].CURRENTMEMBER IS [Time].[Year].&[2019] THEN
sum([Time].[Day].CURRENTMEMBER.LEAD(7),[Measures].[Sales Prev])
ELSE null
END,FORMAT_STRING = "$#,###.00"
MEMBER [Measures].[Order UnitsNew] as
CASE WHEN [Time].[Year].CURRENTMEMBER IS [Time].[Year].&[2019] THEN
sum([Time].[Day].CURRENTMEMBER.LEAD(7),[Measures].[Order Units Prev])
ELSE null
END,FORMAT_STRING = "#,##0"
MEMBER [Measures].[Count of OrdersNew] as
CASE WHEN [Time].[Year].CURRENTMEMBER IS [Time].[Year].&[2019] THEN
sum([Time].[Day].CURRENTMEMBER.LEAD(7),[Measures].[Count of Orders Prev])
ELSE null
END,FORMAT_STRING = "#,##0"
SELECT
{ [Measures].[Sales],[Measures].[Sales Prev],[Measures].[SalesNew],[Measures].[Order Units],[Measures].[Order Units Prev],
[Measures].[Order UnitsNew], [Measures].[Count of Orders],[Measures].[Count of Orders Prev], [Measures].[Count of OrdersNew]} ON COLUMNS ,
[Range] *
[Time].[Day].[Day].ALLMEMBERS * -- 4
[Time].[Hour].ALLMEMBERS *
[Product].[Merch Dept].ALLMEMBERS *
[Product].[Class].ALLMEMBERS ON ROWS
FROM [Cube]
Your MDX looks pretty cool.
Optimizing MDX is a little bit black magic - you need to just try alternatives.
Initially I'd try:
1.Swapping in strToSet rather than the two strToMember functions.
2.Do you need the condition [Time].[Year].CURRENTMEMBER IS [Time].[Year].&[2019] ? Reason I ask is that [Range] seems to
already be 2019 and you cross-join to that set so do you need to
worry about 2019?
3.You can use a simple Tuple instead of the aggregate SUM
4.Maybe while testing it might be worth removing the format_strings - shouldn't make a difference but if so you can add back.
I've not tested the following so please excuse typos:
WITH
SET [DateSet] AS
strtoSet
(
"[Time].[Week].&["
+ Format(DateAdd('d', - DatePart('w', Now(), 2), Now()+7), "yyyy-MM-ddT00:00:00")
+ "]:[Time].[Week].&["
+ Format(DateAdd('d', - DatePart('w', Now(), 2), Now()+14), "yyyy-MM-ddT00:00:00")
+"]"
)
MEMBER [Measures].[SalesNew] AS
(
[Time].[Day].CURRENTMEMBER.LEAD(7)
,[Measures].[Sales Prev]
)
MEMBER [Measures].[Order UnitsNew] AS
(
[Time].[Day].CURRENTMEMBER.LEAD(7)
,[Measures].[Order Units Prev]
)
MEMBER [Measures].[Count of OrdersNew] AS
(
[Time].[Day].CURRENTMEMBER.LEAD(7)
,[Measures].[Count of Orders Prev]
)
SELECT
{
[Measures].[Sales]
,[Measures].[Sales Prev]
,[Measures].[SalesNew]
,[Measures].[Order Units]
,[Measures].[Order Units Prev]
,[Measures].[Order UnitsNew]
,[Measures].[Count of Orders]
,[Measures].[Count of Orders Prev]
,[Measures].[Count of OrdersNew]
} ON COLUMNS ,
[DateSet] *
[Time].[Day].[Day].ALLMEMBERS *
[Time].[Hour].ALLMEMBERS *
[Product].[Merch Dept].ALLMEMBERS *
[Product].[Class].ALLMEMBERS ON ROWS
FROM [Cube];
Related
I am writing an MDX query in which i am selecting some Measures and while selection i have a where condition in which i am doing a cross join two facts , one is date and another a unique id and i am passing around 2000 unique ids and the query is taking around 20 minutes to execute and give the result.
Please find below query for the same
SELECT {[Measures].[TOTAL1], [Measures].[TOTAL2], [Measures].[TOAL3]} ON COLUMNS,
" + " {TOPCOUNT(FILTER([ID].[Ids].MEMBERS,
[ID].CurrentMember > 0),
5,[Measures].[TOTAL])} " + "ON ROWS
FROM [CHARTS]
WHERE({[Date].&[2015-09-01 00:00:00.0]}*{[NUM].[1],[NUM].[10],"
+ "[NUM].[18],[NUM].[47],[NUM].[52],[NUM].[105],[NUM].[126],[NUM].[392],"
+ "[NUM].[588],[NUM].[656],[NUM].[995],[NUM].[1005],[NUM].[1010],[NUM].[1061]})";
The straight mdx without the string manipulation operators (+) is as follows:
SELECT
{
[Measures].[TOTAL1]
,[Measures].[TOTAL2]
,[Measures].[TOAL3]
} ON COLUMNS
,{
TopCount
(
Filter
(
[ID].[Ids].MEMBERS
,
[ID].CurrentMember > 0
)
,5
,[Measures].[TOTAL]
)
} ON ROWS
FROM [CHARTS]
WHERE
{[Date].&[2015-09-01 00:00:00.0]}
*
{
[NUM].[1]
,[NUM].[10]
,[NUM].[18]
,[NUM].[47]
,[NUM].[52]
,[NUM].[105]
,[NUM].[126]
,[NUM].[392]
,[NUM].[588]
,[NUM].[656]
,[NUM].[995]
,[NUM].[1005]
,[NUM].[1010]
,[NUM].[1061]
};
Can you please tell me the different performance optimization techniques for the same.
TopCount is slow if you use the third ordering parameter - it is better to order the data first and then feed your pre-ordered set into TopCount with just 2 parameters:
WITH
SET [S0] AS
Filter
(
[ID].[Ids].MEMBERS
,
[ID].CurrentMember > 0
)
SET [S1] AS
Order
(
[S0]
,[Measures].[TOTAL]
,BDESC
)
SET [S2] AS
TopCount
(
[S1]
,5
)
SELECT
{
[Measures].[TOTAL1]
,[Measures].[TOTAL2]
,[Measures].[TOAL3]
} ON COLUMNS
,[S2] ON ROWS
FROM [CHARTS]
WHERE
{[Date].&[2015-09-01 00:00:00.0]}
*
{
[NUM].[1]
,[NUM].[10]
,[NUM].[18]
,[NUM].[47]
,[NUM].[52]
,[NUM].[105]
,[NUM].[126]
,[NUM].[392]
,[NUM].[588]
,[NUM].[656]
,[NUM].[995]
,[NUM].[1005]
,[NUM].[1010]
,[NUM].[1061]
};
I wanted to do the trend analysis between the dates. For an instance current date- 30 days
30-60 days and so on.Below is the snippet of comparable sql query but same I wanted to do in MDX.
SQL
SELECT
ROUND
(
(
(
(
SELECT
SUM(del_pri_impr)
FROM
reporting.so_sli_calc_val a,
reporting.user_group_tenant b,
reporting.salesorder c
WHERE
created_on BETWEEN DATE(now()-30) AND DATE(now())
)
-
(
SELECT
SUM(del_pri_impr)
FROM
reporting.so_sli_calc_val a,
reporting.user_group_tenant b,
reporting.salesorder c
WHERE
created_on BETWEEN DATE(now()-60) AND DATE(now()-30)
)
)
/
(
SELECT
SUM(del_pri_impr)
FROM
reporting.so_sli_calc_val a,
reporting.user_group_tenant b,
reporting.salesorder c
WHERE
created_on BETWEEN DATE(now()-60) AND DATE(now()-30)
) *100
)
,
0
) AS trend
MDX:
WITH
SET [~FILTER] AS
{[Created_Date.Created_Hir].[Created_On].[2014-04-01]:[Created_Date.Created_Hir].[Created_On].[2014-04-30]}
SET [~ROWS] AS
{[Sales Order Attributes SO.Sales_order].[Sales Order ID].Members}
SELECT
NON EMPTY {[Measures].[CONT_AMT_GROSS], [Measures].[CONT_AMT_NET]} ON COLUMNS,
NON EMPTY [~ROWS] ON ROWS
FROM [SALES_ORDER]
WHERE [~FILTER]
As of now I have hard coded the dates, that will come from parameters.
I am facing difficulty in creating the second set and how to do subtraction between two sets in MDX.
You already have the logic on how to obtain sets of date corresponding to "last 30 days from now" and "last 60 to last 30 days from now". So, I am going to skip that part.
NOTE - You would have to use the parameter values while building these sets.
What you want to do here is first find the values corresponding to these sets of dates and then perform operations on them.
You can proceed like this -
WITH
SET [~FILTER] AS
{[Created_Date.Created_Hir].[Created_On].[2014-04-01]:[Created_Date.Created_Hir].[Created_On].[2014-04-30]}
SET [~ROWS] AS
{[Sales Order Attributes SO.Sales_order].[Sales Order ID].Members}
SET [Last30Days] AS
...
SET [Last60ToLast30Days] AS
...
MEMBER [~Last30Days - Now] AS
Aggregate
(
[Last30Days],
[Measures].[SomeMeasure]
)
MEMBER [~Last60Days - Last30Days] AS
Aggregate
(
[Last60ToLast30Days],
[Measures].[SomeMeasure]
)
MEMBER [~Measure] AS
([~Last30Days - Now]-[~Last60Days - Last30Days] )/([~Last60Days - Last30Days] * 100), format_string = '#,##0'
SELECT
NON EMPTY {
[Measures].[CONT_AMT_GROSS],
[Measures].[CONT_AMT_NET],
[~Measure]
} ON COLUMNS,
NON EMPTY [~ROWS] ON ROWS
FROM [SALES_ORDER]
Format_String takes care of rounding.
Not sure if I totally agree with Sourav's answer as I think some form of aggregation will be needed; creating tuples with sets in them may raise an exception.
Here is a simple model, against AdvWrks, that is tested and will do a subtraction for you:
WITH
SET [Set1] AS
[Date].[Calendar].[Date].&[20060301]
:
[Date].[Calendar].[Date].&[20070308]
SET [Set2] AS
[Date].[Calendar].[Date].&[20070308]
:
[Date].[Calendar].[Date].&[20080315]
MEMBER [Date].[Calendar].[All].[Set1Agg] AS
aggregate([Set1])
MEMBER [Date].[Calendar].[All].[Set2Agg] AS
aggregate([Set2])
MEMBER [Date].[Calendar].[All].[x] AS
(
[Date].[Calendar].[All].[Set1Agg]
,[Measures].[Internet Sales Amount]
)
MEMBER [Date].[Calendar].[All].[y] AS
(
[Date].[Calendar].[All].[Set2Agg]
,[Measures].[Internet Sales Amount]
)
MEMBER [Date].[Calendar].[All].[x-y] AS
[Date].[Calendar].[All].[x] - [Date].[Calendar].[All].[y]
SELECT
{
[Date].[Calendar].[All].[x]
,[Date].[Calendar].[All].[y]
,[Date].[Calendar].[All].[x-y]
} ON 0
,[Product].[Category].[Category] ON 1
FROM [Adventure Works];
Reflecting against your code maybe something like the following:
WITH
SET [Set1] AS
[Created_Date.Created_Hir].[Created_On].[2014-04-01]
:
[Created_Date.Created_Hir].[Created_On].[2014-04-30]
SET [Set2] AS
[Created_Date.Created_Hir].[Created_On].[2014-03-01]
:
[Created_Date.Created_Hir].[Created_On].[2014-03-31]
MEMBER [Created_Date.Created_Hir].[All].[Set1Agg] AS
Aggregate([Set1])
MEMBER [Created_Date.Created_Hir].[All].[Set2Agg] AS
Aggregate([Set2])
MEMBER [Measures].[~Last30Days - Now] AS
(
[Created_Date.Created_Hir].[All].[Set1Agg]
,[Measures].[SomeMeasure]
)
MEMBER [Measures].[~Last60Days - Last30Days] AS
(
[Created_Date.Created_Hir].[All].[Set2Agg]
,[Measures].[SomeMeasure]
)
MEMBER [Measures].[~Measure] AS
([Measures].[~Last30Days - Now] - [Measures].[~Last60Days - Last30Days])
/
[Measures].[~Last60Days - Last30Days]
* 100
,format_string = '#,##0'
SET [~ROWS] AS
{
[Sales Order Attributes SO.Sales_order].[Sales Order ID].MEMBERS
}
SELECT
NON EMPTY
{
[Measures].[CONT_AMT_GROSS]
,[Measures].[CONT_AMT_NET]
,[Measures].[~Measure]
} ON COLUMNS
,NON EMPTY
[~ROWS] ON ROWS
FROM [SALES_ORDER]
WHERE
[~FILTER];
I'm trying to create a Calculated Member in my cube with where clause but couldn't figure how to achieve the proper result.
I created a calculated member "Outlook" using the below code to display only Forecast values.
CREATE MEMBER CURRENTCUBE.[Measures].[Outlook]
AS SUM(([Source Profile].[Source Profile Hierarchy].CurrentMember,
[Source Profile].[Profile Level01].&[Outlook]),
[Measures].[USD Amount]),
FORMAT_STRING = "#,##0;(#,##0)",
VISIBLE = 1 , DISPLAY_FOLDER = 'USD' , ASSOCIATED_MEASURE_GROUP = 'CARS';
Cube Result
Now I would like to filter the results dynamically based on another hidden dimension "Current_Month". This dimension always has current financial period value and it's corresponding outlook profile
Year_Month Outlook_Profile
2015010 10 + 2
Expected result should be "Outlook" measure showing value based on Current_Month dimension, which is '10 + 2' and rest of them should be 0
Expected result
Just to explain the requirement in SQL terms, I would like to achieve the below in MDX
Where Fact.Source_Profile=Dimension.Source_Profile
instead of
Where Fact.Source_Profile='10 + 2'
I'm not sure how to achieve this in Where Clause or by another means. I could see examples of hard coding values, like year.&[2015] but haven't seen one using dynamic values.
I found a solution myself and thought of sharing the same. Used StrToMember function to pass hidden dimension's member as a variable here.
CREATE MEMBER CURRENTCUBE.[Measures].[Outlook]
AS (
Sum
(
(
[Source Profile].[Source Profile Hierarchy].CurrentMember,
strtomember("[Source Profile].[Source Name].&[" + [Outlook Profile].[Outlook Profile].&[1].Member_Caption + "]")
)
,[Measures].[USD Amount]
)
),
FORMAT_STRING = "#,##0;(#,##0)",
VISIBLE = 1 , DISPLAY_FOLDER = 'USD' , ASSOCIATED_MEASURE_GROUP = 'CARS' ;
Using the illustrated mockup
(Note not all DimB members show so the total for DimA is larger than the sum of the displayed DimB, ignore this):
Query is showing (months,revenue) on 0, (dima,dimb) on 1 from cube
I would like some assistance in creating the calculated member that would calculate the running total across "Months" here (not a real time dimension).
Earlier i was using this one, but it was wrong on so many levels:
[Measures].[Accumulated Revenue] =
IIF(
([DimMonths].[Months].CurrentMember,[Measures].[Revenue]) = 0
AND ([DimMonths].[Months].CurrentMember.NextMember,[Measures].[Revenue]) = 0
,
IIF(
AGGREGATE({
[DimMonths].[Months].CurrentMember :
[DimMonths].[Months].Parent.LastChild
},[Measures].[Revenue]) = 0
,
NULL
,
Aggregate( {
[DimMonths].[Months].Parent.FirstChild :
[DimMonths].[Months].CurrentMember.PrevMember
}
,[Measures].[Revenue])
)
,
Aggregate( {
[DimMonths].[Months].Parent.FirstChild :
[DimMonths].[Months].CurrentMember
}
,[Measures].[Revenue])
)
Sounds like a job for a "scoped assignment". Basically, using a scoped assignment, you can overwrite the value of your calculated measure along the [Member_DimA_01] slice so that these values represent a running total instead of a sum of children.
Here's a good tutorial from Chris Webb on scoped assignments to help get you started...
Edit: here's a template for the running total calculation...
WITH
SET [Months] AS
{
[Date].[Calendar Month].&[2011 - Jan]
:
[Date].[Calendar Month].&[2011 - Dec]
}
MEMBER [Measures].[Running Total] AS
SUM(
{
[Date].[Calendar Hierarchy].[Calendar Month].&[2011 - Jan]
:
[Date].[Calendar Hierarchy].CurrentMember
}
,[Measures].[Revenue]
)
SELECT
NON EMPTY{
[Months]
} ON 0,
NON EMPTY{
[DimA].[Member].Allmembers *
[DimB].[Member].Allmembers
} ON 1
FROM
[<<cube name>>]
WHERE
[Measures].[Revenue Running Total]
I have created an MDX query which calculates the TOP 10 ZipCodes (according to my Patient Stay measure) as such:
WITH
MEMBER [Discharge Date].[Y-M-D].[ Aggregation] AS 'AGGREGATE( EXISTING { [Current Month] } )', SOLVE_ORDER = 0
SELECT
NON EMPTY { [Measures].[Patient Stays] }
ON COLUMNS,
TOPCOUNT({ ORDER( HIERARCHIZE( { [Patient].[ByZipcode].[All].CHILDREN } ), ( [Measures].[Patient Stays] ), BDESC ) }, 10)
ON ROWS
FROM [Patient Stay]
WHERE ( [Discharge Date].[Y-M-D].[ Aggregation], [Facility].[ByAffiliation].CURRENTMEMBER, [Facility].[ByRegion].CURRENTMEMBER )
This query is used to populate a PerformancePoint 100% Stacked Bar chart. The client has asked that since this is a !00% based chart, we lump the rest of the zip codes into an "Other" field, such that there should be 11 values: one for each of the top 10, and an eleventh which is a sum of the remaining Zip Codes.
I am an extreme novice to MDX, but this doesn't souund like it should be impossible. Does anyone have any ideas or suggestions?
I'll do my best with untested code, so here goes:
WITH
MEMBER [Discharge Date].[Y-M-D].[ Aggregation] AS 'AGGREGATE( EXISTING { [Current Month] } )', SOLVE_ORDER = 0
SET [Top10ZipCodes] AS
(TOPCOUNT({ ORDER( HIERARCHIZE( { [Patient].[ByZipcode].[All].CHILDREN } ), ( [Measures].[Patient Stays] ), BDESC ) }, 10))
MEMBER [Patient].[ByZipCode].[OtherZipCodes] AS
(AGGREGATE({EXCEPT([Patient].[ByZipCode].Members, [Patient].[ByZipCode].[Top10ZipCodes])}))
SELECT
NON EMPTY { [Measures].[Patient Stays] }
ON COLUMNS,
{[Top10ZipCodes], [Patient].[ByZipCode].[OtherZipCodes]}
ON ROWS
FROM [Patient Stay]
WHERE ( [Discharge Date].[Y-M-D].[ Aggregation], [Facility].[ByAffiliation].CURRENTMEMBER, [Facility].[ByRegion].CURRENTMEMBER )
What this does is creates a set of your top 10 ZIP codes, and then aggregates (different than sum!!!) all the ZIP codes, with the exception of your top 10.
Also, if this is a common set (top 10 ZIP codes), you may want to make a set on the cube, where you can reuse it ad nauseum, without having to change every MDX query you have.
Cheers,
Eric