I've got requirement to display top 5 customer sales by customer group, but with other customers sales within the group aggregated as 'Others'. Something similar to this question, but counted separately for each of customer groups.
According to MSDN to perform TopCount, over a set of members you have to use Generate function.
This part works ok:
with
set [Top5CustomerByGroup] AS
GENERATE
(
[Klient].[Grupa Klientow].[Grupa Klientow].ALLMEMBERS,
TOPCOUNT
(
[Klient].[Grupa Klientow].CURRENTMEMBER * [Klient].[Klient].[Klient].MEMBERS
, 5
, [Measures].[Przychody ze sprzedazy rzeczywiste wartosc]
)
)
SELECT
{ [Measures].[Przychody ze sprzedazy rzeczywiste wartosc]} ON COLUMNS,
{
[Klient].[Grupa Klientow].[Grupa Klientow].ALLMEMBERS * [Klient].[Klient].[All], --for drilldown purposes
[Top5CustomerByGroup]
}
ON ROWS
FROM
(
SELECT ({[Data].[Rok].&[2013]} ) ON COLUMNS
FROM [MyCube]
)
however I've got problem with 'Others' part.
I think I was able to construct set with other customers by group (data looks good) as:
set [OtherCustomersByGroup] AS
GENERATE
(
[Klient].[Grupa Klientow].[Grupa Klientow].ALLMEMBERS,
except
(
{[Klient].[Grupa Klientow].CURRENTMEMBER * [Klient].[Klient].[Klient].MEMBERS},
TOPCOUNT
(
[Klient].[Grupa Klientow].CURRENTMEMBER * [Klient].[Klient].[Klient].MEMBERS
, 5
, [Measures].[Przychody ze sprzedazy rzeczywiste wartosc]
)
)
)
however I don't have idea how to aggregate it with grouping.
Doing this as in this question
member [Klient].[Klient].[tmp] as
aggregate([OtherCustomersByGroup])
produces one value, which is logical.
I think I need list of sets with 'other' customers in each group instead of single [OtherCustomersByGroup]
set, but don't have idea how to construct them.
Does anyone have any ideas or suggestions?
UPDATE:
There is some misunderstanding of my needs. I need Top n customers within each of customer group by sales with sales of other customers in this group aggregated to one position (let's say called Others).
For example for this simplified input:
| Group | Client | Sales |
|--------|----------|--------|
| Group1 | Client1 | 300 |
| Group1 | Client2 | 5 |
| Group1 | Client3 | 400 |
| Group1 | Client4 | 150 |
| Group1 | Client5 | 651 |
| Group1 | Client6 | null |
| Group2 | Client7 | 11 |
| Group2 | Client8 | 52 |
| Group2 | Client9 | 44 |
| Group2 | Client10 | 21 |
| Group2 | Client11 | 201 |
| Group2 | Client12 | 325 |
| Group2 | Client13 | 251 |
| Group3 | Client14 | 15 |
I need such output (here is top 2):
| Group | Client | Sales |
|--------|----------|--------|
| Group1 | Client5 | 651 |
| Group1 | Client3 | 400 |
| Group1 | Others | 455 |
| Group2 | Client12 | 325 |
| Group2 | Client13 | 251 |
| Group2 | Others | 329 |
| Group3 | Client14 | 15 |
| Group3 | Others | null | <- optional row
Sorting isn't required, we are going to process it by client side.
The following is against AdvWrks
and uses a technique I saw on Chris Webb's blog which he outlines here:
https://cwebbbi.wordpress.com/2007/06/25/advanced-ranking-and-dynamically-generated-named-sets-in-mdx/
The section of the script that creates the set MyMonthsWithEmployeesSets I find very difficult to get my head around - maybe @AlexPeshik could shed a little more light on what is happening in the following script.
WITH
SET MyMonths AS
TopPercent
(
[Date].[Calendar].[Month].MEMBERS
,20
,[Measures].[Reseller Sales Amount]
)
SET MyEmployees AS
[Employee].[Employee].[Employee].MEMBERS
SET MyMonthsWithEmployeesSets AS
Generate
(
MyMonths
,Union
(
{[Date].[Calendar].CurrentMember}
,StrToSet
("
Intersect({},
{TopCount(MyEmployees, 10, ([Measures].[Reseller Sales Amount],[Date].[Calendar].CurrentMember))
as EmployeeSet"
+
Cstr(MyMonths.CurrentOrdinal)
+ "})"
)
)
)
MEMBER [Employee].[Employee].[RestOfEmployees] AS
Aggregate
(
Except
(
MyEmployees
,StrToSet
(
"EmployeeSet" + Cstr(Rank([Date].[Calendar].CurrentMember,MyMonths))
)
)
)
MEMBER [Measures].[EmployeeRank] AS
Rank
(
[Employee].[Employee].CurrentMember
,StrToSet
(
"EmployeeSet" + Cstr(Rank([Date].[Calendar].CurrentMember,MyMonths))
)
)
SELECT
{
[Measures].[EmployeeRank]
,[Measures].[Reseller Sales Amount]
} ON 0
,Generate
(
Hierarchize(MyMonthsWithEmployeesSets)
,
[Date].[Calendar].CurrentMember
*
{
Order
(
Filter
(
MyEmployees
,
[Measures].[EmployeeRank] > 0
)
,[Measures].[Reseller Sales Amount]
,BDESC
)
,[Employee].[Employee].[RestOfEmployees]
}
) ON 1
FROM [Adventure Works];
Edit - solution for Alex's third attempt:
WITH
SET [AllCountries] AS [Country].[Country].MEMBERS
SET [AllStates] AS [State-Province].[State-Province].MEMBERS
SET [Top2States] AS
Generate
(
[AllCountries]
,TopCount
(
(EXISTING
[AllStates])
,3
,[Measures].[Internet Order Count]
)
)
MEMBER [State-Province].[All].[RestOfCountry] AS
Aggregate({(EXISTING {[AllStates]} - [Top2States])})
SELECT
{[Measures].[Internet Order Count]} ON COLUMNS
,{
[AllCountries]
*
{
[Top2States]
,[State-Province].[All].[RestOfCountry]
,[State-Province].[All]
}
} ON ROWS
FROM [Adventure Works];
Yes, you've got the main idea by using SET for Others, but several minor additions are required to complete the task.
I'll use my test DBs, but this can easily be transformed to yours.
[Report Date]
- date dimension ([Klient]
analogue)
[REPORT DATE Y]
- years hierarchy ([Grupa Klientow]
)
[REPORT DATE YM]
- months hierarchy ([Klient].[Klient]
)
[Measures].[Count]
- measure for TopCount ([Measures].[Przychody ze sprzedazy rzeczywiste wartosc]
)
I also used top 3 just to show result image here.
And here's the code:
with
/* first, add empty [Other] member to the group level */
member [Report Date].[REPORT DATE Y].[Other] as null
/* second, copy measure by fixing the lowest level */
member [Measures].[Count with Other Groups] as ([Report Date].[REPORT DATE YM],[Measures].[Count])
/* third, create top 10 by group */
set [Report Date Top 10 Groups] as
Generate([Report Date].[REPORT DATE Y].Children
,TopCount([Report Date].[REPORT DATE Y].CurrentMember
* [Report Date].[REPORT DATE YM].Children,3,[Measures].[Count with Other Groups]))
/* this is the part for Other group mapping */
set [Report Date Other Groups] as
[Report Date].[REPORT DATE Y].[Other]
* ([Report Date].[REPORT DATE YM].Children
- Extract([Report Date Top 10 Groups],[Report Date].[REPORT DATE YM]))
select {[Measures].[Count],[Measures].[Count with Other Groups]} on 0
,
{
[Report Date Top 10 Groups],[Report Date Other Groups]}
on 1
from
[DATA]
And here is the result:
..all members till the last one (which is 201606) are on the Other
group.
Hope this helps, bardzo dziękuję!
Update: code was optimized by removing one multiplying in Report Date Other Groups
calculation.
Update-2: (not solved yet, but in progress)
(use 'Other' member under each group)
IMPORTANT! We need additional hierarchy: Group->Client
([Report Date].[REPORT DATE]
with Year->Month
is my case) to be able to determine parent for each low level member.
with
/* create top 10 by group */
set [Report Date Top 10 Groups] as
Generate([Report Date].[REPORT DATE Y].Children
,TopCount([Report Date].[REPORT DATE Y].CurrentMember
* [Report Date].[REPORT DATE].Children,3,[Measures].[Count]))
/* this is the part for Other group the lowest level non-aggregated members */
set [Report Date Other Members] as
[Report Date].[REPORT DATE Y].Children
* ([Report Date].[REPORT DATE].[Month].AllMembers
- [Report Date].[REPORT DATE].[All])
- [Report Date Top 10 Groups]
/* add empty [Other] member to the group level, HERE IS AN ISSUE */
member [Report Date].[REPORT DATE].[All].[Other] as null
set [Report Date Other Groups] as
[Report Date].[REPORT DATE Y].[All].Children
* [Report Date].[REPORT DATE].[Other]
member [Measures].[Sum of Top] as
IIF([Report Date].[Report Date].CurrentMember is [Report Date].[REPORT DATE].[Other]
,null /* HERE SHOULD BE CALCULATION, but only
{[Report Date].[Report Date Y].[All].[Other]}
is shown, because 'Other' is added to the entire hierarchy */
,SUM([Report Date].[REPORT DATE Y].CurrentMember
* ([Report Date].[Report Date].CurrentMember.Parent.Children
- Extract([Report Date Other Members],[Report Date].[REPORT DATE]))
,[Measures].[Count]))
member [Measures].[Sum of Group] as
([Report Date].[Report Date].CurrentMember.Parent,[Measures].[Count])
select {[Measures].[Count],[Measures].[Sum of Group],[Measures].[Sum of Top]} on 0
,
Order(Hierarchize({[Report Date Top 10 Groups]
,[Report Date Other Groups]}),[Measures].[Count],DESC)
on 1
from
[DATA]
And here is the intermediate result:
I need to move this result here, but have no idea how to do it.
I also tried using flat hierarchies of each level. Other
member is shown correctly, but not able to calculate SUM, because both levels are independent. Maybe we can add a property like 'Group_Name' and use unlinked levels, but again - it decreases performance drastically. All this IIF([bla-bla-bla low level member].Properties("Group_Name")=[bla-bla-bla group level].Member_Name
are extremely slow.
Update-3 (AdvWorks version of code above)
with
/* create top 10 by group */
set [Top 10 Groups] as
Generate([Customer].[Country].Children
,TopCount([Customer].[Country].CurrentMember
* [Customer].[Customer Geography].Children,3,[Measures].[Internet Order Count]))
/* this is the part for Other group the lowest level non-aggregated members */
set [Other Members] as
[Customer].[Country].Children
* ([Customer].[Customer Geography].[State-Province].AllMembers
- [Customer].[Customer Geography].[All])
- [Top 10 Groups]
/* add empty [Other] member to the group level */
member [Customer].[Customer Geography].[All].[Other] as
([Customer].[Country],[Measures].[Internet Order Count])
set [Other Groups] as
[Customer].[Country].[All].Children
* [Customer].[Customer Geography].[Other]
member [Measures].[Sum of Top] as
IIF([Customer].[Customer Geography].CurrentMember is [Customer].[Customer Geography].[Other]
,null
,SUM([Customer].[Country].CurrentMember
* ([Customer].[Customer Geography].CurrentMember.Parent.Children
- Extract([Other Members],[Customer].[Customer Geography]))
,[Measures].[Internet Order Count]))
member [Measures].[Sum of Group] as
([Customer].[Customer Geography].CurrentMember.Parent,[Measures].[Internet Order Count])
select {[Measures].[Internet Order Count],[Measures].[Sum of Group],[Measures].[Sum of Top]} on 0
,
Order(Hierarchize({[Top 10 Groups],[Other Groups]}),[Measures].[Internet Order Count],DESC) on 1
from [Adventure Works]
Update-4 (with a solution in year/month example)
Amazing solution of @whytheq helped to do what I want:
WITH
SET [All Grupa Klientow] AS ([Report Date].[Report Date Y].Children)
SET [All Klient] AS ([Report Date].[Report Date YM].Children)
SET [Top N Members] AS
Generate
(
[All Grupa Klientow]
,TopCount
(
(EXISTING
[All Klient])
,3
,[Measures].[Count]
)
)
MEMBER [Report Date].[Report Date YM].[Other] AS
Aggregate({(EXISTING {[All Klient]} - [Top N Members])})
SELECT
{[Measures].[Count]} ON 0
,{
[All Grupa Klientow]
*
{
[Top N Members]
,[Report Date].[Report Date YM].[Other]
}
} ON 1
FROM [DATA];
And the image:
Task is solved, but please mark not this answer, but @whytheq's!