Monday, March 8, 2010

Handling Holidays on the Corporate Calendar: A Qlikview Approach

I was given the task of building a QlikView Dashboard for our services organization. We wanted to include daily consultant billing and then be able to compare that to the budget and consultant salary information. The problem was I was given Salary and Budget as a monthly number and consultant time entry was daily. Generally speaking this is not a problem until I get to the current month. Comparing the incomplete month to a full month budget or salary made progress difficult to measure.

I came up with an easy way to take the monthly budget number and blow it out to a daily figure. The key to the approach was using my corporate calendar to determine how much of the monthly budget or salary needed to be applied to each day. I could have just done some simple division by the number of days in the month but this would have meant we were expecting everyone to work on weekends and holidays and the daily budget would not have been in line with the real expectations for billable time.

The first thing I am going to do is load a table containing all the holidays for the company. there are three fields, the date, a description and an value for how much of the day is actually a holiday. Our company has a policy of offering half day holidays on Christmas Eve and New Year's Eve so I have to account for that in my calculations.


Holidays:
Load * INLINE [
HolidayDate,HolidayDesc,HolidayValue
1/1/2009,New Year's Day,0
5/25/2009,Memorial Day,0
7/3/2009,Independence Day Floating Holiday Placeholder,0
9/7/2009,Labor Day,0
11/26/2009,Thanksgiving,0
11/27/2009,Day After Thanksgiving,0
12/24/2009,Christmas Eve,0.5
12/25/2009,Christmas Day,0
12/31/2009,New Year's Eve,0.5
1/1/2010,New Year's Day,0
5/31/2010,Memorial Day,0
7/5/2010,Independence Day Floating Holiday Placeholder,0
9/6/2010,Labor Day,0
11/25/2010,Thanksgiving,0
11/26/2010,Day After Thanksgiving,0
12/24/2010,Christmas Eve,0.5
12/27/2010,Christmas Day Floating Holiday Placeholder,0
12/31/2010,New Year's Eve,0.5
];

The next step is to concatenate all my holiday dates into one string, I will use this string as a parameter in the NetWorkDays function which will count the number of working days in a range of dates. It uses a comma separated list to define holidays to skip along with the weekends.

The Concat function is an aggregate function so I cannot just assign it to a variable I have to use a load statement to use the aggregate function. There may be other ways to do this with the aggr() function but this is the approach that worked for me.

HolidayCalendar:
Load
Chr(39) & Replace(Concat(HolidayDate,','),',',chr(39) & ',' & chr(39)) & chr(39) as HolidayList
resident Holidays;


Let vHolidayList = Peek('HolidayList',0,'HolidayCalendar');


The next step is to build the Calendar table. If you have taken the developer course then you have undoubtedly seen this code before.
//Define the range of dates you want to include
Let vMinDate = num(Date('01/01/2009','MM/DD/YYYY'));
Let vMaxDate = Num(Date(Today(),'MM/DD/YYYY'));
Let vToday = num(Today());


//Autogenerate a source table for your calendar
TempCalendar:
Load
$(vMinDate) + RowNo() - 1 as Num,
Date($(vMinDate) + RowNo() - 1) as TempDate
Autogenerate
$(vMaxDate) - $(vMinDate) + 1;


Generally speaking the next step is pretty standard stuff, I am loadind all the different date dimensions I intend to use in my dashboard and an additional field called IsWorkingDay this field (1 for true 0 for false).
MasterCalendarTemp:
Load
TempDate as OrderDate,
Week(TempDate) as Week,
Year(TempDate) as Year,
Month(TempDate) as Month,
Day(TempDate) as Day,
WeekDay(TempDate) as WeekDay,
'Q' & ceil(Month(TempDate)/3) as Quarter,
Date(monthstart(TempDate),'MMM-YYYY') as MonthYear,
Week(TempDate) & '-' & Year(TempDate) as WeekYear,
inyeartodate(TempDate,$(vToday),0) * -1 as CurYTDFlag,
inyeartodate(TempDate,$(vToday),-1) * -1 as LastYTDFlag,
NetworkDays(TempDate,TempDate,$(vHolidayList)) as IsWorkingDay
Resident TempCalendar
order by TempDate ASC;



Next I am going to join my holiday table to the calendar
Left Join (MasterCalendarTemp)
Load
HolidayDate as OrderDate,
HolidayDesc,
HolidayValue
Resident
Holidays;


In this step I am going to create a field that gives me the appropriate value for the working days and those half day holidays.
MasterCalendar:
Load
OrderDate,
Week,
Year,
Month,
Day,
WeekDay,
Quarter,
MonthYear,
WeekYear,
CurYTDFlag,
LastYTDFlag,
IsWorkingDay,
HolidayDesc,
IF(isnull(HolidayValue),IsWorkingDay,HolidayValue) as WorkDayValue
Resident
MasterCalendarTemp;


Finally adding some additional fields so I can easily reference the number of working days in a month or year and cleaning up my temp tables.
Left join (MasterCalendar)
Load
Year,
Sum(WorkDayValue) as YearWorkingDays
Resident
MasterCalendar
Group By
Year;

Left Join (MasterCalendar)
Load
MonthYear,
Sum(WorkDayValue) as MonthWorkingDays
Resident
MasterCalendar
Group By
MonthYear;

Now when I load in my budget and salary tables I can use the MonthWorkingDays field to caclutate the appropriate daily budget and salary amount. I hope you find this post helpful, please comment if you find something that could be done better or if I left something out.

4 comments:

  1. Thanks a lot Chris! Saved lot of time

    ReplyDelete
  2. Could you explain me this please
    inyeartodate(TempDate,$(vToday),0) * -1 as CurYTDFlag,
    inyeartodate(TempDate,$(vToday),-1) * -1 as LastYTDFlag,

    ReplyDelete
  3. Hi Marina, Thanks for reading.

    Those fields are intended to provide year over year comparisons but I don't use that technique anymore. Let me explain and then I guess I should write a post about how I handle year over year comparisons. Basically those fields have a value of 1 or 0(zero) depending if the date in the calendar table is part of the current year to date or the previous year to date, respectively. I then multiply the flag by the thing I am measuring to remove the values that are outside the desired date range.

    So there is an important reason why I don't use this technique anymore. Qlikview is a VERY flexible tool and users may make selections from all of the data in you application. If you have several years worth of data in the application then users may select time periods that don't include dates from the current or previous year to date. When this happens the charts using those expressions will be blank.

    I response to this issue I have implemented a set of variables that I use in set analysis to provide current year/prior year comparisons based on the dates available in the application. I will write a new post to explain this approach and elaborate on the usage of the flags in this post.

    Thanks Again

    Chris

    ReplyDelete
  4. Thanks Chris, good suggestions. Can you explain the part of the calculation? I see all the date tables part, and therefore you are able to bring the right dates, but how are you calculating if you have monthly totals only?

    In my scenario, I have monthly budget totals assigned to the 1st of each month and would like to have a daily total to see daily comparisons. It seems you example is bringing in the the daily totals in the tables and then you calculate using your date solution above.

    Thanks!

    Jeb

    ReplyDelete