Search code examples
ssasmdxolapcube

PY YTD calculated measure that can handle leap years MDX


This might be a longshot, but maybe there is an ssas/mdx guru here that has stumpled upon the same problem regarding aggregated calculations and leap years. So what I am trying to do, is to create a PY YTD calculated measure in an ssas cube that can handle leap years. The issue I am facing is that the calculated measure becomes extremely slow based on the logic below (see code sample). Has anyone found a better way to handle leap years or has a document with best practise to share ? I am assume that if-statements and NonEmpty function could be performance deadly for a calculated measure. All tips are greatly appreciated (Doesn't have to be a solution).

-- YTD PRIOR YEAR
[Time Calculations].[YTD Pr Yr] = IIF([Calendar].[Year - Month - Day].CurrentMember IS [Calendar].[Year - Month - Day].[Month].&[201702]
,Aggregate(
    NonEmptyCrossjoin({[Time Calculations].[Current Period]},
    PeriodsToDate(
        [Calendar].[Year - Month - Day].[Year],
[Calendar].[Year - Month - Day].[Day].&[2016-02-29 00:00:00.000])
))
,IIF([Calendar].[Year - Month - Day].CurrentMember IS [Calendar].[Year - Month - Day].[Month].&[201602]
,Aggregate(
    NonEmptyCrossjoin({[Time Calculations].[Current Period]},
    PeriodsToDate(
        [Calendar].[Year - Month - Day].[Year],
[Calendar].[Year - Month - Day].[Day].&[2015-02-28 00:00:00.000])
))
,(Aggregate(
    NonEmptyCrossjoin({[Time Calculations].[Current Period]},
    PeriodsToDate(
        [Calendar].[Year - Month - Day].[Year],
        ParallelPeriod(
           [Calendar].[Year - Month - Day].[Year],1,

TAIL(DESCENDANTS([Calendar].[Year - Month - Day].CurrentMember
, [Calendar].[Year - Month - Day].[Day]),1).ITEM(0)))

    )
    )
    )
    )
);

Best regards, Rubrix


Solution

  • Try the following, some caveats follow:

    CREATE MEMBER CURRENTCUBE.[Time Calculations].[YTD Prior Year] AS NULL; 
    
    /* Make sure the scope is for all days in all years in your calendar year */
    Scope([Invoice Date].[Calendar Year].[Calendar Year].members, [Invoice Date].[Calendar Day].members); 
    
        // YTD PRIOR YEAR
        ([Time Calculations].[YTD Prior Year] =
            iif(
                /* Check to see if the prior year member is empty */
                isempty(
                    ParallelPeriod(
                        [Invoice Date].[CY Hierarchy].[Calendar Year],
                        1,
                        [Invoice Date].[CY Hierarchy].CurrentMember
                    ).MemberValue
                ),
                /* If so, use the .LastChild */
                Aggregate(
                    Crossjoin(
                        {[Time Calculations].[Current Period]},
                        PeriodsToDate(
                            [Invoice Date].[CY Hierarchy].[Calendar Year],
                            ParallelPeriod(
                                [Invoice Date].[CY Hierarchy].[Calendar Year],
                                1,
                                Ancestor(
                                    [Invoice Date].[CY Hierarchy].CurrentMember,
                                    [Invoice Date].[CY Hierarchy].[Calendar Month]
                                )
                            ).LastChild
                        )
                    )
                ),
                /* Otherwise just use the prior year */
                Aggregate(
                    Crossjoin(
                        {[Time Calculations].[Current Period]},
                        PeriodsToDate(
                            [Invoice Date].[CY Hierarchy].[Calendar Year],
                            ParallelPeriod(
                                [Invoice Date].[CY Hierarchy].[Calendar Year],
                                1,
                                [Invoice Date].[CY Hierarchy].CurrentMember
                            )
                        )
                    )
                )
            )
        ); 
    
    End Scope;
    

    One possible hitch is that if you have several days in a row where there are no transactions, .LastChild might not work accurately. I didn't have that situation when I originally developed this code. It was specifically developed to deal with this exact case of prior year-to-date and leap year nuances. It might need to be tweaked if that's the case.

    This assumes that you have a proper time dimension and a time calculations dimension, which it looks like you do from the code sample you provided.

    I would say that the performance of this solution is pretty great even on large cubes (hundreds of millions of rows).