SQL Server - Set Canadian Business Holiday Date - sql

Note: this is for Canadian Holidays:
I need to populate a table with Canadian business holidays.
I have the following query that sets up business holiday dates and also considers the movement of the holiday to the next weekday in the event of the holiday occurring on a weekend. ie if the holiday occurs on Sunday then Monday becomes a holiday. I also identify weekdays with a flag and add an English description of the day.
I add a few calculated columns to make the job a bit easier, these are removed again after all the data has been created. I cannot drop the table; the table maybe partly populated and I cannot overwrite existing data. This is the root of my problem. Because of the 'Were' the query is very slow.
The query I am using to populate the table is:
DECLARE #StartDate DATE = '20000101'
,#EndDate DATE = '21631231';
WITH N10(n)
AS (SELECT 1
FROM ( VALUES ( 0), ( 1), ( 2), ( 3), ( 4), ( 5), ( 6), ( 7), ( 8), ( 9) ) v (n)),
N100(n)
AS (SELECT 1
FROM N10
,N10 n),
N10000(n)
AS (SELECT 1
FROM N100
,N100 n),
N100000(n)
AS (SELECT 1
FROM N10
,N10000 n),
N AS (SELECT TOP (DATEDIFF(DAY,#StartDate,#EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
FROM N100000)
INSERT INTO [dbo].[BusinessCalendarDetails2] (BusinessDate,CreatedById,CreatedTime,BusinessCalendarId,IsWeekday,
IsHoliday)
(SELECT InsertDate,1,CURRENT_TIMESTAMP,1,1,0
FROM N
CROSS APPLY (SELECT DATEADD (DAY,n,#StartDate)) d (InsertDate)
WHERE NOT EXISTS ( SELECT 1 FROM [BusinessCalendarDetails2]
WHERE [BusinessDate] = InsertDate ));
This works but the problem is the 'WHERE' to identify if the date already exists in the table is severely slowing it down (14 mins). So I am wondering if someone has a faster solution for identifying already existing dates?
Thanks in advance for your assistance.
The following is the entire script, it works fairly well; others may find it useful.
-- Populate table with business holidays
-- If the table is missing add it
IF OBJECT_ID('BusinessCalendarDetails2') IS NULL
BEGIN
CREATE TABLE dbo.BusinessCalendarDetails2 (
Id BIGINT IDENTITY(1,1)
NOT NULL
,BusinessDate [DATE] NOT NULL
,Day AS DAY(BusinessDate)
,Week AS DATEPART(WEEK,BusinessDate)
,[Month] AS MONTH(BusinessDate)
,Quarter AS DATEPART(QUARTER,BusinessDate)
,[Year] AS YEAR(BusinessDate)
,DayOfWeek AS DATEPART(WEEKDAY,BusinessDate)
,CreatedById BIGINT NOT NULL
,CreatedTime DATETIMEOFFSET(7) NOT NULL
,UpdatedById BIGINT NULL
,UpdatedTime DATETIMEOFFSET(7) NULL
,BusinessCalendarId BIGINT NOT NULL
,RowVersion TIMESTAMP NOT NULL
,IsWeekday BIT NOT NULL
,IsHoliday BIT NOT NULL
,Description NVARCHAR(50)
,PRIMARY KEY CLUSTERED (Id ASC)
WITH (PAD_INDEX = OFF,STATISTICS_NORECOMPUTE = OFF,IGNORE_DUP_KEY = OFF,ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY])
ON [PRIMARY];
END;
-- Check if this col exists,
IF COL_LENGTH('dbo.BusinessCalendarDetails2','Day') IS NULL
BEGIN
ALTER TABLE dbo.BusinessCalendarDetails2
ADD -- Add calculated fields
Day AS DAY(BusinessDate), Week AS DATEPART(WEEK, BusinessDate), [Month] AS MONTH(BusinessDate), Quarter AS DATEPART(QUARTER, BusinessDate), [Year] AS YEAR(BusinessDate), DayOfWeek AS DATEPART(WEEKDAY, BusinessDate);
END;
GO
-- Date range to populate
DECLARE #StartDate DATE= '20000101' ,#EndDate DATE= '21631231';
WITH N10(n)
AS (SELECT 1
FROM ( VALUES ( 0 ), ( 1 ), ( 2 ), ( 3 ), ( 4 ), ( 5 ), ( 6 ), ( 7 ), ( 8 ), ( 9 ) ) AS v (n)),
N100(n)
AS (SELECT 1
FROM N10
,N10 AS n),
N10000(n)
AS (SELECT 1
FROM N100
,N100 AS n),
N100000(n)
AS (SELECT 1
FROM N10
,N10000 AS n),
N AS (SELECT TOP (DATEDIFF(DAY,#StartDate,#EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
FROM N100000)
INSERT INTO dbo.BusinessCalendarDetails2 (BusinessDate,CreatedById,CreatedTime,BusinessCalendarId,IsWeekday,
IsHoliday)
(SELECT InsertDate,1,CURRENT_TIMESTAMP,1,1,0
FROM N
CROSS APPLY (SELECT DATEADD (DAY,n,#StartDate)) AS d (InsertDate)
WHERE NOT EXISTS ( SELECT 1
FROM BusinessCalendarDetails2
WHERE BusinessDate = InsertDate ));
-- Set Descriptions
UPDATE dbo.BusinessCalendarDetails2
SET Description = DATENAME(dw,BusinessDate)
FROM dbo.BusinessCalendarDetails2;
-- Set weekdays
UPDATE dbo.BusinessCalendarDetails2
SET IsWeekday = 0,Description = Description + ' Weekend'
FROM dbo.BusinessCalendarDetails2 AS c1
WHERE DATEPART(WEEKDAY,c1.BusinessDate) IN (1,7);
-- New Years Day
UPDATE dbo.BusinessCalendarDetails2
SET IsHoliday = 1,Description = Description + ' New Years Day'
FROM dbo.BusinessCalendarDetails2
WHERE BusinessCalendarDetails2.[Month] = 1
AND (SELECT CASE (##DATEFIRST + DATEPART(DW,CAST(DATEPART(YEAR,BusinessDate) AS VARCHAR) + '-01-01')) % 7
WHEN 0 THEN 3 -- SAT
WHEN 1 THEN 2 -- Sunday
ELSE 1
END) = BusinessCalendarDetails2.Day;
-- Family Day Day -- 3rd Monday in February
UPDATE dbo.BusinessCalendarDetails2
SET IsHoliday = 1,Description = Description + ' Family Day'
FROM dbo.BusinessCalendarDetails2 AS c1
WHERE c1.[Month] = 2
AND c1.DayOfWeek = 2
AND c1.Day BETWEEN 15 AND 21;
-- Canada Day
UPDATE dbo.BusinessCalendarDetails2
SET IsHoliday = 1,Description = Description + ' Canada Day'
FROM dbo.BusinessCalendarDetails2
WHERE BusinessCalendarDetails2.[Month] = 7
AND (SELECT CASE (##DATEFIRST + DATEPART(DW,CAST(DATEPART(YEAR,BusinessDate) AS VARCHAR) + '-07-01')) % 7
WHEN 0 THEN 3 -- SAT
WHEN 1 THEN 2 -- Sunday
ELSE 1
END) = BusinessCalendarDetails2.Day;
-- Civic Holiday
UPDATE dbo.BusinessCalendarDetails2
SET IsHoliday = 1,Description = Description + ' Civic Holiday'
FROM dbo.BusinessCalendarDetails2 AS c1
WHERE c1.[Month] = 8
AND c1.DayOfWeek = 2
AND c1.Day BETWEEN 1 AND 7;
-- Good Friday
UPDATE BusinessCalendarDetails2
SET IsHoliday = 1,Description = Description + ' Good Friday'
FROM dbo.BusinessCalendarDetails2 AS dimdate
CROSS APPLY (SELECT dimdate.Year AS y) _y
CROSS APPLY (SELECT y / 100 AS c,y - 19 * y / 19 AS n) _nc
CROSS APPLY (SELECT (c - 17) / 25 AS k) _k
CROSS APPLY (SELECT c - c / 4 - (c - k) / 3 + 19 * n + 15 AS i1) _i1
CROSS APPLY (SELECT i1 - 30 * i1 / 30 AS i2) _i2
CROSS APPLY (SELECT i2 - (i2 / 28) * (1 - (i2 / 28) * 29 / (i2 + 1) * (21 - n) / 11) AS i) _i
CROSS APPLY (SELECT y + y / 4 + i + 2 - c + c / 4 AS j1) _j1
CROSS APPLY (SELECT j1 - 7 * j1 / 7 AS j) _j
CROSS APPLY (SELECT i - j AS el) _el
CROSS APPLY (SELECT 3 + (el + 40) / 44 AS m) _m
CROSS APPLY (SELECT el + 28 - 31 * m / 4 AS d) _d
CROSS APPLY (SELECT DATEFROMPARTS (y,m,d) AS EasterSunday) _Easter
WHERE dimdate.BusinessDate = DATEADD(DAY,-2,EasterSunday);
-- Easter Sunday
UPDATE BusinessCalendarDetails2
SET IsHoliday = 0,Description = Description + ' Easter Sunday'
FROM dbo.BusinessCalendarDetails2 AS dimdate
CROSS APPLY (SELECT dimdate.Year AS y) _y
CROSS APPLY (SELECT y / 100 AS c,y - 19 * y / 19 AS n) _nc
CROSS APPLY (SELECT (c - 17) / 25 AS k) _k
CROSS APPLY (SELECT c - c / 4 - (c - k) / 3 + 19 * n + 15 AS i1) _i1
CROSS APPLY (SELECT i1 - 30 * i1 / 30 AS i2) _i2
CROSS APPLY (SELECT i2 - (i2 / 28) * (1 - (i2 / 28) * 29 / (i2 + 1) * (21 - n) / 11) AS i) _i
CROSS APPLY (SELECT y + y / 4 + i + 2 - c + c / 4 AS j1) _j1
CROSS APPLY (SELECT j1 - 7 * j1 / 7 AS j) _j
CROSS APPLY (SELECT i - j AS el) _el
CROSS APPLY (SELECT 3 + (el + 40) / 44 AS m) _m
CROSS APPLY (SELECT el + 28 - 31 * m / 4 AS d) _d
CROSS APPLY (SELECT DATEFROMPARTS (y,m,d) AS EasterSunday) _Easter
WHERE dimdate.BusinessDate = EasterSunday;
-- Labour Day -- first Monday of September
UPDATE dbo.BusinessCalendarDetails2
SET IsHoliday = 1,Description = Description + ' Labour Day'
FROM dbo.BusinessCalendarDetails2 AS c1
WHERE c1.[Month] = 9
AND c1.DayOfWeek = 2
AND c1.Day BETWEEN 1 AND 7;
-- Set Thanksgiving
UPDATE dbo.BusinessCalendarDetails2
SET IsHoliday = 1,Description = Description + ' Thanksgiving'
FROM dbo.BusinessCalendarDetails2 AS c1
WHERE c1.[Month] = 10
AND c1.DayOfWeek = 2
AND c1.Day BETWEEN 8 AND 14;
---- Christmas
UPDATE dbo.BusinessCalendarDetails2
SET IsHoliday = 1,Description = Description + ' Christmas Holidays'
FROM dbo.BusinessCalendarDetails2
WHERE [Month] = 12
AND Day BETWEEN 26 AND 28
AND (DATEPART(WEEKDAY,CAST(DATEPART(YEAR,BusinessDate) AS VARCHAR) + '-12-25') IN (1,7) -- Is Christmas on a weekend this year
OR DATEPART(WEEKDAY,CAST(DATEPART(YEAR,BusinessDate) AS VARCHAR) + '-12-26') IN (1,7)) -- Is Boxingday on a weekend this year
AND DATEPART(dw,BusinessDate) <> 4 -- NOT ON Wednesday!
AND DATEPART(DAY,(SELECT CASE (##DATEFIRST + DATEPART(dw,BusinessDate)) % 7
WHEN 0 THEN DATEADD(DAY,2,BusinessDate) -- Saturday
WHEN 1 THEN DATEADD(DAY,1,BusinessDate) -- Sunday
ELSE BusinessDate
END AS Weekday)) BETWEEN 26
AND 28
OR [Month] = 12 -- Is christmas a week day?
AND Day BETWEEN 25 AND 26
AND IsWeekday = 1;
GO
-- Return table to orig state
ALTER TABLE dbo.BusinessCalendarDetails2 DROP COLUMN Day, Week, [Month], Quarter, [Year], DayOfWeek;

If there's no index on BusinessDate and the table is big enough, that would certainly slow things down. If it is indexed, you may need to rebuild/reorganize it, or even just redo the statistics.
Otherwise, depending on your disks, it may be quicker to insert the distinct BusinessDate column values into a temp table with an index on it and compare against that.
It could be many things. If you haven't already, you should look at the ACTUAL execution plan and statistics for your query to get a better idea of what exactly is causing the issue.

This is the final answer using a Temp table it runs much faster
-- Populate table with business holidays
CREATE TABLE #BusCalDet (
Id BIGINT IDENTITY(1,1)
NOT NULL
,BusinessDate [DATE] NOT NULL
,Day AS DAY(BusinessDate)
,Week AS DATEPART(WEEK,BusinessDate)
,[Month] AS MONTH(BusinessDate)
,Quarter AS DATEPART(QUARTER,BusinessDate)
,[Year] AS YEAR(BusinessDate)
,DayOfWeek AS DATEPART(WEEKDAY,BusinessDate)
,CreatedById BIGINT NOT NULL
,CreatedTime DATETIMEOFFSET(7) NOT NULL
,UpdatedById BIGINT NULL
,UpdatedTime DATETIMEOFFSET(7) NULL
,BusinessCalendarId BIGINT NOT NULL
,RowVersion TIMESTAMP NOT NULL
,IsWeekday BIT NOT NULL
,IsHoliday BIT NOT NULL
,Description NVARCHAR(50)
,PRIMARY KEY CLUSTERED (Id ASC)
WITH (PAD_INDEX = OFF,STATISTICS_NORECOMPUTE = OFF,IGNORE_DUP_KEY = OFF,ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [PRIMARY])
ON [PRIMARY];
-- Date range to populate
DECLARE #StartDate DATE= '20000101'
,#EndDate DATE= '21631231';
WITH N10(n)
AS (SELECT 1
FROM ( VALUES ( 0 ), ( 1 ), ( 2 ), ( 3 ), ( 4 ), ( 5 ), ( 6 ), ( 7 ), ( 8 ), ( 9 ) ) AS v (n)),
N100(n)
AS (SELECT 1
FROM N10
,N10 AS n),
N10000(n)
AS (SELECT 1
FROM N100
,N100 AS n),
N100000(n)
AS (SELECT 1
FROM N10
,N10000 AS n),
N AS (SELECT TOP (DATEDIFF(DAY,#StartDate,#EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1
FROM N100000)
INSERT INTO #BusCalDet (BusinessDate,CreatedById,CreatedTime,BusinessCalendarId,IsWeekday,IsHoliday)
SELECT InsertDate,1,CURRENT_TIMESTAMP,1,1,0
FROM N
CROSS APPLY (SELECT DATEADD (DAY,n,#StartDate)) AS d (InsertDate);
-- Set Descriptions
UPDATE #BusCalDet
SET Description = DATENAME(dw,BusinessDate)
FROM #BusCalDet;
-- Set weekdays
UPDATE #BusCalDet
SET IsWeekday = 0,Description = Description + ' Weekend'
FROM #BusCalDet AS c1
WHERE DATEPART(WEEKDAY,c1.BusinessDate) IN (1,7);
-- New Years Day
UPDATE #BusCalDet
SET IsHoliday = 1,Description = Description + ' New Years Day'
FROM #BusCalDet
WHERE #BusCalDet.[Month] = 1
AND (SELECT CASE (##DATEFIRST + DATEPART(DW,CAST(DATEPART(YEAR,BusinessDate) AS VARCHAR) + '-01-01'))
% 7
WHEN 0 THEN 3 -- SAT
WHEN 1 THEN 2 -- Sunday
ELSE 1
END) = #BusCalDet.Day;
-- Family Day Day -- 3rd Monday in February
UPDATE #BusCalDet
SET IsHoliday = 1,Description = Description + ' Family Day'
FROM #BusCalDet AS c1
WHERE c1.[Month] = 2
AND c1.DayOfWeek = 2
AND c1.Day BETWEEN 15 AND 21;
-- Canada Day
UPDATE #BusCalDet
SET IsHoliday = 1,Description = Description + ' Canada Day'
FROM #BusCalDet
WHERE #BusCalDet.[Month] = 7
AND (SELECT CASE (##DATEFIRST + DATEPART(DW,CAST(DATEPART(YEAR,BusinessDate) AS VARCHAR) + '-07-01'))
% 7
WHEN 0 THEN 3 -- SAT
WHEN 1 THEN 2 -- Sunday
ELSE 1
END) = #BusCalDet.Day;
-- Civic Holiday
UPDATE #BusCalDet
SET IsHoliday = 1,Description = Description + ' Civic Holiday'
FROM #BusCalDet AS c1
WHERE c1.[Month] = 8
AND c1.DayOfWeek = 2
AND c1.Day BETWEEN 1 AND 7;
-- Good Friday
UPDATE #BusCalDet
SET IsHoliday = 1,Description = Description + ' Good Friday'
FROM #BusCalDet AS dimdate
CROSS APPLY (SELECT dimdate.Year AS y) _y
CROSS APPLY (SELECT y / 100 AS c,y - 19 * y / 19 AS n) _nc
CROSS APPLY (SELECT (c - 17) / 25 AS k) _k
CROSS APPLY (SELECT c - c / 4 - (c - k) / 3 + 19 * n + 15 AS i1) _i1
CROSS APPLY (SELECT i1 - 30 * i1 / 30 AS i2) _i2
CROSS APPLY (SELECT i2 - (i2 / 28) * (1 - (i2 / 28) * 29 / (i2 + 1) * (21 - n) / 11) AS i) _i
CROSS APPLY (SELECT y + y / 4 + i + 2 - c + c / 4 AS j1) _j1
CROSS APPLY (SELECT j1 - 7 * j1 / 7 AS j) _j
CROSS APPLY (SELECT i - j AS el) _el
CROSS APPLY (SELECT 3 + (el + 40) / 44 AS m) _m
CROSS APPLY (SELECT el + 28 - 31 * m / 4 AS d) _d
CROSS APPLY (SELECT DATEFROMPARTS (y,m,d) AS EasterSunday) _Easter
WHERE dimdate.BusinessDate = DATEADD(DAY,-2,EasterSunday);
-- Easter Sunday
UPDATE #BusCalDet
SET IsHoliday = 0,Description = Description + ' Easter Sunday'
FROM #BusCalDet AS dimdate
CROSS APPLY (SELECT dimdate.Year AS y) _y
CROSS APPLY (SELECT y / 100 AS c,y - 19 * y / 19 AS n) _nc
CROSS APPLY (SELECT (c - 17) / 25 AS k) _k
CROSS APPLY (SELECT c - c / 4 - (c - k) / 3 + 19 * n + 15 AS i1) _i1
CROSS APPLY (SELECT i1 - 30 * i1 / 30 AS i2) _i2
CROSS APPLY (SELECT i2 - (i2 / 28) * (1 - (i2 / 28) * 29 / (i2 + 1) * (21 - n) / 11) AS i) _i
CROSS APPLY (SELECT y + y / 4 + i + 2 - c + c / 4 AS j1) _j1
CROSS APPLY (SELECT j1 - 7 * j1 / 7 AS j) _j
CROSS APPLY (SELECT i - j AS el) _el
CROSS APPLY (SELECT 3 + (el + 40) / 44 AS m) _m
CROSS APPLY (SELECT el + 28 - 31 * m / 4 AS d) _d
CROSS APPLY (SELECT DATEFROMPARTS (y,m,d) AS EasterSunday) _Easter
WHERE dimdate.BusinessDate = EasterSunday;
-- Labour Day -- first Monday of September
UPDATE #BusCalDet
SET IsHoliday = 1,Description = Description + ' Labour Day'
FROM #BusCalDet AS c1
WHERE c1.[Month] = 9
AND c1.DayOfWeek = 2
AND c1.Day BETWEEN 1 AND 7;
-- Set Thanksgiving
UPDATE #BusCalDet
SET IsHoliday = 1,Description = Description + ' Thanksgiving'
FROM #BusCalDet AS c1
WHERE c1.[Month] = 10
AND c1.DayOfWeek = 2
AND c1.Day BETWEEN 8 AND 14;
---- Christmas
UPDATE #BusCalDet
SET IsHoliday = 1,Description = Description + ' Christmas Holidays'
FROM #BusCalDet
WHERE [Month] = 12
AND Day BETWEEN 26 AND 28
AND (DATEPART(WEEKDAY,CAST(DATEPART(YEAR,BusinessDate) AS VARCHAR) + '-12-25') IN (1,7) -- Is Christmas on a weekend this year
OR DATEPART(WEEKDAY,CAST(DATEPART(YEAR,BusinessDate) AS VARCHAR) + '-12-26') IN (1,7)) -- Is Boxingday on a weekend this year
AND DATEPART(dw,BusinessDate) <> 4 -- NOT ON Wednesday!
AND DATEPART(DAY,(SELECT CASE (##DATEFIRST + DATEPART(dw,BusinessDate)) % 7
WHEN 0 THEN DATEADD(DAY,2,BusinessDate) -- Saturday
WHEN 1 THEN DATEADD(DAY,1,BusinessDate) -- Sunday
ELSE BusinessDate
END AS Weekday)) BETWEEN 26
AND 28
OR [Month] = 12 -- Is christmas a week day?
AND Day BETWEEN 25 AND 26
AND IsWeekday = 1;
-- Match orig table Schema
ALTER TABLE #BusCalDet DROP COLUMN Day, Week, [Month], Quarter, [Year], DayOfWeek;
-- Insert in to the main table'
INSERT INTO dbo.BusinessCalendarDetails (BusinessDate,CreatedById,CreatedTime,BusinessCalendarId,IsWeekday,
IsHoliday,[Description])
-- Find Rows that are missing
SELECT DT.BusinessDate,DT.CreatedById,DT.CreatedTime,DT.BusinessCalendarId,DT.IsWeekday,DT.IsHoliday,
DT.[Description]
FROM #BusCalDet DT
LEFT JOIN BusinessCalendarDetails ON BusinessCalendarDetails.BusinessDate = DT.BusinessDate
WHERE BusinessCalendarDetails.Id IS NULL
ORDER BY DT.BusinessDate;-- id

Related

Easter / Good Friday for Sowflake Date Dim

Further Evolved : The code below satisfies what was wanted in earlier comments [if you follow history] :), my new question right below is stumping me.
Question: I want to have 'Week_Of_Year' Adjust every Sunday, the code below does it every Monday.. I have tried 1 and 0 but via alter session per Snowflake but no luck! Any idea how to have a new week triggered by Sunday not Monday (as that is what it is currently)?
ie the result I want is for Date:1/2/2005 to reflect Week_Of_Year as 2 , not 1.
alter session set week_of_year_policy = 1;
vs
alter session set week_of_year_policy = 0;
WORKING CODE
create or replace temporary table test_temptable
(
DATE_ID SMALLINT NOT NULL
,FULL_DATE DATE NOT NULL
,DATE Varchar(10) NOT NULL
,YEAR SMALLINT NOT NULL
,WEEK_OF_YEAR SMALLINT NOT NULL
,DAY_OF_YEAR SMALLINT NOT NULL
,QTR_NUMBER SMALLINT NOT NULL
,DAY_OF_QUARTER SMALLINT NOT NULL
,MONTH_OF_YEAR SMALLINT NOT NULL
,MONTH_NAME CHAR(3) NOT NULL --need to have full month name, if it comes to it maybe do if logic
,DAY_OF_MONTH SMALLINT NOT NULL
,DAY_OF_WEEK VARCHAR(9) NOT NULL
,DAY_NAME VARCHAR(12) NOT NULL
,DAY_IS_WEEKDAY boolean NOT Null
,DAY_IS_LAST_OF_MONTH boolean NOT Null
,DAY_OF_WEEK_IN_MONTH SMALLINT NOT NULL
,HOLIDAYUSA VARCHAR(80) ----left out NOT NULL on Purpose
--- ,DAY_IS_HOLIDAY boolean NOT NULL
)
AS
WITH MY_DATES AS (
SELECT DATEADD(DAY, SEQ4(), '2005-01-01') AS Full_DATE
,(seq8()+ 1) AS date_id
,DATE_TRUNC('QUARTER',Full_DATE) as Q
,DATEDIFF('day',Q, Full_DATE) as Day_of_Quarter
/*logic to support Easter Day calculation */
,Full_DATE as SinCurDay
,MONTH(Full_Date) as inCurMonth
,YEAR(Full_Date) as inCurYear
,FLOOR(inCurYear/100) as inCurCent
,inCurYear%19 as inYear
,FLOOR((inCurCent-17)/25) as inYearTmp
,(inCurCent-FLOOR(inCurCent/4)-FLOOR((inCurCent-inYearTmp)/3)+(19*inYear)+15)%30 as inTemp2a
,inTemp2a-FLOOR(inTemp2a/28)*(1 - FLOOR(inTemp2a/28)*FLOOR(29/(inTemp2a+1))*FLOOR((21-inYear)/11)) as inTemp2b
,(inCurYear+FLOOR(inCurYear/4)+inTemp2b+2-inCurCent+FLOOR(inCurCent/4))%7 as inTemp3
,inTemp2b-inTemp3 as inTemp4
,3+FLOOR((inTemp4+40)/44) as inEastMontha
,inTemp4+28-31*FLOOR(inEastMontha/4) as inEastDay
,inEastMontha /*- 1*/ as inEastMonthb
,Date_from_parts(inCurYear,inEastMonthb, inEastDay) as EasterDay
/*End Easter Day Logic */
/*Day of Week in Month*/
/* CASE
WHEN Day(Full_Date) < 8 THEN 1
WHEN Day(Full_Date) BETWEEN 7 AND 14 then 2
WHEN Day(Full_Date) BETWEEN 14 AND 21 then 3
WHEN Day(Full_Date) BETWEEN 21 AND 28 then 4
ELSE 5 End as Day_In_Month
*/
FROM TABLE(GENERATOR(ROWCOUNT=>365))
)
SELECT date_id
,Full_Date
,to_varchar(Full_Date, 'mm/dd/yyyy')
,YEAR(Full_Date)
,WEEKOFYEAR(Full_Date)
,DAYOFYEAR(Full_Date)
,QUARTER(Full_Date)
,Day_Of_Quarter + 1
,MONTH(Full_Date)
,MONTHNAME(Full_Date)
,DAY(Full_Date)
,DAYOFWEEK(Full_Date) + 1
,DAYNAME(Full_Date)
/*Weekend boolean */
,CASE
WHEN DAYOFWEEK(Full_date) + 1 = 7 THEN FALSE
WHEN DAYOFWEEK(Full_date) + 1 = 1 THEN FALSE
ELSE TRUE END
/*Last Day Of Month Boolean*/
,CASE
WHEN Full_Date = last_day(Full_Date) THEN True
ELSE FALSE END
/*Week in Month*/
---,CAST(Round((day(Full_Date) +6)/7,0) as VARCHAR)
,CASE
WHEN Day(Full_Date) < 8 THEN 1
WHEN Day(Full_Date) BETWEEN 7 AND 14 then 2
WHEN Day(Full_Date) BETWEEN 14 AND 21 then 3
WHEN Day(Full_Date) BETWEEN 21 AND 28 then 4
ELSE 5 End
/*HolidayUSA Logic */
,CASE
WHEN MONTH(Full_Date) = 10 AND DAY(Full_Date) = 31 THEN 'Halloween'
/*ThanksGiving*/
WHEN MONTH(Full_Date) = 11 AND DAYOFWEEK(Full_Date) + 1 = 5 AND
CASE
WHEN Day(Full_Date) < 8 THEN 1
WHEN Day(Full_Date) BETWEEN 7 AND 14 then 2
WHEN Day(Full_Date) BETWEEN 14 AND 21 then 3
WHEN Day(Full_Date) BETWEEN 21 AND 28 then 4
ELSE 5 End = 4
THEN 'Thanksgiving Day' -- should I add ()
WHEN MONTH(Full_Date) = 11 AND DAYOFWEEK(Full_Date) + 1 = 6 AND
CASE
WHEN Day(Full_Date) < 8 THEN 1
WHEN Day(Full_Date) BETWEEN 7 AND 14 then 2
WHEN Day(Full_Date) BETWEEN 14 AND 21 then 3
WHEN Day(Full_Date) BETWEEN 21 AND 28 then 4
ELSE 5 End = 4
THEN 'Black Friday' -- should I add ()
WHEN MONTH(Full_Date) = 12 AND DAY(Full_Date) = 25 THEN 'Christmas Day'
WHEN MONTH(Full_Date) = 7 AND DAY(Full_Date) = 4 THEN 'Independence Day'
WHEN MONTH(Full_Date) = 12 AND DAY(Full_Date) = 31 THEN 'New Years Eve'
WHEN MONTH(Full_Date) = 1 AND DAY(Full_Date) = 1 THEN 'New Years Day'
WHEN MONTH(Full_Date) = 5 AND DAYOFWEEK(Full_Date)+ 1 = 2 AND Day(Full_Date) > '24' then 'Memorial Day'
WHEN MONTH(Full_Date) = 9 AND DAYOFWEEK(Full_Date) + 1 = 2 AND Day(Full_Date) < '8'THEN 'Labor Day'
/*Martin Luther King Jr Day */
WHEN MONTH(Full_Date) = 1 AND DAYOFWEEK(Full_Date) + 1 = 2 AND
CASE
WHEN Day(Full_Date) < 8 THEN 1
WHEN Day(Full_Date) BETWEEN 7 AND 14 then 2
WHEN Day(Full_Date) BETWEEN 14 AND 21 then 3
WHEN Day(Full_Date) BETWEEN 21 AND 28 then 4
ELSE 5 End
= 3 THEN 'Martin Luther King Jr Day'
/*Presidents Day*/
WHEN MONTH(Full_Date) = 2 AND DAYOFWEEK(Full_Date) + 1 = 2 AND
CASE
WHEN Day(Full_Date) < 8 THEN 1
WHEN Day(Full_Date) BETWEEN 7 AND 14 then 2
WHEN Day(Full_Date) BETWEEN 14 AND 21 then 3
WHEN Day(Full_Date) BETWEEN 21 AND 28 then 4
ELSE 5 END
= 3 THEN 'Presidents Day'
WHEN MONTH(Full_Date) = 11 AND DAY(Full_Date) = 11 THEN 'Veterans Day'
/*Mothers Day */
WHEN MONTH(Full_Date) = 5 AND DAYOFWEEK(Full_Date) + 1 = 1 AND
CASE
WHEN Day(Full_Date) < 8 THEN 1
WHEN Day(Full_Date) BETWEEN 7 AND 14 then 2
WHEN Day(Full_Date) BETWEEN 14 AND 21 then 3
WHEN Day(Full_Date) BETWEEN 21 AND 28 then 4
ELSE 5 END
= 2 THEN 'Mothers Day'
/*Fathers Day */
WHEN MONTH(Full_Date) = 6 AND DAYOFWEEK(Full_Date) + 1 = 1 AND
CASE
WHEN Day(Full_Date) < 8 THEN 1
WHEN Day(Full_Date) BETWEEN 7 AND 14 then 2
WHEN Day(Full_Date) BETWEEN 14 AND 21 then 3
WHEN Day(Full_Date) BETWEEN 21 AND 28 then 4
ELSE 5 END
= 3 THEN 'Fathers Day'
WHEN MONTH(Full_Date) = 2 AND DAY(Full_Date) = 14 THEN 'Valentines Day'
WHEN Full_Date = EasterDay THEN 'Easter Day'
WHEN Full_Date = EasterDay - 2 THEN 'Good Friday'
ELSE NULL END
--- ,CASE
--- WHEN HOLIDAYUSA is not NULL THEN TRUE Else False
--- END
FROM MY_DATES
Order By Full_Date;
Evolved Question: Please view updated code, where I translated the T-SQL logic to Calculate Easter into something Snowflake could understand. The only issue is that for Year 2005 I am off by one day ( the code below returns March 28, 2005 for Easter but it is March 27, 2008).
Question: Can someone help me understand why the below line is so close but wrong, I have tried so many variations of rtrim, RIGHT, and other manipulations to the values that feed the date thinking it had to do with rounding down, but all making the return easter dates further off. Below is my closest for a 20 year span , all within about 1-5 days of actual.
Line that is causing error:date_from_parts(YR, '0' + rtrim(EasterMonth),'0' + rtrim(EasterDay)) AS test6
Easter Logic
(24 + 19 * (YR % 19)) % 30 AS EpactCalc,
EpactCalc - (EpactCalc / 28) AS PaschalDaysCalc,
PaschalDaysCalc - ((YR + YR / 4 + PaschalDaysCalc - 13) % 7) AS NumOfDaysToSunday,
3 + (NumOfDaysToSunday + 40) / 44 AS EasterMonth,
NumOfDaysToSunday + 28 - (31 * (EasterMonth / 4)) AS EasterDay, ---EasterMonth + RTRIM(YR) as test6
---to_date_from_parts(YR,(("0" + EasterMonth).substr(-2)), (("0" + EasterDay).substr(-2)) as test6
date_from_parts(YR, '0' + rtrim(EasterMonth),'0' + rtrim(EasterDay)) AS test6
Full Script:
CREATE OR REPLACE
TEMPORARY TABLE .test_temptable (Date_Id SMALLINT NOT NULL ,Full_Date DATE NOT NULL ,Date Varchar(10) NOT NULL ,YEAR SMALLINT NOT NULL ,WEEK_OF_YEAR SMALLINT NOT NULL ,DAY_OF_YEAR SMALLINT NOT NULL ,QTR_Number SMALLINT NOT NULL ,Day_Of_Quarter SMALLINT NOT NULL,MONTH_OF_YEAR SMALLINT NOT NULL ,MONTH_NAME CHAR(3) NOT NULL --need to have full month name, if it comes to it maybe do if logic
,DAY_OF_MONTH SMALLINT NOT NULL ,DAY_OF_WEEK VARCHAR(9) NOT NULL ,DAY_NAME VARCHAR(12) NOT NULL ,DAY_IS_WEEKDAY boolean NOT NULL,DAY_IS_LAST_OF_MONTH boolean NOT NULL ,DAY_OF_WEEK_IN_MONTH SMALLINT NOT NULL ,HOLIDAYUSA VARCHAR(80) ----left out NOT NULL on Purpose
,test1 smallint NOT NULL,test2 smallint NOT NULL ,test3 smallint NOT NULL ,test4 smallint NOT NULL,test5 smallint NOT NULL ,test6 DATE NOT NULL) AS WITH CTE_MY_DATE AS
(---Returns a sequence of monotonically increasing integers, with wrap-around. Wrap-around occurs after the largest representable integer of the integer width (1, 2, 4, or 8 byte)..??I'd like to understand this a tad bit better.Is SEQ4 for float?
SELECT DATEADD(DAY, SEQ4(), '2005-01-01') AS Full_DATE,
YEAR(Full_Date) AS YR,
(seq8()+ 1) AS date_id,
DATE_TRUNC('QUARTER',Full_DATE) AS q,
DATEDIFF('day',q, Full_DATE) AS Day_of_Quarter,
(24 + 19 * (YR % 19)) % 30 AS EpactCalc,
EpactCalc - (EpactCalc / 28) AS PaschalDaysCalc,
PaschalDaysCalc - ((YR + YR / 4 + PaschalDaysCalc - 13) % 7) AS NumOfDaysToSunday,
3 + (NumOfDaysToSunday + 40) / 44 AS EasterMonth,
NumOfDaysToSunday + 28 - (31 * (EasterMonth / 4)) AS EasterDay, ---EasterMonth + RTRIM(YR) as test6
---to_date_from_parts(YR,(("0" + EasterMonth).substr(-2)), (("0" + EasterDay).substr(-2)) as test6
date_from_parts(YR, '0' + rtrim(EasterMonth),'0' + rtrim(EasterDay)) AS test6
FROM TABLE(GENERATOR(ROWCOUNT=>9125))
)
SELECT date_id ,
Full_Date ,
to_varchar(Full_Date, 'mm/dd/yyyy') ,
YEAR(Full_Date) ,
WEEKOFYEAR(Full_Date) ,
DAYOFYEAR(Full_Date) ,
QUARTER(Full_Date) ,
Day_Of_Quarter + 1 ,
MONTH(Full_Date) ,
MONTHNAME(Full_Date) ,
DAY(Full_Date) ,
DAYOFWEEK(Full_Date) + 1 ,
DAYNAME(Full_Date) ---calculates if it is on weekend or not
,
CASE
WHEN DAYOFWEEK(Full_date) = 7 THEN FALSE
WHEN DAYOFWEEK(Full_date) = 1 THEN FALSE
ELSE TRUE
END ----calculates if last day of month
,
CASE
WHEN Full_Date = last_day(Full_Date) THEN TRUE
ELSE FALSE
END,
CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) -- this is what week you are in in the month, double check that what it ought to be
--- calculates holidays, is Thxgiving always in the fifth week?,
,
CASE
WHEN MONTH(Full_Date) = 10
AND DAY(Full_Date) = 31 THEN 'Halloween'
WHEN MONTH(Full_Date) = 11
AND DAYOFWEEK(Full_Date) + 1 = 4
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 5 THEN 'Thanksgiving Day'
WHEN MONTH(Full_Date) = 12
AND DAY(Full_Date) = 25 THEN 'Christmas Day'
WHEN MONTH(Full_Date) = 7
AND DAY(Full_Date) = 4 THEN 'Independence Day' --adding
WHEN MONTH(Full_Date) = 12
AND DAY(Full_Date) = 31 THEN 'New Years Eve'
WHEN MONTH(Full_Date) = 1
AND DAY(Full_Date) = 1 THEN 'New Years Day' ---memorial day attempt
WHEN MONTH(Full_Date) = 5
AND DAYOFWEEK(Full_Date)+ 1 = 2
AND Day(Full_Date) > '24' THEN 'Memorial Day' ---labor day
WHEN MONTH(Full_Date) = 9
AND DAYOFWEEK(Full_Date) + 1 = 2
AND Day(Full_Date) < '8'THEN 'Labor Day'
WHEN MONTH(Full_Date) = 1
AND DAYOFWEEK(Full_Date) = 2
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 3 THEN 'Martin Luther King Jr Day'
WHEN MONTH(Full_Date) = 2
AND DAYOFWEEK(Full_Date) = 2
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 3 THEN 'Presidents Day'
WHEN MONTH(Full_Date) = 11
AND DAY(Full_Date) = 11 THEN 'Veterans Day' ---added Mother's Day
WHEN MONTH(Full_Date) = 5
AND DAYOFWEEK(Full_Date) + 1 = 1
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 2 THEN 'Mothers Day'
WHEN MONTH(Full_Date) = 6
AND DAYOFWEEK(Full_Date) + 1 = 1
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 3 THEN 'Fathers Day'
WHEN MONTH(Full_Date) = 2
AND DAY(Full_Date) = 14 THEN 'Valentines Day' ---easter
---good friday
ELSE NULL
END ,
EpactCalc ,
PaschalDaysCalc ,
NumOfDaysToSunday ,
EasterMonth ,
EasterDay ,
test6
FROM CTE_MY_DATE;
Old Question:
I have this nice piece of code for snowflake users that I need a little help finishing. I specifically want to use the second chunk of code that was written for SqlServer to be used in Snowflakes env, and integrated into my script below (first chunk of code).
Specifically:
a)"How do you integrate a function into a query like this" as in Advice where to put the code inside my script above because I am having trouble understanding how to integrate a function within a select statement
b)"Is there anything glaring about this query that would make running it in Snowflake uniquely difficult" I attempted to run the SQLServer "easter date" code alone inside snowflake, and changed the variables to match snowflakes requirements (ie take out #) and then I got an error unexpected 'BEGIN'.
CREATE OR REPLACE
TEMPORARY TABLE test_temptable (Date_Id SMALLINT NOT NULL ,Full_Date DATE NOT NULL ,Date Varchar(10) NOT NULL ,YEAR SMALLINT NOT NULL ,WEEK_OF_YEAR SMALLINT NOT NULL ,DAY_OF_YEAR SMALLINT NOT NULL ,QTR_Number SMALLINT NOT NULL ,Day_Of_Quarter SMALLINT NOT NULL,MONTH_OF_YEAR SMALLINT NOT NULL ,MONTH_NAME CHAR(3) NOT NULL
,DAY_OF_MONTH SMALLINT NOT NULL ,DAY_OF_WEEK VARCHAR(9) NOT NULL ,DAY_NAME VARCHAR(12) NOT NULL ,DAY_IS_WEEKDAY boolean NOT NULL,DAY_IS_LAST_OF_MONTH boolean NOT NULL ,DAY_OF_WEEK_IN_MONTH SMALLINT NOT NULL ,HOLIDAYUSA VARCHAR(80)
(
SELECT DATEADD(DAY, SEQ4(), '2005-01-01') AS Full_DATE,
(seq8()+ 1) AS date_id,
DATE_TRUNC('QUARTER',Full_DATE) AS q,
DATEDIFF('day',q, Full_DATE) AS Day_of_Quarter
FROM TABLE(GENERATOR(ROWCOUNT=>366))
)
SELECT date_id ,
Full_Date ,
to_varchar(Full_Date, 'mm/dd/yyyy') ,
YEAR(Full_Date) ,
WEEKOFYEAR(Full_Date) ,
DAYOFYEAR(Full_Date) ,
QUARTER(Full_Date) ,
Day_Of_Quarter + 1 ,
MONTH(Full_Date) ,
MONTHNAME(Full_Date) ,
DAY(Full_Date) ,
DAYOFWEEK(Full_Date) + 1 ,
DAYNAME(Full_Date)
,
CASE
WHEN DAYOFWEEK(Full_date) = 7 THEN FALSE
WHEN DAYOFWEEK(Full_date) = 1 THEN FALSE
ELSE TRUE
END
,
CASE
WHEN Full_Date = last_day(Full_Date) THEN TRUE
ELSE FALSE
END,
CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR)
,
CASE
WHEN MONTH(Full_Date) = 10
AND DAY(Full_Date) = 31 THEN 'Halloween'
WHEN MONTH(Full_Date) = 11
AND DAYOFWEEK(Full_Date) + 1 = 4
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 5 THEN 'Thanksgiving Day'
WHEN MONTH(Full_Date) = 12
AND DAY(Full_Date) = 25 THEN 'Christmas Day'
WHEN MONTH(Full_Date) = 7
AND DAY(Full_Date) = 4 THEN 'Independence Day' --adding
WHEN MONTH(Full_Date) = 12
AND DAY(Full_Date) = 31 THEN 'New Years Eve'
WHEN MONTH(Full_Date) = 1
AND DAY(Full_Date) = 1 THEN 'New Years Day' ---memorial day attempt
WHEN MONTH(Full_Date) = 5
AND DAYOFWEEK(Full_Date)+ 1 = 2
AND Day(Full_Date) > '24' THEN 'Memorial Day' ---labor day
WHEN MONTH(Full_Date) = 9
AND DAYOFWEEK(Full_Date) + 1 = 2
AND Day(Full_Date) < '8'THEN 'Labor Day'
WHEN MONTH(Full_Date) = 1
AND DAYOFWEEK(Full_Date) = 2
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 3 THEN 'Martin Luther King Jr Day'
WHEN MONTH(Full_Date) = 2
AND DAYOFWEEK(Full_Date) = 2
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 3 THEN 'Presidents Day'
WHEN MONTH(Full_Date) = 11
AND DAY(Full_Date) = 11 THEN 'Veterans Day' ---added Mother's Day
WHEN MONTH(Full_Date) = 5
AND DAYOFWEEK(Full_Date) + 1 = 1
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 2 THEN 'Mothers Day'
WHEN MONTH(Full_Date) = 6
AND DAYOFWEEK(Full_Date) + 1 = 1
AND CAST(Round((day(Full_Date) +6)/7,0) AS VARCHAR) = 3 THEN 'Fathers Day'
WHEN MONTH(Full_Date) = 2
AND DAY(Full_Date) = 14 THEN 'Valentines Day' ---easter
---good friday
ELSE NULL
END
FROM CTE_MY_DATE;
Below is the SQLServer code I need help to input the above!! (thanks Function to return date of Easter for the given year)
CREATE FUNCTION dbo.GetEasterSunday
( #Y INT )
RETURNS SMALLDATETIME
AS
BEGIN
DECLARE #EpactCalc INT,
#PaschalDaysCalc INT,
#NumOfDaysToSunday INT,
#EasterMonth INT,
#EasterDay INT
SET #EpactCalc = (24 + 19 * (#Y % 19)) % 30
SET #PaschalDaysCalc = #EpactCalc - (#EpactCalc / 28)
SET #NumOfDaysToSunday = #PaschalDaysCalc - (
(#Y + #Y / 4 + #PaschalDaysCalc - 13) % 7
)
SET #EasterMonth = 3 + (#NumOfDaysToSunday + 40) / 44
SET #EasterDay = #NumOfDaysToSunday + 28 - (
31 * (#EasterMonth / 4)
)
RETURN
(
SELECT CONVERT
( SMALLDATETIME,
RTRIM(#Y)
+ RIGHT('0'+RTRIM(#EasterMonth), 2)
+ RIGHT('0'+RTRIM(#EasterDay), 2)
)
)
END
GO
New answer:
You must use month and day numerical values directly, not reformat as TEXT:
DATE_FROM_PARTS(Year, EasterMonth, EasterDay)
Old answer:
It should be fairly simple to convert your T-SQL function to a Snowflake JavaScript function. Maybe you have to learn JavaScript on the way, though.
The skeleton of such a function can be:
CREATE OR REPLACE FUNCTION GetEasterSunday(Y FLOAT) RETURNS STRING LANGUAGE JAVASCRIPT AS
$$
var EpactCalc = (24 + 19 * (Y % 19)) % 30;
// more stuff here
var EasterMonth = 4, EasterDay = 21;
return Y + "-" + ("0" + EasterMonth).substr(-2) + "-" + ("0" + EasterDay).substr(-2);
$$;
SELECT GetEasterSunday(2019)::DATE;
It seems that T-SQL truncates values when you use integers. It does not take into count the fractional part at all.
In T-SQL it goes like this:
select -2 + 28 - (31 * (3 / 4)) as [EasterDay]
Returns: 26
In snowflake
select -2 + 28 - (31 * (3 / 4)) as [EasterDay]
Returns: 2.75
But if you truncate the value 3 / 4 = 0.75 , it gives same as T-SQL (because if you omit the fractional part it is trunc(0.75)=0:
select -2 + 28 - (31 * trunc(3 / 4)) as [EasterDay]
Returns: 26
So in T-SQL like this:
WITH years as(
SELECT 2009 AS [Year]
UNION ALL
SELECT yl.[Year] + 1 AS [Year]
FROM years yl
WHERE yl.[Year] + 1 <= YEAR(dateadd(year,5,GETDATE()))
),
e AS
(
SELECT d.[Year], [EasterDate] = CONVERT(DATE, RTRIM([Year]) + '0' + RTRIM([Month])
+ RIGHT('0' + RTRIM([Day]),2))
FROM (SELECT [Year],[Month], [Day] = DaysToSunday + 28 - (31 * ([Month] / 4))
FROM (SELECT [Year],[Month] = 3 + (DaysToSunday + 40) / 44, DaysToSunday
FROM (SELECT [Year],DaysToSunday = paschal - (([Year] + [Year] / 4 + paschal - 13) % 7 )
FROM (SELECT [Year],paschal = epact - (epact / 28)
FROM (SELECT years.[Year], epact = (24 + 19 * (years.[Year] % 19)) % 30 from years) AS epact) AS paschal) AS dts) AS m) AS d
)
select *
from(
SELECT [Year],[EasterDate], HolidayName = 'Easter Sunday' FROM e
UNION ALL SELECT [Year],DATEADD(DAY,-2,[EasterDate]), 'Good Friday' FROM e
UNION ALL SELECT [Year],DATEADD(DAY, 1,[EasterDate]), 'Easter Monday' FROM e
UNION ALL SELECT [Year],DATEADD(DAY, 39,[EasterDate]), 'Ascension Day' FROM e --NOTE! 1973–1991 this was moved to always to previous saturday!
UNION ALL SELECT [Year],DATEADD(DAY, 49,[EasterDate]), 'Whit Sunday' FROM e --NOTE! 7th sunday from Easter Sunday
) easter
order by easter.[Year],[EasterDate]
In Snowflake like this
WITH years as(
--select distinct "YearNumber" as "Year" FROM dm.D_DAY
select year(dateadd(year, seq4(), dateadd(year,-13,current_date()))) as "Year"
from
table(generator(rowcount => 19))
),
e AS
(
SELECT "EasterYear","EasterMonth", "EasterDay","DaysToSunday","epact","paschal",
DATE_FROM_PARTS("EasterYear", "EasterMonth", "EasterDay") as "EasterDate"
FROM (SELECT "EasterYear","epact","paschal","EasterMonth" AS "EasterMonth","DaysToSunday", "DaysToSunday" + 28 - (31 * trunc("EasterMonth" / 4)) as "EasterDay"
FROM (SELECT "EasterYear","epact","paschal",trunc(3 + ("DaysToSunday" + 40) / 44,0) as "EasterMonth", "DaysToSunday"
FROM (SELECT "EasterYear","epact","paschal","paschal" - trunc(mod(("EasterYear" + "EasterYear" / 4 + "paschal" - 13),7 )) as "DaysToSunday"
FROM (SELECT "EasterYear","epact","epact" - trunc("epact" / 28) as "paschal"
FROM (SELECT years."Year" as "EasterYear", mod((24 + (19 * mod(years."Year",19))),30) as "epact" from years) AS "epact") AS "paschal") AS "dts") AS "m") AS d
)
select *
from( SELECT "EasterYear","EasterDate",'Easter Sunday' as "HolidayName" FROM e
UNION ALL SELECT "EasterYear",DATEADD(DAY,-2,"EasterDate"), 'Good Friday' FROM e
UNION ALL SELECT "EasterYear",DATEADD(DAY, 1,"EasterDate"), 'Easter Monday' FROM e
UNION ALL SELECT "EasterYear",DATEADD(DAY, 39,"EasterDate"), 'Ascension Day' FROM e --NOTE! 1973–1991 this was moved to always to previous saturday!
UNION ALL SELECT "EasterYear",DATEADD(DAY, 49,"EasterDate"), 'Whit Sunday' FROM e --NOTE! 7th sunday from Easter Sunday
) easter
order by easter."EasterYear","EasterDate"

Building a table of year and quarter combinations

Say I have a year and a quarter and I want to build a table lists a specific number of year and quarter combinations ending with the given year and quarter.
E.g.
**Input:**
Year = 2018
Quarter = 3
NumRows = 4
**Output:**
Year.....|Quarter
2018.....|3
2018.....|2
2018.....|1
2017.....|4
You can use recursive generation.
First you need to build a DATETIME object from the year and quarter.
DECLARE #Quarter INT = 3;
DECLARE #Year INT = 2018;
DECLARE #NumRows INT = 4;
DECLARE #initial_date DATETIME = DATEADD(quarter, #Quarter-1, DATEADD(year, #Year-1900, 0));
Then you can recursively generate the year & quarter combinations like this.
;WITH quarters AS (
SELECT #initial_date AS [qdate]
UNION ALL
SELECT DATEADD(quarter, -1, [qdate]) AS [qdate]
FROM quarters
WHERE [qdate] > DATEADD(quarter, -1*(#NumRows-1), #initial_date)
)
SELECT DATEPART(year, qdate) as year, DATEPART(quarter, qdate) as quarter FROM quarters
this uses a recursive cte
for
declare #year int = 2018,
#quarter int = 3,
#numrows int = 6
; with rcte as
(
select n = 1,
yr = #year,
qtr = #quarter
union all
select n = n + 1,
yr = yr - (((qtr - 1 + 4 - 1) % 4 + 1) / 4),
qtr = (qtr - 1 + 4 - 1) % 4 + 1
from rcte
where n < #numrows
)
select *
from rcte
order by n
/* result:
n yr qtr
1 2018 3
2 2018 2
3 2018 1
4 2017 4
5 2017 3
6 2017 2
*/
You can try to use cte recursive with some calculation
DECLARE #Year INT= 2018
DECLARE #Quarter INT = 3
DECLARE #NumRows INT= 10
;WITH CTE AS (
SELECT (#Year - #NumRows/4) yr,
4 - (#NumRows % 4) Quarter,
#NumRows NumRows
UNION ALL
SELECT yr + Quarter / 4,
CASE WHEN (Quarter + 1) % 4 = 0 THEN 4
ELSE (Quarter + 1) % 4
END,
NumRows- 1
FROM CTE
WHERE NumRows > 1
)
select yr,Quarter
from cte
sqlfiddle

Collapse down calendar table by grouping like fields

I have a script that generates a calendar table and fills in the public holidays and weekends for England and Wales. I would like to have the non working days as start and end periods, so taking the input
1980-04-01 00:00:00.000 Tuesday 0
1980-04-02 00:00:00.000 Wednesday 0
1980-04-03 00:00:00.000 Thursday 0
1980-04-04 00:00:00.000 Friday 1
1980-04-05 00:00:00.000 Saturday 1
1980-04-06 00:00:00.000 Sunday 1
1980-04-07 00:00:00.000 Monday 1
1980-04-08 00:00:00.000 Tuesday 0
1980-04-09 00:00:00.000 Wednesday 0
It would give the output
Start end
1980-04-03 1980-04-08
in other words, the day before the first non working day and the day after the last non working day of the group. To do this I would presumably use a min and max with a group and dateadd, but how do I go about obtaining a unique number to group each set of days by? This is using 2008 R2.
IF OBJECT_ID('tempdb.dbo.#calendar', 'U') IS NOT NULL
DROP TABLE #calendar
IF OBJECT_ID('tempdb.dbo.#easter', 'U') IS NOT NULL
DROP TABLE #easter;
-- CREATE TABLE CALENDAR
CREATE TABLE #calendar
(
[CalendarDate] DATETIME,
dayofwk varchar(20),
)
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
--INPUTS GO HERE
SET #StartDate = '01-01-1980'
SET #EndDate = '31-12-2018'
DECLARE #Startyear int
DECLARE #endyear int
set #startyear = YEAR(#StartDate)
set #endyear = YEAR(#EndDate)
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #calendar
(
CalendarDate, dayofwk
)
SELECT
#StartDate, datename(dw,#startdate)
SET #StartDate = DATEADD(dd, 1, #StartDate)
END
---CREATE LIST OF EASTER MONDAY & GOOD FRIDAYS
create table #easter(
eastersunday_goodfriday date)
DECLARE #y int,
#EpactCalc INT,
#PaschalDaysCalc INT,
#NumOfDaysToSunday INT,
#EasterMonth INT,
#EasterDay INT
WHILE #Startyear <= #endyear
BEGIN
SET #y = #startyear
SET #EpactCalc = (24 + 19 * (#Y % 19)) % 30
SET #PaschalDaysCalc = #EpactCalc - (#EpactCalc / 28)
SET #NumOfDaysToSunday = #PaschalDaysCalc - (
(#Y + #Y / 4 + #PaschalDaysCalc - 13) % 7
)
SET #EasterMonth = 3 + (#NumOfDaysToSunday + 40) / 44
SET #EasterDay = #NumOfDaysToSunday + 28 - (
31 * (#EasterMonth / 4)
)
insert into #easter
SELECT dateadd(d,-2, CONVERT
( SMALLDATETIME,
RTRIM(#Y)
+ RIGHT('0'+RTRIM(#EasterMonth), 2)
+ RIGHT('0'+RTRIM(#EasterDay), 2) ))
insert into #easter
SELECT dateadd(d,1, CONVERT
( SMALLDATETIME,
RTRIM(#Y)
+ RIGHT('0'+RTRIM(#EasterMonth), 2)
+ RIGHT('0'+RTRIM(#EasterDay), 2) ))
SET #Startyear =#Startyear +1
end
select calendardate, dayofwk,
--NEW YEAR'S DAY
case
when day(calendardate) = 1 and month(calendardate) = 1 and dayofwk not in ('Saturday', 'sunday') then 1
when day(calendardate) between 2 and 3 and month(calendardate) = 1 and dayofwk = 'Monday' then 1
--GOOD FRIDAY, EASTER MONDAY
WHEN eastersunday_goodfriday IS NOT NULL THEN 1
--EARLY MAY BANK HOLIDAY
WHEN (datepart(weekday, CALENDARDATE) + ##DATEFIRST - 2) % 7 + 1 = 1
and (datepart(day, CALENDARDATE) - 1) / 7 + 1 = 1
AND MONTH(CALENDARDATE) = 5
THEN 1
--LATE MAY BANK HOLIDAY
WHEN (datepart(weekday, CALENDARDATE) + ##DATEFIRST - 2) % 7 + 1 = 1
and (datepart(day, CALENDARDATE) - 1) / 7 + 1 = 4
AND MONTH(CALENDARDATE) = 5
THEN 1
--LATE AUGUST BANK HOLIDAY
WHEN (datepart(weekday, CALENDARDATE) + ##DATEFIRST - 2) % 7 + 1 = 1
and (datepart(day, CALENDARDATE) - 1) / 7 + 1 = 4
AND MONTH(CALENDARDATE) = 8
THEN 1
--CHRISTMAS DAY
WHEN DAY(CalendarDate) = 25 AND MONTH(CALENDARDATE) = 12 AND dayofwk NOT IN ('SATURDAY', 'SUNDAY') THEN 1
--BOXING DAY
WHEN DAY(CalendarDate) = 26 AND MONTH(CALENDARDATE) = 12 AND dayofwk NOT IN ('SATURDAY', 'SUNDAY') THEN 1
WHEN DAY(CalendarDate) between 27 and 28 AND MONTH(CALENDARDATE) = 12 AND DAYOFWK IN ('MONDAY','TUESDAY') THEN 1
--SAT&SUN
WHEN DATENAME(DW,CALENDARDATE) IN ('SATURDAY','SUNDAY') THEN 1
ELSE 0
end as ISNONWORKINGDAY
from #calendar c
left join #easter e
on c.calendardate = e.eastersunday_goodfriday
ORDER BY C.CALENDARDATE
This seams like a typical island problem. Given what you posted you could do this:
DECLARE #table TABLE (dt DATE, isNonWorkDay BIT);
INSERT #table(dt,isNonWorkDay)
VALUES
('1980-04-01', 0),
('1980-04-02', 0),
('1980-04-03', 0),
('1980-04-04', 1),
('1980-04-05', 1),
('1980-04-06', 1),
('1980-04-07', 1),
('1980-04-08', 0),
('1980-04-09', 0);
SELECT DATEADD(DAY,-1,MIN(t.dt)), DATEADD(DAY,1,MAX(t.dt))
FROM #table t
WHERE t.isNonWorkDay = 1;
Returns: 1980-04-03, 1980-04-08
For multiple islands you could do this:
DECLARE #table TABLE (dt DATE, isNonWorkDay BIT);
INSERT #table(dt,isNonWorkDay)
VALUES
('1980-04-01', 0),
('1980-04-02', 0),
('1980-04-03', 0),
('1980-04-04', 1),
('1980-04-05', 1),
('1980-04-06', 1),
('1980-04-07', 1),
('1980-04-08', 0),
('1980-04-09', 0),
('1980-04-10', 0),
('1980-04-11', 1),
('1980-04-12', 1),
('1980-04-13', 1),
('1980-04-14', 0),
('1980-04-15', 0);
WITH x AS
(
SELECT *, rn = ROW_NUMBER() OVER (ORDER BY t.dt)
FROM #table t
),
xx AS
(
SELECT *, grouper = x.rn - ROW_NUMBER() OVER (ORDER BY x.dt)
FROM x
WHERE x.isNonWorkDay = 1
)
SELECT dtStart = DATEADD(DAY,-1,MIN(xx.dt)), dtend = DATEADD(DAY,1,MAX(xx.dt))
FROM xx
GROUP BY xx.grouper;
Returns:
dtStart dtend
---------- ----------
1980-04-03 1980-04-08
1980-04-10 1980-04-14
You can use the difference of a consecutive numbering of all days and a consecutive numbering of all non-workingdays to use for the grouping.
For a consecutive group of non-workingdays, these differences will all have the same value, and because of the gaps (workingsdays) between the groups, the next group will have a different (higher) value.
For numbering all days, you can use DATEDIFF, for the numbering of non-workingdays you can use ROW_NUMBER. The query could look like this:
WITH
numbering (calendardate, grp_num) AS (
SELECT
calendardate,
DATEDIFF(day, 0, calendardate) - ROW_NUMBER() OVER (ORDER BY calendardate)
FROM YourTable WHERE ISNONWORKINGDAY = 1
)
SELECT
DATEADD(day, -1, MIN(calendardate)) AS StartDate,
DATEADD(day, +1, MAX(calendardate)) AS EndDate
FROM numbering GROUP BY grp_num;

loop dates and return weeks tsql

I am very new in SQL (started just 3 days back) and I got an issue and would hope someone is able to assist me.
I have a query that creates date loops to return me the week number based on the passed in dates. The idea here is to pass loop through the days, months and years and return me a week value. But because of these, I have been getting multiple entries of the same result.
For eg. I am getting:
period_wk_key period_yr_key period_week period_week_day period_week_full_desc
--------------- -------------- ------------ -------------- -----------------------
200001 2000 1 6 2000 WEEK 1
200001 2000 1 6 2000 WEEK 1
200001 2000 1 6 2000 WEEK 1
. . . . .
200002 2000 2 6 2000 WEEK 2
200002 2000 2 6 2000 WEEK 2
200002 2000 2 6 2000 WEEK 2
. . . . .
200003 2000 3 6 2000 WEEK 3
each period_wk_key is returning me 7 similar rows which is not what I wanted.
The ideal situation should be:
period_wk_key period_yr_key period_week period_week_day period_week_full_desc
--------------- -------------- ------------ -------------- -----------------------
200001 2000 1 6 2000 WEEK 1
200002 2000 2 6 2000 WEEK 2
200003 2000 3 6 2000 WEEK 3
200004 2000 4 6 2000 WEEK 4
I need to know a way to limit the number of output from a loop so that I can have distinct records instead of multiple similar rows. I have attached a code below. It maybe crap to many of you but this is just my 3rd day beginning scripting. Much thanks for any help rendered.
DECLARE #iStartYear INT
SET #iStartYear = 2000
DECLARE #iEndYear INT
SET #iEndYear = 2030
DECLARE #iMth INT
SET #iMth = 1
DECLARE #iDay INT
SET #iDay = 1
DECLARE #iWeek INT
`DECLARE #StartDate DATETIME
WHILE (#iStartYear <= #iEndYear)
BEGIN
WHILE (#iMth <= 12)
BEGIN
WHILE (#iDay <= DATEDIFF(DAY,DATEADD(DAY, 0, DATEADD(m, ((#iStartYear - 1900) * 12) + #iMth - 1, 0)),DATEADD(DAY, 0, DATEADD(m, ((#iStartYear - 1900) * 12) + #iMth, 0)))))
BEGIN `
SET #iWeek = (DATEPART(dy, CONVERT(DATETIME, CONVERT(VARCHAR(4), #iStartYear) + '/' + CONVERT(VARCHAR(2), #iMth) + '/' + CONVERT(VARCHAR(2), #iDay))) - 1) / 7 + 1;
INSERT INTO dim_period_week (period_wk_key, period_yr_key, period_week, period_week_full_desc,start_date, end_date, period_week_day)
VALUES (
(SELECT CASE WHEN #iWeek < 10 THEN
CAST((#iStartYear) AS VARCHAR) + '0' + CAST((#iWeek) AS VARCHAR)
ELSE
CAST((#iStartYear) AS VARCHAR)+ CAST((#iWeek) AS VARCHAR)
END),
#iStartYear,
(SELECT (DATEPART(dy, CONVERT(DATETIME, CONVERT(VARCHAR(4), #iStartYear) + '/' + CONVERT(VARCHAR(2), #iMth) + '/' + CONVERT(VARCHAR(2), #iDay))) - 1) / 7 + 1),
(SELECT CAST((#iStartYear) AS VARCHAR) + ' ' + 'WEEK' + ' ' + CAST((#iWeek) AS VARCHAR)),
(SELECT DATEADD(wk,#iWeek-1, DATEADD(yy,#iStartYear-1900,0))), --START DATE
(SELECT DATEADD(wk,#iWeek, DATEADD(yy,#iStartYear-1900,0)) -1), --END DATE
(SELECT DATEDIFF(DAY, DATEADD(wk,#iWeek-1, DATEADD(yy,#iStartYear-1900,0)), DATEADD(wk,#iWeek, DATEADD(yy,#iStartYear-1900,0)) -1))
)
`SET #iDay = #iDay + 1
END
SET #iDay = 1
SET #iMth = #iMth + 1
END
SET #iMth = 1
SET #iStartYear = #iStartYear + 1
END
`
You are looping every day, but never storing at the day level, so you are doing 7 times as many INSERTs as needed. Add the following just above your INSERT statement
IF DATEPART(weekday, CAST(CAST(#iStartYear * 10000 + #iMth * 100 + #iDay AS VARCHAR(8)) AS datetime)) = 1
INSERT INTO dim_period_week ...
Below is a set based approach using a numbers table.
IF OBJECT_ID('tempdb.dbo.#Numbers') IS NOT NULL DROP TABLE #Numbers
GO
DECLARE #startDate DATETIME = '20000101'
SELECT
(a.Number * 256) + b.Number AS Number
INTO #Numbers
FROM
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) a (Number),
(SELECT number FROM master..spt_values WHERE type = 'P' AND number <= 255) b (Number)
WHERE
(a.Number * 256) + b.Number BETWEEN 0 AND 1617
SELECT
WeekKey = CAST(YEAR(DATEADD(day, N.Number * 7, #startDate)) * 100 + DATEPART(week, DATEADD(day, N.Number * 7, #startDate)) AS VARCHAR(10)),
TheYear = YEAR(DATEADD(day, N.Number * 7, #startDate)),
TheWeek = DATEPART(week, DATEADD(day, N.Number * 7, #startDate)),
TheWeekDay= DATEPART(weekday, DATEADD(day, N.Number * 7, #startDate)),
WeekDetail= DATENAME(year, DATEADD(day, N.Number * 7, #startDate)) + ' WEEK ' + DATENAME(week, DATEADD(day, N.Number * 7, #startDate)),
StartDate = DATEADD(day, N.Number * 7, #startDate),
EndDate = DATEADD(day, 6, DATEADD(day, N.Number * 7, #startDate))
FROM #Numbers N
ORDER BY 1

Increment row depending on value of another column

I have a sql query below, where dtMontno could start from any month and am adding Row column manually as below :
SELECT COUNT(*) as count,
MONTH(TourTbl.DT_Started) as dtMonthno,
DATENAME(YYYY, TourTbl.DT_Started) as dtYear,
row_number() over (order by DATENAME(YYYY, TourTbl.DT_Started) asc,
MONTH(TourTbl.DT_Started) asc ) as Row
FROM TourTbl
INNER JOIN BranchTbl ON TourTbl.BranchID = BranchTbl.BranchID
INNER JOIN AgencyTbl on AgencyTbl.AgencyID = BranchTbl.AgencyID
WHERE Cancelled = 0 AND
(TourTbl.DT_Started >= '2010/03/15' and
TourTbl.DT_Started <= '2012/03/15') AND
AgencyTbl.AgencyID in ( 245 ) and
BranchRODID > 0
group by datename(M, TourTbl.DT_Started),
DATENAME(YYYY, TourTbl.DT_Started),
MONTH(TourTbl.DT_Started)
order by dtYear asc, dtMonthno asc
now my result is :
count dtMonthno dtYear Row
6 5 2011 1
8 6 2011 2
2 7 2011 3
23 8 2011 4
126 9 2011 5
101 10 2011 6
85 11 2011 7
92 12 2011 8
115 1 2012 9
102 2 2012 10
48 3 2012 11
Is there any way to start the Row column depending on the dtMonthno and increment by one in the example above would start from 5 and end in 15?
Thanks
Try changing the derivation of Row to:
row_number() over (order by YEAR(TourTbl.DT_Started) asc,
MONTH(TourTbl.DT_Started) asc ) +
min(YEAR(TourTbl.DT_Started)*12+MONTH(TourTbl.DT_Started)-1) OVER () % 12 as Row
You can add month of first DT_Started date:
SELECT COUNT(*) as count,
MONTH(TourTbl.DT_Started) as dtMonthno,
DATENAME(YYYY, TourTbl.DT_Started) as dtYear,
row_number() over (order by DATENAME(YYYY, TourTbl.DT_Started) asc,
MONTH(TourTbl.DT_Started) asc )
+ substring(min(DATENAME(YYYY, [TourTbl].DT_Started) + right ('0' + str (MONTH([TourTbl].DT_Started), 2), 2)) over (), 5, 2) - 1 as Row
FROM TourTbl
INNER JOIN BranchTbl ON TourTbl.BranchID = BranchTbl.BranchID
INNER JOIN AgencyTbl on AgencyTbl.AgencyID = BranchTbl.AgencyID
WHERE Cancelled = 0 AND
(TourTbl.DT_Started >= '2010/03/15' and
TourTbl.DT_Started <= '2012/03/15') AND
AgencyTbl.AgencyID in ( 245 ) and
BranchRODID > 0
group by datename(M, TourTbl.DT_Started),
DATENAME(YYYY, TourTbl.DT_Started),
MONTH(TourTbl.DT_Started)
order by dtYear asc, dtMonthno asc
I would truncate the dates to months and group by those values, then obtain years, months and row numbers based on the truncated dates:
SELECT
COUNT(*) AS count,
MONTH(GroupMonth) AS dtMonthno,
DATENAME(YYYY, GroupMonth) AS dtYear, /* why do you want year as a string? */
ROW_NUMBER() OVER (ORDER BY GroupMonth) + MONTH(MIN(GroupMonth) OVER ()) - 1 AS Row
FROM (
SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, TourTbl.DT_Started), 0) AS GroupMonth
FROM TourTbl
INNER JOIN BranchTbl ON TourTbl.BranchID = BranchTbl.BranchID
INNER JOIN AgencyTbl on AgencyTbl.AgencyID = BranchTbl.AgencyID
WHERE Cancelled = 0 AND
(TourTbl.DT_Started >= '2010/03/15' and
TourTbl.DT_Started <= '2012/03/15') AND
AgencyTbl.AgencyID in ( 245 ) and
BranchRODID > 0
) s
GROUP BY GroupMonth