7
\$\begingroup\$

I have a calendar table with a record for every date I need to care about (i.e. 365 records per year), with various meta-information about each date, including the weekday, the fiscal calendar week, month, quarter and year it's under, whether there's a Holiday on that date, etc.

The table has the following keys / indexes:

,constraint PK_FiscalCalendars primary key clustered (_Id)
,constraint NK_FiscalCalendars unique (CalendarDate)

and:

create nonclustered index IX_FiscalCalendars_HolidayFiscalDayOfWeek
 on dwd.FiscalCalendars (Holiday, CalendarDate, FiscalDayOfWeek);

That's nice, but now I need to use this calendar table in various queries, and one of my requirements is to be able to determine the number of workdays between two dates, taking into account the contents of the Holiday column, which contains a non-null nvarchar(50) value (the name of the Holiday / reason for closing) whenever a weekday/date shouldn't be considered a workday; the column contains a NULL value otherwise.

So I've implemented this little function that intakes two dates and returns the number of workdays between them (inclusively):

create function dbo.WorkWeekDateDiff(@fromDate date, @toDate date)
returns int
as
begin
 /* returns the number of workdays (non-holiday week days) between two dates. */
 declare @result int;
 with calendar as (
 select CalendarDate
 from dwd.FiscalCalendars 
 where 
 CalendarDate between @fromDate and @toDate
 and Holiday is null -- exclude holidays
 and FiscalDayOfWeek not in (1,7) -- exclude Sunday and Saturday
 )
 select @result = (select count(*) from calendar);
 return @result;
end

Execution plan is showing 73% of the cost going on seeking the non-clustered index, and 27% performing that Count(*). It gets the job done, but since this is a scalar-valued function I'd like it as efficient as possible. Is there anything to improve?

execution plan: 73% on Index Seek (IX_FiscalCalendars_HolidayFiscalDayOfWeek), 27% Stream Aggregate

asked Sep 23, 2015 at 15:14
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

You have a physical table that has been established for the purpose of managing calendar dates. This is great, but with just a little more work it can be extended to do so much more.

Consider adding a column "DayType", with values like:

0 -> Normal 1 -> Weekend 2 -> Holiday

You could add this easily... right?

Then, you can maintain it with:

update FiscalCalendars
set DayType = case
 when Holiday is not null then 2
 when FiscalDayOfWeek in (1, 7) then 1
 else 0
 end

Now your query becomes quite simple:

select count (*)
from FiscalCalendars
where CalendarDate between @fromDate and @toDate
 and DateType = 0

Index the column with (DateType, CalendarDate)

Maintaining this static data in a function would be a poor use of computational power, and make indexing harder.

Your function would thus become:

create function dbo.WorkWeekDateDiff(@fromDate date, @toDate date)
returns int
as
begin
 /* returns the number of workdays (non-holiday week days) between two dates. */
 declare @result int;
 select @result = (select count(*)
 from dwd.FiscalCalendars
 where DateType = 0
 and CalendarDate between @fromDate and @toDate
 );
 return @result;
end
answered Sep 23, 2015 at 15:47
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.