1
votes

This is from a heavily manually maintained report that I am trying to automate a bit view an SSAS cube.

The report contains daily sales, and, among other things, a measure called "last 4 's". E.g., For Friday, October 16 the measure was the average sales over the last 4 Fridays.

Is there a way to construct this in MDX in way that can be placed in a calculated measure in an SSAS cube?


ps--In response to whytheq's question, yes, the date dimension includes day of week, which is an integer in which Sun = 1, Mon = 2 and so on to Sat = 7.


I see that I was slightly ambiguous above. By "last 4 Fridays" above I meant the 4 Fridays immediately preceding October 16, not the most recent 4 Fridays.

2
depends - we really need to see the structure of your Date dimension to help in a constructive manner. Do you have an attribute hierarchy "Day of Week"? (it would just have 7 members - "Mon", "Tues", ...whytheq
Yes we do. If we didn't but it was worth it we would put it in.Scott Wood

2 Answers

3
votes

Agreed with whytheq, that more information may help us to create optimal solution. Anyway:

Solved if you have only flat days hierarchy:

+All
-2015/01/01
-2015/01/02
...
-2015/12/31
...

Logic could be like this:

  1. rank all days

  2. split by weeks

  3. calculate last 4 for every day type

  4. show result for every selected member

Example of flat hierarchy [Report Date].[Report Date].[Day] calculation:

with
member [Measures].[AllDaysRank] as Rank([Report Date].[Report Date].CurrentMember,[Report Date].[Report Date].[Day].Members)

member [Measures].[WeekDay] as ([Measures].[AllDaysRank]-(Int([Measures].[AllDaysRank]/7)*7))

set [Last4Set0] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=0),4,[Measures].[AllDaysRank])
set [Last4Set1] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=1),4,[Measures].[AllDaysRank])
set [Last4Set2] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=2),4,[Measures].[AllDaysRank])
set [Last4Set3] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=3),4,[Measures].[AllDaysRank])
set [Last4Set4] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=4),4,[Measures].[AllDaysRank])
set [Last4Set5] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=5),4,[Measures].[AllDaysRank])
set [Last4Set6] as TopCount(Filter([Report Date].[Report Date].[Day].Members,[Measures].[WeekDay]=6),4,[Measures].[AllDaysRank])

member [Measures].[Last4Measure] as
case [Measures].[WeekDay]
    when 0 then sum([Last4Set0],[Measures].[Count])
    when 1 then sum([Last4Set1],[Measures].[Count])
    when 2 then sum([Last4Set2],[Measures].[Count])
    when 3 then sum([Last4Set3],[Measures].[Count])
    when 4 then sum([Last4Set4],[Measures].[Count])
    when 5 then sum([Last4Set5],[Measures].[Count])
    when 6 then sum([Last4Set6],[Measures].[Count])
end

select {[Measures].[Count],[Measures].[AllDaysRank],[Measures].[WeekDay],[Measures].[Last4Measure]} on 0
,[Report Date].[Report Date].[Day].Members on 1
from [DATA]

Result (Count, AllDaysRank, WeekDay, Last4Measure):

20151001    10  740 5   35
20151002    10  741 6   39
20151003    8   742 0   37
20151004    12  743 1   42
20151005    13  744 2   42
20151006    12  745 3   39
20151007    10  746 4   36
20151008    8   747 5   35
20151009    6   748 6   39
20151010    11  749 0   37
20151011    10  750 1   42
20151012    7   751 2   42
20151013    8   752 3   39
20151014    6   753 4   36
20151015    9   754 5   35
20151016    11  755 6   39
20151017    11  756 0   37
20151018    10  757 1   42
20151019    14  758 2   42
20151020    8   759 3   39
20151021    11  760 4   36
20151022    4   761 5   35
20151023    16  762 6   39
20151024    5   763 0   37
20151025    10  764 1   42
20151026    8   765 2   42
20151027    11  766 3   39
20151028    9   767 4   36
20151029    14  768 5   35
20151030    6   769 6   39
20151031    10  770 0   37

If you have week hierarchy or some properties (not to calculate day numbers), it would be easier.

UPDATE (weekly attribute is present):

Here is script for weeks, but please create weekday->day hierarchy first, e.g:

All
+1
-2015/01/01
-2015/01/08
...
+2
-2015/01/02
-2015/01/09
...

Code has code tricks I'll explain further:

with
member [Measures].[Week Day INFO] as [Report Date].[Week Day].Properties( "Report Date Week Day" )

member [Measures].[Last4Measure] as
/* if there are no empty 4 weeks for the first dates with data, take smaller size */
 iif([Report Date].[Week Day].CurrentMember.Lag(3).Parent.Member_Key <> [Report Date].[Week Day].CurrentMember.Parent.Member_Key
,iif([Report Date].[Week Day].CurrentMember.Lag(2).Parent.Member_Key <> [Report Date].[Week Day].CurrentMember.Parent.Member_Key
,iif([Report Date].[Week Day].CurrentMember.Lag(1).Parent.Member_Key <> [Report Date].[Week Day].CurrentMember.Parent.Member_Key
,sum({[Report Date].[Week Day].CurrentMember},[Measures].[Count])
,sum({[Report Date].[Week Day].CurrentMember.Lag(1):[Report Date].[Week Day].CurrentMember},[Measures].[Count]))
,sum({[Report Date].[Week Day].CurrentMember.Lag(2):[Report Date].[Week Day].CurrentMember},[Measures].[Count]))
/* end of fixing, which could be necessary */

/* calculation part */
,sum({[Report Date].[Week Day].CurrentMember.Lag(3):[Report Date].[Week Day].CurrentMember},[Measures].[Count]))

select
{[Measures].[Count],[Measures].[Week Day INFO],[Measures].[Last4Measure]} on 0
,[Report Date].[Report Date].[Day].members on 1
from [DATA]

Week Day INFO measure is for result viewing only, not necessary in calculations.

Exact code to calculate is really simple: sum({[Report Date].[Week Day].CurrentMember.Lag(3):[Report Date].[Week Day].CurrentMember},[Measures].[Count]))

But it's possible, that you don't have empty or senseless days, which is necessary for this calculation! Since it uses .lag(3), and once we try to calculate for the VERY first, second or third weeks, it will take last members from the previous weekday, e.g. to calculate .lag(3) for the Second Wednesday ever (at the beginning of your Date dimension), it will take 2nd Wed, 1st Wed, Last Tue, Pre-last Tue, which is unacceptable, so I've added decreasing lag-level by checking Parent name (since Parent is WeekDay number from the hierarchy we've already created).

Sure thing, you'll use AVG instead of SUM. I used SUM to simplify checking an answer. It's dynamical, not for the very last member only. It depends on current member. LastNMembers

0
votes

If you have a hierarchy within the date dimension that indicates Day of the Week then you should use it in the sort of circumstance of this question.

Via AdvWrks I wrote the following:

WITH 
  MEMBER [Measures].[Avg4wkDays] AS 
    Sum
    (
      Tail
      ( --<<find just the past 4 
        Exists
        ( --<< find all the days from the past 25 days which have the same day of the week as the current date
          Tail
          ( --<< find the last 25 days prior to each date
            NULL : [Date].[Calendar].CurrentMember
           ,25
          )
         ,Exists
          ( --<< find the Day of the Week for the currentmember
            [Date].[Day of Week].[Day of Week].MEMBERS
           ,[Date].[Calendar].CurrentMember
          )
        )
       ,4
      )
     ,[Measures].[Internet Sales Amount]
    ) 
SELECT 
  {
    [Measures].[Internet Sales Amount]
   ,[Measures].[Avg4wkDays]
  } ON 0
 ,
  [Date].[Calendar].[Date] * [Date].[Day of Week].[Day of Week] ON 1
FROM [Adventure Works]
WHERE 
  [Date].[Calendar Year].&[2007];

It gives the following which agrees with the requirements:

enter image description here