Optimize a MDX Query - ssas

I am quite new to MDX and I need some help with this query.
Generate(
filter(
[Dim Products].[Product].[Product].members
*
[Dim Date].week.week.members,
[Measures].[Price]
),
nonempty(
topcount(
[Dim Price].[Price].[Price]
*
[Dim Products].[Product].currentmember
*
[Dim Date].week.currentmember,
1,
[Measures].[Price Count]
)
)
)
I am using the above Named Set in a dashboard tool (Dundas Dashboard) in order to retrieve the MODE (price value that repeats the most). It does show correct results however it is slow. It takes 2-3 seconds if there is a filter on a single week and takes about 6-7 seconds if there is no filter on week (shows data for all weeks). And this is in SSMS but the client tool takes even longer to get the result set, sometimes timing out.
After some tests it appears that the number of the rows in the fact table does not affect the performance, I've drastically decreased it but still the same. The performance increased, however, once I've decreased the number of members in 2 of the dimension tables - [Dim Price], [Dim Products]. Initially it was taking 20+ seconds to get result set but improved to 6-7 seconds once I've decreased the number of members as follows:
Table | Rows Before | Rows After
[Dim Price] | 2400 | 620
[Dim Products] | 1080 | 101
This makes me think there is a Cartesian Product between the 3 dimensions that is affecting the performance.
I need someone to advise how I can improve the query to increase the performance.

Try the EXISTING function before the NONEMPTY function like this:
Generate(
filter(
[Dim Products].[Product].[Product].members
*
[Dim Date].week.week.members,
[Measures].[Price] //<<<<<<<<<<SHOULD THIS NOT INCLUDE A CONDITION e.g. [Measures].[Price] > 0 ?
),
EXISTING nonempty(
topcount(
[Dim Price].[Price].[Price]
*
[Dim Products].[Product].currentmember
*
[Dim Date].week.currentmember,
1,
[Measures].[Price Count]
)
)
)
Attempt 2:
Generate(
filter(
NONEMPTY(
[Dim Products].[Product].[Product].members
*
[Dim Date].week.week.members,
[Measures].[Price]
)
),
EXISTING nonempty(
topcount(
[Dim Price].[Price].[Price]
*
[Dim Products].[Product].currentmember
*
[Dim Date].week.currentmember,
1,
[Measures].[Price Count]
)
)
)
Attempt 3:
GENERATE(
FILTER(
NONEMPTY(
[Dim Products].[Product].[Product].MEMBERS
*
[Dim Date].week.week.MEMBERS
),
[Measures].[Price] > 0
),
TOPCOUNT(
NONEMPTY(
EXISTING
[Dim Price].[Price].[Price]
*
[Dim Products].[Product].currentmember
*
[Dim Date].week.currentmember,
[Measures].[Price Count]
),
1,
[Measures].[Price Count]
)
)

Related

The STRTOSET function expects a string or numeric expression for the 1 argument. A tuple set expression was used

I am very new to this whole MDX query thing. I have the following query which I am trying to execute but keep getting the following error:
Executing the query ...
Query (19, 16) The STRTOSET function expects a string or numeric expression for the 1 argument. A tuple set expression was used.
Execution complete
My query looks as follows:
SELECT
NON EMPTY { [Measures].[Effective Duration] } ON COLUMNS,
NON EMPTY { (
[Dim Cause Code].[Cause].[Cause].ALLMEMBERS *
[Dim Classification].[Classification].[Classification].ALLMEMBERS *
[Dim Date 1].[Full Date Alternate Key].[Full Date Alternate Key].ALLMEMBERS *
[Dim Location 1].[Work Center Name].[Work Center Name].ALLMEMBERS *
[Fact Process Downtime].[Start Datetime].[Start Datetime].ALLMEMBERS *
[Fact Process Downtime].[End Datetime].[End Datetime].ALLMEMBERS *
[Fact Process Downtime].[Id].[Id].ALLMEMBERS )
} DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME ON ROWS FROM (
SELECT ( STRTOSET({[Fact Process Downtime].[Is Virtual].&[False]}, CONSTRAINED) ) ON COLUMNS
FROM (
SELECT ( STRTOSET({[Dim Location 1].[Work Center].&[South32 Manganese.Mamatwan.DMS]}, CONSTRAINED) ) ON COLUMNS
FROM (
SELECT ( STRTOSET({[Dim Classification].[Classification].&[Scheduled Process]}, CONSTRAINED) ) ON COLUMNS
FROM (
SELECT ( STRTOSET({[Dim Date 1].[Month Fiscal Year].&[Jul/17]}, CONSTRAINED) ) ON COLUMNS FROM [FactDownTime])))) WHERE (
IIF( STRTOSET({[Dim Date 1].[Month Fiscal Year].&[Jul/17]}, CONSTRAINED).Count = 1, STRTOSET({[Dim Date 1].[Month Fiscal Year].&[Jul/17]}, CONSTRAINED), [Dim Date 1].[Month Fiscal Year].currentmember ),
IIF( STRTOSET(STRTOSET({[Dim Location 1].[Work Center].&[South32 Manganese.Mamatwan.DMS]}, CONSTRAINED).Count = 1, STRTOSET(STRTOSET({[Dim Location 1].[Work Center].&[South32 Manganese.Mamatwan.DMS]}, CONSTRAINED), [Dim Location 1].[Work Center].currentmember ),
IIF( STRTOSET({[Fact Process Downtime].[Is Virtual].&[False]}, CONSTRAINED).Count = 1, STRTOSET({[Fact Process Downtime].[Is Virtual].&[False]}, CONSTRAINED), [Fact Process Downtime].[Is Virtual].currentmember )
)))
You need to supply strings to this function strToSet is short for "String to Set"
https://msdn.microsoft.com/en-us/library/ms144782.aspx
So this is incorrect:
STRTOSET({[Fact Process Downtime].[Is Virtual].&[False]}, CONSTRAINED)
But this is correct
STRTOSET('{[Fact Process Downtime].[Is Virtual].&[False]}', CONSTRAINED))
The main use case for the strToSet function is when passing parameters into an mdx query from say SSRS so syntax like this is seldom used:
STRTOSET('{[Fact Process Downtime].[Is Virtual].&[False]}', CONSTRAINED))
If parameters are not involved then just get rid of the STRTOSET:
{[Fact Process Downtime].[Is Virtual].&[False]}
If you need to use parameters then check out this excellent recent answer from # alejandrozuleta:
MDX SSRS Parameter category chooses all sub category
If parameters are not important then your script can maybe get simplified to something like this:
SELECT
NON EMPTY [Measures].[Effective Duration] ON 0,
NON EMPTY
[Dim Cause Code].[Cause].[Cause].ALLMEMBERS *
[Dim Classification].[Classification].[Classification].ALLMEMBERS *
[Dim Date 1].[Full Date Alternate Key].[Full Date Alternate Key].ALLMEMBERS *
[Dim Location 1].[Work Center Name].[Work Center Name].ALLMEMBERS *
[Fact Process Downtime].[Start Datetime].[Start Datetime].ALLMEMBERS *
[Fact Process Downtime].[End Datetime].[End Datetime].ALLMEMBERS *
[Fact Process Downtime].[Id].[Id].ALLMEMBERS
ON 1
FROM
(
SELECT
[Fact Process Downtime].[Is Virtual].&[False] ON 0,
[Dim Location 1].[Work Center].&[South32 Manganese.Mamatwan.DMS] ON 1,
[Dim Classification].[Classification].&[Scheduled Process] ON 2,
[Dim Date 1].[Month Fiscal Year].&[Jul/17] ON 3
FROM [FactDownTime]
);

how to use MDX average function?

I am VERY new to mdx, and about to learn it. i got into trouble right now ,because i want to get average amount for each weekday between two dates.
For example between 1st of november and 1st of december there are 4 mondays, so average would be [Measures].[Amount] / 4, but i really cant figure out how to express this simple function in MDX
with member [Measures].[Avg] as
avg([Dim Date].[Day Of Week].[Day Of Week]
, [Measures].[amount])
SELECT NON EMPTY { ([Measures].[Avg]), ([Measures].[Amount])} ON COLUMNS,
NON EMPTY { ([Dim Date].[Day Of Week].[Day Of Week].ALLMEMBERS * [Dim Date].[Date Int].[Date Int].ALLMEMBERS )}
ON ROWS FROM ( SELECT ( { [Dim Client].[Common Client UID].&[{xx}] } )
ON COLUMNS FROM ( SELECT ( [Dim Date].[Date Int].&[20151115] : [Dim Date].[Date Int].&[20151215] )
ON COLUMNS FROM [ff]))
WHERE ( [Dim Client].[Common Client UID].&[{xx}] )
This query give me each weekday and all the dates which are bound to the specific weekday, and for each date there are an total amount.
like you can see my average is the same as total. I thought that i could just select all the weekdays between two date, find out for example how many mondays there are and divide it number with amount. but i couldnt figure that out either.
with member [Measures].[avg] as
avg([Dim Date].[Day Of Week].[Day Of Week], [Measures].[Amount])
SELECT NON EMPTY {[Measures].[avg], [Measures].[Amount]} ON COLUMNS, NON EMPTY { ([Dim Date].[Day Of Week].[Day Of Week].ALLMEMBERS )}
ON ROWS FROM ( SELECT ( { [Dim Client].[Common Client UID].&[{xx}] } )
ON COLUMNS FROM ( SELECT ( [Dim Date].[Date Int].&[20151115] : [Dim Date].[Date Int].&[20151215] )
ON COLUMNS FROM [ff]))
WHERE ( [Dim Client].[Common Client UID].&[{xx}] )
result :
How do i solve this?
What you really want is average amount over the dates corresponding to that weekday for the given date range.
You can just move the date range to the definition of calculated member, so that when value for a particular weekday is evaluated, the date range is also considered.
with member [Measures].[Avg] as
avg
(
[Dim Date].[Day Of Week].CURRENTMEMBER * {[Dim Date].[Date Int].&[20151115] : [Dim Date].[Date Int].&[20151215]}
,[Measures].[amount]
)
SELECT NON EMPTY { ([Measures].[Avg]), ([Measures].[Amount])} ON COLUMNS,
NON EMPTY [Dim Date].[Day Of Week].[Day Of Week].ALLMEMBERS * [Dim Date].[Date Int].[Date Int].ALLMEMBERS ON ROWS
FROM [ff]
WHERE ( [Dim Client].[Common Client UID].&[{xx}] )

How to use avg function in mdx?

I have my mdx query:
SELECT
NON EMPTY
{[Measures].[Amount]} ON COLUMNS
,NON EMPTY
{[Dim Date].[Day Of Week].[Day Of Week].MEMBERS} ON ROWS
FROM
(
SELECT
[Dim Date].[Date Int].&[20140730] : [Dim Date].[Date Int].&[20150730] ON COLUMNS
FROM [Cube]
WHERE
[Dim Client].[Common Client UID].&[{some id}]
);
so i have my a weekday dim, which contain members as numbers from 1-7. Query find returns amount for each weekday, which is summed up, but i want to find out an average, so somehow i need to find out how many items was summed to give me [Measures].[Amount] result. I have tryed with separate member function which didnt worked.
WITH MEMBER [Measures].[Avg] AS
Avg(
( [Dim Date].[Day Of Week].CURRENTMEMBER, [Measures].[Amount] )
)
Avg return exectly the same value. How do i do such a request in mdx?
This will give you an average over 1 member:
WITH MEMBER [Measures].[Avg] AS
Avg(
( [Dim Date].[Day Of Week].CURRENTMEMBER, [Measures].[Amount] )
)
That is because CURRENTMEMBER is returning 1 member.
If you want an average over several members than you need to supply a set as the first argument for the Avg function. Here is an example:
WITH MEMBER [Measures].[Daily Avg] AS
Avg(
Descedants(
[Date].[Date - Calendar Month].CURRENTMEMBER
,[Date].[Date - Calendar Month].[Calendar Day]
,[Measures].[Amount]
)
Although I suspect something like the follwoing should work in your context:
WITH
MEMBER [Measures].[Avg] AS
Avg
(
(EXISTING
[Dim Date].[Date Int].MEMBERS)
,[Measures].[Amount]
)
SELECT
NON EMPTY
{
[Measures].[Amount]
,[Measures].[Avg]
} ON COLUMNS
,NON EMPTY
{[Dim Date].[Day Of Week].[Day Of Week].MEMBERS} ON ROWS
FROM
(
SELECT
[Dim Date].[Date Int].&[20140730] : [Dim Date].[Date Int].&[20150730] ON COLUMNS
FROM [Cube]
WHERE
[Dim Client].[Common Client UID].&[{some id}]
);

Equivalent to max group by in mdx

How do I get the sales for the last product of a cross join of each product group and brand? I had a look at the Tail function but I can't get it to work properly.
This is the MDX I have so far:
SELECT {[Measures].[Sales Amount]} ON COLUMNS,
{
[Dim Brands].[Brand Name].[Brand Name].ALLMEMBERS *
[Dim Product Groups].[Product Group Name].[Product Group Name].ALLMEMBERS *
Tail ([Dim Products].[Product Name].[Product Name].ALLMEMBERS)
}
It only returns the last product for the whole cube rather than the last product for each brand and product group.
You can use the GENERATE function to generate the product for every combo or Brand and product group. The EXISTING takes care of the scope.
WITH SET LastProductForEachBrandAndProductGroup AS
GENERATE
(
EXISTING
NonEmpty
(
[Dim Brands].[Brand Name].[Brand Name].ALLMEMBERS, [Dim Product Groups].[Product Group Name].[Product Group Name].ALLMEMBERS
)
,
Tail (
[Dim Products].[Product Name].[Product Name].ALLMEMBERS
)
)
SELECT {[Measures].[Sales Amount]} ON COLUMNS,
NonEmpty({
[Dim Brands].[Brand Name].[Brand Name].ALLMEMBERS *
[Dim Product Groups].[Product Group Name].[Product Group Name].ALLMEMBERS *
LastProductForEachBrandAndProductGroup
}) ON ROWS
FROM YourCube
If by last, you imply the last member in any sorted(by some measure) list, the above would need some more work. Let me know how it works out for you.
Separating out the sets should speed this script up. Like the following:
WITH
SET [allBrands] AS
[Dim Brands].[Brand Name].[Brand Name].ALLMEMBERS
SET [allGroups] AS
[Dim Product Groups].[Product Group Name].[Product Group Name].ALLMEMBERS
SET [A] AS
Generate
(
{[allBrands] * [allGroups]} AS s
,
s.Current
*
Tail
(
NonEmpty
(
[Dim Products].[Product Name].[Product Name].ALLMEMBERS
,[Measures].[Sales Amount]
)
,1
)
)
SELECT
NON EMPTY
{[Measures].[Sales Amount]} ON 0
,NON EMPTY
[A] ON 1
FROM [YourCube];
Against Microsoft's AdvWrks a similar, and maybe more useful, variant of the above would be to use TopCount rather than the slightly artitrary Tail:
WITH
SET [allYears] AS
[Date].[Calendar].[Calendar Year].MEMBERS
SET [allCountries] AS
[Customer].[Customer Geography].[Country].MEMBERS
SET [A] AS
Generate
(
{[allYears] * [allCountries]} AS s
,
s.Current
*
TopCount
(
[Product].[Product Categories].[Product].ALLMEMBERS
,2
,[Measures].[Internet Sales Amount]
)
)
SELECT
{[Measures].[Internet Sales Amount]} ON 0
,[A] ON 1
FROM [Adventure Works];
This results in the following:

Filtering hierarchies in MDX WITH clause

By using the answer of MDX on multiple hierarchical dimensions I have the following MDX query:
with
member L1Y1 as ([Dim Location].[Hierarchy].[Center].&[1],
[Dim Date].[Calendar Date].[Year].&[2010],
[Measures].[x])
select ([Dim Attribute].[Parent Code].&[10000]) on 0,
({L1Y1}) on 1
from DS
Now the problem is how to filter the L1Y1 based on its children. For example suppose we want to filter it so that only season 2 and month 7 included in the query. The following query output is the same as above and the where clause has no effect:
with
member L1Y1 as ([Dim Location].[Hierarchy].[Center].&[1],
[Dim Date].[Calendar Date].[Year].&[2010],
[Measures].[x])
select ([Dim Attribute].[Parent Code].&[10000]) on 0,
({L1Y1}) on 1
from DS
where [Dim Date].[Calendar Date].[Season].&[2] and
[Dim Date].[Calendar Date].[Month].&[7]
Unless you removed the attributes you should have multiple hierarchies available in your time dimension. Can you try this :
with
member L1Y1 as ([Dim Location].[Hierarchy].[Center].&[1],
[Dim Date].[Calendar Date].[Year].&[2010],
[Measures].[x])
select ([Dim Attribute].[Parent Code].&[10000]) on 0,
({L1Y1}) on 1
from DS
where [Dim Date].[Season].&[2] and [Dim Date].[Month].&[7]
Note, as we are not using the hierarchy [Calendar Date] time member is not overwritten but filtered.
How about:
with
member L1Y1 as ([Dim Location].[Hierarchy].[Center].&[1],
[Dim Date].[Calendar Date].[Year].&[2010],
[Measures].[x])
member S2 as ([Dim Location].[Hierarchy].[Center].&[1],
[Dim Date].[Calendar Date].[Season].&[2],
[Measures].[x])
member M7 as ([Dim Location].[Hierarchy].[Center].&[1],
[Dim Date].[Calendar Date].[Month].&[7],
[Measures].[x])
select ([Dim Attribute].[Parent Code].&[10000]) on 0,
(L1Y1, S2, M7) on 1
from DS
I understand it is a somewhat more laborous approach, but you would get the result. If you otherwise want to get a single value only you can try keeping only the M7 measure.