Introduction
I am unfortunate enough to have to be tasked with writing QlikView scripts instead of being a real programmer, and
sometimes, the scripts (I hesitate to call them "apps", because that's not what they are at all, however, Qlik calls
them that - what a bunch of bozos) require a "calendar" table that is generated from scratch, and that covers a specific
date range.
Background
Up to this point, we had been using a Qlikview script to generate this calendar table. Since Qlikview is the
performance pig you might already assume that it is, I decided that I'd create a SQL-based solution and just load the
data into Qlikview from SQL. This tip describes the technique I used. To give fair warning, I am not what I would
call a SQL wizard, but I know how to get around. More importantly, I know how to use google.
Using the code
With that in mind, here's the scalar function I came up with that feeds my stored procedure. I make frequent use of
the DATEPART, DATEADD, DATENAME functions available in SQL at the top of the function to ease typing, and follow that up
with merely checking values to determine whether or not the specified date is a federal holiday.
There are essentially three types of holidays - holidays that fall on an nth day of the week (such as MLK day,
President's Day, etc), and holidays that occur on a certain day of the month but that are adjusted when that day occurs
on a weekend (such as New Year's Day, Christmas, etc). For no other reason than "it just made sense to do it that way",
I check for each holiday in the order it appears in the year. Using the function below as a template, you can add
additional holidays that are applicable to your project(s). Comments indicate which holiday for which I am checking.
NOTE: Your namespace probably won't be Reference_Tables
, so remember to change that to fit your
schema.
USE [Reference_Tables]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[sp_GetCalendar]
@startDate DATE,
@endDate DATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @fiscalOffset int = 3;
if (@startDate > @endDate)
BEGIN
DECLARE @temp date = @startDate;
SET @startDate = @endDate;
SET @endDate = @temp;
END
IF OBJECT_ID('tempdb..#tempCalendarJMS') IS NOT NULL
DROP TABLE #tempCalendar
CREATE TABLE #tempCalendarJMS (YearMonth VARCHAR(6), MonthSerial int)
What is a common table expression is? A common table expression (CTE) can be thought of
as a temporary result set that is defined within the execution scope of a single SELECT,
INSERT, UPDATE, DELETE, or CREATE VIEW statement. A CTE is similar to a derived table in
that it is not stored as an object and lasts only for the duration of the query. In our
case, we are creating DateTime values, and deriving additional properties based on those
values, all without having to use an excisting table in the database. Pretty handy, eh?
;WITH m AS
(
SELECT TOP (DATEDIFF(Month, @startDate, @endDate) + 1) m = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects
)
INSERT INTO #temp
(
YearMonth,
MonthSerial
)
SELECT CONVERT(VARCHAR, DATEPART(YEAR, DATEADD(Month, m-1, @startDate))) + CONVERT(VARCHAR, DATEPART(MONTH, DATEADD(Month, m-1, @startDate))),
DATEDIFF(MONTH, @startDate, DATEADD(Month, m-1, @startDate)) + 1
FROM m;
;WITH n AS
(
SELECT TOP (DATEDIFF(DAY, @startDate, @endDate) + 1) n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects
)
SELECT n as [rownumber]
,DATEADD(DAY, n-1, @startDate) AS CalendarDate
,DATEPART(YEAR, DATEADD(DAY, n-1, @startDate)) AS CalendarYear
,DATEPART(MONTH, DATEADD(DAY, n-1, @startDate)) AS CalendarMonth
,DATEPART(QUARTER, DATEADD(DAY, n-1, @startDate)) AS CalendarQuarter
,DATEPART(DAYOFYEAR, DATEADD(DAY, n-1, @startDate)) As CalendarJulianDay
,CONVERT(VARCHAR, DATEPART(YEAR, DATEADD(DAY, n-1, @startDate))) +
CONVERT(VARCHAR, DATEPART(MONTH, DATEADD(DAY, n-1, @startDate))) AS CalendarYearMonth
,CEILING(DATEPART(DAYOFYEAR, DATEADD(DAY, n-1, @startDate))/7.0) AS NthCalendarWeek
,DATEADD(MONTH, 3, DATEADD(DAY, n-1, @startDate)) AS FiscalDate
,DATEPART(YEAR, DATEADD(MONTH, @fiscalOffset, DATEADD(DAY, n-1, @startDate))) AS FiscalYear
,DATEPART(MONTH, DATEADD(MONTH, @fiscalOffset, DATEADD(DAY, n-1, @startDate))) AS FiscalMonth
,DATEPART(QUARTER, DATEADD(MONTH, @fiscalOffset, DATEADD(DAY, n-1, @startDate))) AS FiscalQuarter
,DATEPART(DAYOFYEAR, DATEADD(MONTH, @fiscalOffset, DATEADD(DAY, n-1, @startDate))) As FiscalJulianDay
,CONVERT(VARCHAR,DATEPART(YEAR, DATEADD(MONTH, @fiscalOffset, DATEADD(DAY, n-1, @startDate)))) +
CONVERT(VARCHAR,DATEPART(MONTH, DATEADD(MONTH, @fiscalOffset, DATEADD(DAY, n-1, @startDate)))) AS FiscalYearMonth
,CEILING(DATEPART(DAYOFYEAR, DATEADD(MONTH, @fiscalOffset, DATEADD(DAY, n-1, @startDate)))/7.0) As NthFiscalWeek
,DATENAME(MONTH, DATEADD(DAY, n-1, @startDate)) AS [MonthName]
,DATEPART(DAY, DATEADD(DAY, n-1, @startDate)) AS [DayOfMonth]
,DATEPART(WEEKDAY, DATEADD(DAY, n-1, @startDate)) AS WeekDayNumber
,DATENAME(WEEKDAY, DATEADD(DAY, n-1, @startDate)) AS WeekDayName
,ceiling(DATEPART(DAY, DATEADD(DAY, n-1, @startDate)) / 7.0) As NthWeekday
,dbo.fn_IsHoliday(DATEADD(DAY, n-1, @startDate)) AS IsHoliday
,t.MonthSerial AS MonthSerial
FROM n
LEFT JOIN #temp t ON t.YearMonth = CONVERT(VARCHAR, DATEPART(YEAR, DATEADD(DAY, n-1, @startDate))) + CONVERT(VARCHAR, DATEPART(MONTH, DATEADD(DAY, n-1, @startDate)))
END
GO
The code above calls a scalar function called fn_IsHoliday
. You can fid that function in this tip: US Federal Holdiays (SQL)
Usage
I've used this stored procedure from C# code, an Excel spreadsheet, and from Qlikview scripts.
Essentially, anything that can call a stored procedure can call this one (assuming the caller
has the appropriate SQL Server permissions).
Points of Interest
Chuck Norris was born in a log cabin he built with his own hands.
History
01 Feb 2017 - Original submission.