use tempdb
go
CREATE PROCEDURE ai_GenerateFiscalCalendar
/*
Expands a financial year specification (start date and weeks-per month pattern)
into a set of daily update statements for an existing Date Dimension table.
Expands one financial year at a time.
PRE-REQUISITES :
* Min SQL Server 2012 (for window function).
OUTPUTS :
* A summary of fiscal periods with counts is printed to standard output.
* Update statements returned as rowset for updating @TableToUpdate.
PARAMETERS :
@FirstDayOfFiscalYear
(date) The date of the first day of first period of fiscal year.
@FiscalYearName
(string) Identifier for the fiscal year (e.g. '2017'). This is only used in update statements (if required).
@TableToUpdate
The name of the table to be updated, including schema if necessary, as it would appear in an UPDATE statement.
@DateKeyColumn
The Date-Key column of table to update. Column must be integer conversion of date format 112 (yyyymmdd).
@ColumnUpdatePattern
A string containing multiple update statements as they would appear following SET in an UPDATE statement.
Inverted commas must be escaped. The following placeholders can be used.
{year} : the value of @FiscalYearName
{d}, {w}, {m}, {q}, {s} : day, week, month, quarter and semester numbers.
{wst}, {mst}, {qst}, {sst} : week, month, quarter, semester start date key
{wend}, {mend}, {qend}, {send} : week, month, quarter, semester end date key
@WeeksPerMonthPattern
A comma separated set of values indicating the number of weeks in each fiscal month.
e.g. '4,4,5,4,4,5,4,4,5,4,4,5'.
@MonthsPerQuarterPattern
A comma separated set of values indicating the number of months in each fiscal quarter.
e.g. '3,3,3,3'
@NbrOfWeeks
(int) The total number of weeks in the fiscal year. Default is 52. Override this value
for 53 week or non-standard fiscal years.
EXAMPLE SUMMARY OUTPUT :
FISCAL YEAR: 2017
START DATE: 2017-04-30 Sunday
TOTAL DAYS: 364
TOTAL WEEKS: 52
MONTHS:
1 2017-04-30 Sun 28 days 4 weeks Qtr: 1 Sem: 1
2 2017-05-28 Sun 28 days 4 weeks Qtr: 1 Sem: 1
3 2017-06-25 Sun 35 days 5 weeks Qtr: 1 Sem: 1
4 2017-07-30 Sun 28 days 4 weeks Qtr: 2 Sem: 1
5 2017-08-27 Sun 28 days 4 weeks Qtr: 2 Sem: 1
6 2017-09-24 Sun 35 days 5 weeks Qtr: 2 Sem: 1
7 2017-10-29 Sun 28 days 4 weeks Qtr: 3 Sem: 2
8 2017-11-26 Sun 28 days 4 weeks Qtr: 3 Sem: 2
9 2017-12-24 Sun 35 days 5 weeks Qtr: 3 Sem: 2
10 2018-01-28 Sun 28 days 4 weeks Qtr: 4 Sem: 2
11 2018-02-25 Sun 28 days 4 weeks Qtr: 4 Sem: 2
12 2018-03-25 Sun 35 days 5 weeks Qtr: 4 Sem: 2
HISTORY
Andrew Ing. Ver 1. 22/4/2017.
*/
@FirstDayOfFiscalYear date = '2017-04-30',
@FiscalYearName varchar(50) = '2017',
@TableToUpdate varchar(50) = 'DestinationTableName',
@DateKeyColumn varchar(50) = 'DateKey',
@ColumnUpdatePattern varchar(1000) = 'Wk={w}, Mth={m}, Qtr={q}, S={s}, WkStart = {wst}, WkEnd={wend}, MStart={mst}, Mend={mend}, QStart={qst}, QEnd={qend}, SStart={sst}, SEnd={send}',
@WeeksPerMonthPattern varchar(100) = '4,4,5,4,4,5,4,4,5,4,4,5',
@MonthsPerQuarterPattern varchar(100) = '3,3,3,3',
@NbrOfWeeks int = 52
AS
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @WeeksPerMonth_XML xml;
DECLARE @MonthsPerQuarter_XML xml;
DECLARE @NbrOfDays int;
DECLARE @x int;
DECLARE @MonthSummary varchar(1000);
BEGIN TRY
-- Validation.
IF (REPLACE(@WeeksPerMonthPattern, ',', '') LIKE '%[^0-9]%') RAISERROR ('Illegal character in @WeeksPerMonthPatter. Numbers and commas only.', 16, 1);
IF (REPLACE(@MonthsPerQuarterPattern, ',', '') LIKE '%[^0-9]%') RAISERROR ('Illegal character in @MonthsPerQuarterPattern. Numbers and commas only.', 16,1);
CREATE TABLE #ai_FiscalPeriods (DayNbr int IDENTITY (1,1), [Date] date, WeekNbr int, MonthNbr tinyint, QuarterNbr tinyint, SemesterNbr tinyint);
-- Convert csv patterns to xml.
SET @WeeksPerMonth_XML = CAST((''+ REPLACE(@WeeksPerMonthPattern, ',' ,'')+'') AS xml);
SET @MonthsPerQuarter_XML = CAST(('' + REPLACE(@MonthsPerQuarterPattern, ',', '') + '') AS xml);
IF ((SELECT SUM(N.value('.', 'smallint')) FROM @MonthsPerQuarter_XML.nodes('x') AS T(N))
<> (SELECT COUNT(*) FROM @WeeksPerMonth_XML.nodes('x') AS T(N)) ) RAISERROR('The total months in @MonthsPerQuarterPattern doesn''t match number of months in @WeeksPerMonthPattern.', 16, 1);
IF (@NbrOfWeeks <> (SELECT SUM(N.value('.', 'tinyint')) FROM @WeeksPerMonth_XML.nodes('x') as T(N)))
RAISERROR('The total number of weeks specified in @WeeksPerMonthPattern does not match the @NbrOfWeeks parameter.', 16, 1);
-- Insert one row per day into table adn add week divisions.
SET @x = 0;
WHILE (@x < (@NbrOfWeeks * 7))
BEGIN
INSERT INTO #ai_FiscalPeriods ([Date], WeekNbr)
SELECT DATEADD(DAY, @x, @FirstDayOfFiscalYear), FLOOR((@x + 7 )/ 7);
SET @x += 1;
END;
--Insert Month Numbers.
WITH MonthSpec AS (
SELECT
MonthNbr = DENSE_RANK() OVER (ORDER BY T.N)
,StartWeekNbr = 1 + ISNULL(SUM(N.value('.', 'tinyint')) OVER (ORDER BY N ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0)
,EndWeekNbr = SUM(N.value('.', 'tinyint')) OVER (ORDER BY N)
FROM @WeeksPerMonth_XML.nodes('/x') AS T (N)
)
UPDATE FP
SET MonthNbr = MonthSpec.MonthNbr
FROM
#ai_FiscalPeriods FP
INNER JOIN MonthSpec ON FP.WeekNbr BETWEEN MonthSpec.StartWeekNbr AND MonthSpec.EndWeekNbr;
-- Insert Quarter and Semester Numbers.
WITH QuarterSpec AS (
SELECT
QuarterNbr = DENSE_RANK() OVER (ORDER BY T.N)
,StartMonthNumber = 1 + ISNULL(SUM(N.value('.', 'tinyint')) OVER (ORDER BY N ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), 0)
,EndMonthNumber = SUM(N.value('.', 'tinyint')) OVER (ORDER BY N)
FROM @MonthsPerQuarter_XML.nodes('/x') AS T(N)
)
UPDATE FP
SET QuarterNbr = QuarterSpec.QuarterNbr,
SemesterNbr = CASE WHEN QuarterSpec.QuarterNbr <= 2 THEN 1 ELSE 2 END
FROM
#ai_FiscalPeriods FP
INNER JOIN QuarterSpec ON FP.MonthNbr BETWEEN QuarterSpec.StartMonthNumber AND QuarterSpec.EndMonthNumber;
-- Print a summary of the fiscal periods.
SELECT @NbrOfDays = COUNT(*) FROM #ai_FiscalPeriods;
print 'FISCAL YEAR: ' + @FiscalYearName + CHAR(13) +
'START DATE: ' + CAST(@FirstDayOfFiscalYear AS varchar(20)) + ' ' + DATENAME(dw, @FirstDayOfFiscalYear) + CHAR(13) +
'TOTAL DAYS: ' + CAST(@NbrOfDays AS varchar(10)) + CHAR(13) +
'TOTAL WEEKS: ' + CAST(@NbrOfWeeks AS varchar(10)) + CHAR(13) + 'MONTHS:';
DECLARE cur CURSOR FOR
SELECT
MonthSummary = RIGHT(' ' + CAST(FP.MonthNbr AS varchar(10)), 2) + ' ' +
CAST(MIN(FP.[Date]) AS varchar(10)) + ' ' + LEFT(DATENAME(dw, MIN(FP.[Date])), 3) + ' ' +
CAST(COUNT(*) AS varchar(10)) +' days ' +
CAST(COUNT(DISTINCT FP.WeekNbr) AS varchar(10)) + ' weeks Qtr: ' +
CAST(MIN(FP.QuarterNbr) AS varchar(10)) + ' Sem: ' +
CAST(MIN(FP.SemesterNbr) AS varchar(10))
FROM
#ai_FiscalPeriods FP
GROUP BY
FP.MonthNbr
ORDER BY
FP.MonthNbr;
OPEN cur;
FETCH NEXT FROM cur INTO @MonthSummary;
WHILE @@FETCH_STATUS = 0
BEGIN
print @MonthSummary;
FETCH NEXT FROM cur INTO @MonthSummary;
END
CLOSE cur;
DEALLOCATE cur;
-- Generate Update statements.
SELECT
'UPDATE ' + @TableToUpdate + ' SET ' +
REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(@ColumnUpdatePattern,
'{year}', @FiscalYearName),
'{wst}', (SELECT CONVERT(char(8), MIN([Date]), 112) FROM #ai_FiscalPeriods F WHERE F.WeekNbr = FP.WeekNbr)),
'{wend}', (SELECT CONVERT(char(8), MAX([Date]), 112) FROM #ai_FiscalPeriods F WHERE F.WeekNbr = FP.WeekNbr)),
'{mst}', (SELECT CONVERT(char(8), MIN([Date]), 112) FROM #ai_FiscalPeriods F WHERE F.MonthNbr = FP.MonthNbr)),
'{mend}', (SELECT CONVERT(char(8), MAX([Date]), 112) FROM #ai_FiscalPeriods F WHERE F.MonthNbr = FP.MonthNbr)),
'{qst}', (SELECT CONVERT(char(8), MIN([Date]), 112) FROM #ai_FiscalPeriods F WHERE F.QuarterNbr = FP.QuarterNbr)),
'{qend}', (SELECT CONVERT(char(8), MAX([Date]), 112) FROM #ai_FiscalPeriods F WHERE F.QuarterNbr = FP.QuarterNbr)),
'{sst}', (SELECT CONVERT(char(8), MIN([Date]), 112) FROM #ai_FiscalPeriods F WHERE F.SemesterNbr = FP.SemesterNbr)),
'{send}', (SELECT CONVERT(char(8), MAX([Date]), 112) FROM #ai_FiscalPeriods F WHERE F.SemesterNbr = FP.SemesterNbr)),
'{d}', CAST(FP.DayNbr AS varchar(10)) ),
'{w}', CAST(FP.WeekNbr AS varchar(10)) ),
'{m}', CAST(FP.MonthNbr AS varchar(10)) ),
'{q}', CAST(FP.QuarterNbr AS varchar(10)) ) ,
'{s}', CAST(FP.SemesterNbr AS varchar(10)) ) +
' WHERE ' + @DateKeyColumn + ' = ' + CONVERT(char(8), FP.[Date], 112) + ';'
FROM #ai_FiscalPeriods FP
ORDER BY DayNbr;
IF OBJECT_ID('tempdb..#ai_FiscalPeriods') IS NOT NULL DROP TABLE #ai_FiscalPeriods;
END TRY
BEGIN CATCH
THROW
END CATCH
GO