I have an existing SSRS report out of T-SQL query and I am trying to recreate it using MDX queries on SSAS cube. I am stuck with rewriting Row num and rank logic to MDX.
It is written as:
SELECT ceil((ROW_NUMBER() OVER (PARTITION BY PRODUCT ORDER BY YEARMONTH))/12)
Rank1 in the SQL. Can someone tell me if this can be done using MDX? In the cube, PRODUCT and YEARMONTH are coming from separate dimensions.
Thank you for your help!
There is the Rank() function. For example:
with
Dynamic Set OrderedSet as
Order(
NonEmptyCrossJoin(
[Date].[Year].[Year].Members,
[Product].[Product].[Product].Members,
[Measures].[Invoice Count],
2
),
[Measures].[Invoice Count],
BDESC
)
Member [Measures].[Rank] as
Rank(
([Client].[Client].Currentmember,[Date].[Year].CurrentMember),
OrderedSet
)
select {[Measures].[Invoice Count],[Measures].[Rank]} on 0,
non empty OrderedSet on 1
from [BI Fake]
where ([Date].[Day].&[20160120])
You can read in details about it from my blog post.
You can use Generate to repeat ranks like this:
WITH
SET [SalesRank] AS
Generate
(
[Customer].[Customer Geography].[State-Province]
,Order
(
NonEmpty
(
(
[Customer].[Customer Geography].CurrentMember
,[Product].[Product Categories].[Subcategory]
)
,{[Measures].[Internet Sales Amount]}
)
,[Measures].[Internet Sales Amount]
,BDESC
)
)
MEMBER [Measures].[CategoryRank] AS
Rank
(
(
[Customer].[Customer Geography].CurrentMember
,[Product].[Product Categories].CurrentMember
)
,Exists
(
[SalesRank]
,[Product].[Product Categories].CurrentMember
)
)
SELECT
{
[Measures].[Internet Sales Amount]
,[Measures].[CategoryRank]
} ON 0
,[SalesRank] ON 1
FROM [Adventure Works];
It results in this:
Related
This is my sql code - I want to convert it into an mdx query.
Also those results set use into power bi report.
I am unable to write this sql query into mdx query. Can anybody help me?
Select * From(
Select
dense_RANK()over(partition by t.MonthName order by t.amount desc)rank
,t.*
From(
Select DSP.SalesPointShortName ,dd.MonthName
,SUM(fs.salesAmount)Amount
From FactSales FS
INNER JOIN DimDate dd on fs.DateKey=dd.DateKey
INNER JOIN DimSalesPoint DSP on DSP.SalesPointID=FS.SalesPointID
group by dsp.SalesPointShortName ,dd.MonthName
)as t
)as f where f.rank=1
My expected output is:
Lots of google searching i have the answer from below link
After following this link desire results comes.
https://bipassion.wordpress.com/2013/06/22/mdx-dense-rank/
`
WITH SET [Sorted Models] AS
// For each month get Top 25 records, choosed a Top 25 from business case
Generate (
[Dim Date].[Month Name].[Month Name].Members,
TopCount
( nonempty(
{
[Dim Date].[Month Name].CurrentMember
* [Dim Sales Point].[SalesPoint].[Sales Point Short Name].MEMBERS
}
,[Measures].[Sales Amount]
)
, 1
,[Measures].[Sales Amount]
)
)
//Select
//[Measures].[Sales Amount] on 0,[Sorted Models] on 1
//From [MdCubeTest]
//where [Dim Product Group Item].[Sub Category Name].&[Bag]
MEMBER [Measures].[Rank] AS
// Get the Rank of current member Tuple to Current Month Set
// Do not use Sorted Models set due to dynamic for each set
Rank (
(
[Dim Date].[Month Name].CurrentMember
, [Dim Sales Point].[SalesPoint].CurrentMember
)
, Generate ( [Dim Date].[Month Name].CurrentMember,
TopCount
( nonempty(
{ [Dim Date].[Month Name].CurrentMember
* [Dim Sales Point].[SalesPoint].[Sales Point Short Name]
}
,[Measures].[Sales Amount]
)
, 25
,[Measures].[Sales Amount]
)
)
, [Measures].[Sales Amount]
)
MEMBER [Measures].[Previous Set Index] AS
// Get the Set Index using Rank
(
[Measures].[Rank] - 2
)
MEMBER [Measures].[Dense Rank] AS
// Get the Dense Rank using the Index position value
CASE
WHEN [Measures].[Rank] = 1
THEN 1
ELSE
(
[Sorted Models].Item([Measures].[Previous Set Index]),
[Measures].[Dense Rank]
)
+
Iif
(
(
[Sorted Models].Item([Measures].[Previous Set Index]),
[Measures].[Sales Amount]
)
=
[Measures].[Sales Amount]
,0
,1
)
End
SELECT
{
[Measures].[Sales Amount]
, [Measures].[Rank]
, [Measures].[Dense Rank]
//, [Measures].[Previous Set Index]
} ON rows
,NON EMPTY
{
// FILTER Can be used to get the TOP 3 using DENSE RANK
FILTER (
[Sorted Models]
, [Measures].[Dense Rank] <=1
)
} ON columns
FROM [MdCubeTest]
where [Dim Product Group Item].[Sub Category Name].&[Bag]`
I have a measure, Sales Amount. I want to rank customers within a divison by year for that measure. I need to also display that rank as a measure. The rank needs to start over every year. I am able to do customers by year and customers by division, but I can't seem to figure out how to combine them both so it iterates over both dimensions properly. Below is what I have for the customers by year. I have tried adding another Division set, creating another named set that I GENERATE with the YearsWithCustomers set, and RANK using that new named set. I seem to be super close to figuring this out but I think I am putting something in the wrong place. I got the idea to iterate over a set from one of Chris Webb's blogs, located here.
WITH
SET Years AS
TopPercent
(
[Sales and Forecast Date].[Calendar Year].[Year Number].MEMBERS
,100
,[Measures].[Sales Amount]
)
SET Customers AS
Filter
(
[Customer].[Customer Number].[Customer Number].MEMBERS
,
[Measures].[Sales Amount] > 0
)
SET YearsWithCustomers AS
Generate
(
Years
,Union
(
{[Sales and Forecast Date].[Calendar Year].CurrentMember}
,StrToSet
("
Intersect({},
{order(Customers,([Sales Amount],[Sales and Forecast Date].[Calendar Year].CurrentMember),desc)
as CustomerSet"
+
Cstr(Years.CurrentOrdinal)
+ "})"
)
)
,ALL
)
MEMBER [Measures].[Customer Rank] AS
Rank
(
[Customer].[Customer Number].CurrentMember
,StrToSet
("CustomerSet"
+
Cstr
(
Rank
(
[Sales and Forecast Date].[Calendar Year].CurrentMember
,Years
)
)
)
)
SELECT
{
[Customer Rank]
,[Measures].[Sales Amount]
} ON 0
,Order
(
Filter
(
(
YearsWithCustomers
,Customers
)
,
[Sales Amount] > 0
)
,[Sales Amount]
,desc
) ON 1
FROM [OrdersAndBudgets];
Here is what I currently have. I would expect to see 1, 2, 3, etc for the Rank measure. It should reset for each division for every year.
I like this sort of pattern:
WITH
SET [AllCountries] AS
[Country].[Country].MEMBERS
SET [AllProds] AS
[Product].[Product].[Product].MEMBERS
SET [Top5Prods] AS
Generate
(
[AllCountries] AS a
,{
(
a.CurrentMember
,[Product].[Product].[All]
)
+
//The top x prods
a.CurrentMember
*
TopCount
(
[AllProds]
,5
,[Measures].[Internet Sales Amount]
)
}
)
MEMBER [Product].[Product].[All].[Other Products] AS
Aggregate
(
[Country].CurrentMember * [Product].[Product].[Product].MEMBERS
-
[Top5Prods]
)
SELECT
{[Measures].[Internet Sales Amount]} ON COLUMNS
,Hierarchize(
{
[Top5Prods]
,[AllCountries] * [Product].[Product].[All].[Other Products]
}
) ON ROWS
FROM [Adventure Works];
It returns the following:
There is quite an extensive thread here: Top X of Top Y with RestOf member where X and Y are hierarchies from different dimensions
I hope you can help i have tried so many way to try get this right with no luck. I am trying to get out the player account number the date and start and end date time and maybe calculate the play duration between the start and end times.
I would like the output to look something like this.
PlayerAccount | GamingDate | StartTime | EndTime | PlayDuration | ActualWin
I always seem to return the start and end time for the whole day and not Per account.
WITH
SET [MySet] AS
[Customer].[Player Account Number].Children*
Head
(
NonEmpty
(
[Start Time].[Hour].Children
,[Measures].[Actual Win]
)
,1
)*
Tail
(
NonEmpty
(
[End Time].[Hour].Children
,[Measures].[Actual Win]
)
,1
)
SELECT
{[Measures].[Actual Win]} ON 0
,{[MySet]} ON 1
FROM
(
SELECT
[Customer].[Player Account Number].&[1040002184]
:
[Customer].[Player Account Number].&[1040002198] ON 0
FROM Ratings
)
WHERE
{[Gaming Date].[Full Date].&[20150101]};
Usually first date and last date would be measures. If you only want results for those two player then use the WHERE clause rather than a sub-select:
WITH
MEMBER [Measures].[fDate] AS
Head
(
NonEmpty
(
[Start Time].[Hour].MEMBERS
,[Measures].[Actual Win]
)
).Item(0).Item(0).Member_Caption
MEMBER [Measures].[lDate] AS
Tail
(
NonEmpty
(
[End Time].[Hour].MEMBERS
,[Measures].[Actual Win]
)
).Item(0).Item(0).Member_Caption
SELECT
{
[Measures].[fDate]
,[Measures].[lDate]
,[Measures].[Actual Win]
} ON 0
,{[Customer].[Player Account Number].Children} ON 1
FROM Ratings
WHERE
([Gaming Date].[Full Date].&[20150101],
{ [Customer].[Player Account Number].&[1040002184]
,[Customer].[Player Account Number].&[1040002198]});
This is a working AdvWrks script which does the sort of thing you're trying to achieve:
WITH
MEMBER [Measures].[firstDate] AS
Head
(
NonEmpty
(
[Date].[Date].[Date].MEMBERS
,[Measures].[Internet Sales Amount]
)
).Item(0).Item(0).Member_Caption
MEMBER [Measures].[lastDate] AS
Tail
(
NonEmpty
(
[Date].[Date].[Date].MEMBERS
,[Measures].[Internet Sales Amount]
)
).Item(0).Item(0).Member_Caption
SELECT
{
[Measures].[Internet Sales Amount]
,[Measures].[firstDate]
,[Measures].[lastDate]
} ON 0
,NON EMPTY {[Promotion].[Promotion].MEMBERS} ON 1
FROM [Adventure Works];
If you'd rather pull back a member instead of using Measures I think the GENERATE function will work like this AdvWrks example:
WITH
SET [aSet] AS
Generate
(
[Promotion].[Promotion].MEMBERS
,
[Promotion].[Promotion].CurrentMember
*
Head
(
NonEmpty
(
[Date].[Date].[Date].MEMBERS
,[Measures].[Internet Sales Amount]
)
)
)
SELECT
{[Measures].[Internet Sales Amount]} ON 0
,NON EMPTY
{[aSet]} ON 1
FROM [Adventure Works];
Thanks a million whytheq it seems like that .Item(0).Item(0).Member_Caption made all the difference the results seem to be come out right now. The final solution looks like this now..
with set [MySet]
as
[Casino Hierarchy].[Casino Key].children *
[Gaming Date].[Date].currentmember *
[Customer].[Player Account Number].children *
[Start Time].[Hour].children
member [measures].[Mindate]
as
HEAD(nonempty([End Time].[Hour].children,[Measures].[Actual Win]),1).Item(0).Item(0).Member_Caption
member [measures].[Maxdate]
as
TAIL(nonempty([Start Time].[Hour].children,[Measures].[Actual Win]),1).Item(0).Item(0).Member_Caption
select non empty
{
[Measures].[Actual Win],[measures].[Mindate],[measures].[Maxdate]
} on 0, non empty
{
[Customer].[Player Account Number].children
} on 1
from
(
select ([Customer].[Player Account Number].&[1040002184]:[Customer].[Player Account Number].&[1040002198]) on 0 from Ratings
)
where
{
[Gaming Date].[Full Date].&[20150101]
}
You are having a static set(which always return the same members). Use the EXISTING keyword to evaluate the Start and end time in the current context.
with member [Measures].[StartDate] as
EXISTING
HEAD(
nonempty(
[Start Time].[Hour].Children,[Measures].[Actual Win]
) ,1
).ITEM(0).ITEM(0).MEMBERVALUE
member [Measures].[EndDate] as
EXISTING
TAIL(
nonempty(
[End Time].[Hour].children,[Measures].[Actual Win]
) ,1
).ITEM(0).ITEM(0).MEMBERVALUE
select
{
[Measures].[Actual Win],
[Measures].[StartDate],
[Measures].[EndDate]
} on 0,
[Customer].[Player Account Number].children on 1
from
(
select (
[Customer].[Player Account Number].&[1040002184]:
[Customer].[Player Account Number].&[1040002198]
) on 0
from Ratings
)
where {[Gaming Date].[Full Date].&[20150101]}
Have rearranged the [Customer].[Player Account Number] so that dates are evaluated in the context of each player account. Hope that's fine!
Edit.
A simplified AdWrks version of this approach is as follows: still not quite working:
WITH
SET [aSet] AS
(EXISTING
Head
(
NonEmpty
(
[Date].[Date].[Date].MEMBERS
,[Measures].[Internet Sales Amount]
)
))
SELECT
{[Measures].[Internet Sales Amount]} ON 0
,NON EMPTY
{[Promotion].[Promotion].MEMBERS * [aSet]} ON 1
FROM [Adventure Works];
Let me describe the issue giving example from [Adventure Works] cube.
Following MDX returns count of 17473
SELECT NON EMPTY { [Measures].[Internet Order Count] } ON COLUMNS,
[Internet Sales Order Details].[Sales Order Number] on ROWS
FROM [Adventure Works])
WHERE ( [Sales Reason].[Sales Reason].&[1] -- price
and following returns count of 3515
SELECT NON EMPTY { [Measures].[Internet Order Count] } ON COLUMNS ,
[Internet Sales Order Details].[Sales Order Number] on ROWS
FROM [Adventure Works]
WHERE ( [Sales Reason].[Sales Reason].&[2]) -- on promotion
I would like to count [Sales Order Number] which are common in [Sales Reason].&1 and [Sales Reason].&[2]
SQL equivalent would be:
select count(distinct f.SalesOrderNumber)
from FactInternetSales f
join FactInternetSalesReason fs
on f.SalesOrderNumber = fs.SalesOrderNumber and f.SalesOrderLineNumber = fs.SalesOrderLineNumber
where fs.SalesReasonKey = 1 and fs.SalesOrderNumber in
(select SalesOrderNumber from FactInternetSalesReason fs1 where fs1.SalesReasonKey = 2)
-- sales reason 1 = 17473
-- sales reason 2 = 3515
-- common 1689
I got common count using following mdx:
WITH MEMBER [Measures].[common] AS count
( exists ( exists ([Internet Sales Order Details].[Sales Order Number].[Sales Order Number].Members,
[Sales Reason].[Sales Reason].&[1],"Internet Orders"
),
[Sales Reason].[Sales Reason].&[2],"Internet Orders"
)
)
SELECT NON EMPTY [Measures].[common] ON COLUMNS
FROM [Adventure Works]
-- 1689
But use of EXISTS is rather slow for my requirement. Please suggest an alternative.
Also please see related thread here
Thank you
Please try adding both reasons to the WHERE clause as a single set:
SELECT
NON EMPTY
{[Measures].[Internet Order Count]} ON COLUMNS
,[Internet Sales Order Details].[Sales Order Number] ON ROWS
FROM [Adventure Works]
WHERE
{
[Sales Reason].[Sales Reason].&[2]
,[Sales Reason].[Sales Reason].&[1]
};
Here is an alternative that runs faster, only looks at the common orders, and does not use the EXISTS function:
WITH
SET [AllOrders] AS
[Internet Sales Order Details].[Sales Order Number].[Sales Order Number].MEMBERS
SET [OrdersIntersection] AS
Intersect
(
NonEmpty
(
[AllOrders]
,{
(
[Sales Reason].[Sales Reason].&[1]
,[Measures].[Internet Order Count]
)
}
)
,NonEmpty
(
[AllOrders]
,{
(
[Sales Reason].[Sales Reason].&[2]
,[Measures].[Internet Order Count]
)
}
)
)
MEMBER [Measures].[commonCount] AS
[OrdersIntersection].Count
SELECT
//NON EMPTY //<<not needed
[Measures].[commonCount] ON COLUMNS
FROM [Adventure Works];
This article gives a method for ignoring null when applying RANK : http://www.bidn.com/blogs/CraigLove/ssas/2617/mdx-walkthrough
Is there a more elegant way of doing this than using CASE ?
WITH
MEMBER [Measures].[City Rank]
AS
CASE
WHEN
NOT ISEMPTY
(
(
[Geography].[City].CurrentMember
,[Measures].[Reseller Sales Amount]
)
)
THEN
RANK
(
[Geography].[City].CurrentMember
,[Geography].[City].AllMembers
,[Measures].[Reseller Sales Amount]
)
ELSE
NULL
END
SET [OrderedCity]
AS
ORDER
(
[Geography].[City].AllMembers
,[Measures].[Reseller Sales Amount]
,DESC
)
SELECT
{
[Measures].[City Rank]
,[Measures].[Reseller Sales Amount]
} ON COLUMNS
,NON EMPTY [OrderedCity] ON ROWS
FROM [Adventure Works];
You could apply the NonEmpty function within OrderedCity:
WITH
SET [OrderedCity]
AS
NonEmpty(
ORDER
(
[Geography].[City].AllMembers
,[Measures].[Reseller Sales Amount]
,DESC
)
)
MEMBER [Measures].[City Rank]
AS
RANK
(
[Geography].[City].CurrentMember
,[OrderedCity]
)
SELECT
{
[Measures].[City Rank]
,[Measures].[Reseller Sales Amount]
} ON COLUMNS
,[OrderedCity]
ON ROWS
FROM [Adventure Works];
This way the NON EMPTY is applied on the set on the rows before it it put there, so there is no need to apply it on the rows themselves. Furthermore, re-using the set within the definition of City Rank allows Analysis Services to calculate the sorting only once, and cache the ordered set. Otherwise, the Rank would require the ordering to be re- calculated for each row.