SQL Server Weekdays function - tsql

I'm using the following stored procedure to get an array of dates based on a weeknumber:
ALTER FUNCTION [dbo].[udfGetweekdays] (
#year INT,
#weekno INT
)
RETURNS TABLE
AS
RETURN
SELECT DATEADD(wk,#WeekNo-1,DATEADD(yy,#Year-1900,0)) AS Sunday,
DATEADD(day, 2, DATEADD(wk,#WeekNo-1,DATEADD(yy,#Year-1900,0))) as Monday,
DATEADD(day, 3, DATEADD(wk,#WeekNo-1,DATEADD(yy,#Year-1900,0))) as Tuesday,
DATEADD(day, 4, DATEADD(wk,#WeekNo-1,DATEADD(yy,#Year-1900,0))) as Wednesday,
DATEADD(day, 5, DATEADD(wk,#WeekNo-1,DATEADD(yy,#Year-1900,0))) as Thursday,
DATEADD(day, 6, DATEADD(wk,#WeekNo-1,DATEADD(yy,#Year-1900,0))) as Friday,
DATEADD(day, 7, DATEADD(wk,#WeekNo-1,DATEADD(yy,#Year-1900,0))) as Saturday
And I'm calling it with....
DECLARE #WeekNo int= convert(int,DATEPART(week, getdate()));
DECLARE #Year int=convert(int, DATEPART(year, getdate()))
Select * from dbo.udfGetweekdays(#WeekNo,#year)
But the error says...
Msg 517, Level 16, State 1, Line 6
Adding a value to a 'datetime' column caused an overflow.
What am I doing wrong?

Related

Calculate Fiscal Year

I am generating a case statement that will be a master shared dataset for our report date ranges since we frequently use the same date ranges. Currently I have previous week, previous month, previous year, current week, current month and current year. I would like to add our fiscal year which is (10/1 to 9/30) as well but I haven't been able to come up with a pure SQL way to do it.
DECLARE #frequency as nvarchar(20);
SET #frequency = 'CURRENT MONTH'
SELECT
CASE #frequency
WHEN 'PREVIOUS MONTH' THEN CONVERT(DATE,DATEADD(m,DATEDIFF(m,0,GETDATE())-1,0))
WHEN 'CURRENT MONTH' THEN CONVERT(DATE,DATEADD(dd, 0, DATEDIFF(dd, 0, DATEADD(dd,-(DAY(GETDATE())-1),GETDATE()))))
WHEN 'CURRENT WEEK' THEN CONVERT(DATE,DATEADD(dd, 1, DATEDIFF(dd, 0,DATEADD(DAY, 1 - DATEPART(WEEKDAY, GETDATE()), CAST(GETDATE() AS DATETIME)))))
WHEN 'PREVIOUS WEEK' THEN CONVERT(DATE,DATEADD(dd, -6, DATEDIFF(dd, 0,DATEADD(DAY, 1 - DATEPART(WEEKDAY, GETDATE()), CAST(GETDATE() AS DATETIME)))))
WHEN 'CURRENT YEAR' THEN CONVERT(DATE, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0))
WHEN 'PREVIOUS YEAR' THEN CONVERT(DATE, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()) - 1, 0))
END as [START DATE],
CASE #frequency
WHEN 'PREVIOUS MONTH' THEN CONVERT(DATE,DATEADD(dd, 0, DATEDIFF(dd, 0,DATEADD(ms,-2,DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)))))
WHEN 'CURRENT MONTH' THEN CONVERT(DATE,DATEADD(D, -1, DATEADD(MONTH, DATEDIFF(MONTH, '19000101', GETDATE()) + 1, '19000101')))
WHEN 'CURRENT WEEK' THEN CONVERT(DATE,DATEADD(dd, 1, DATEDIFF(dd, 0,DATEADD(DAY, 7 - DATEPART(WEEKDAY, GETDATE()), CAST(GETDATE() AS DATETIME)))))
WHEN 'PREVIOUS WEEK' THEN CONVERT(DATE,DATEADD(dd, -6, DATEDIFF(dd, 0,DATEADD(DAY, 7 - DATEPART(WEEKDAY, GETDATE()), CAST(GETDATE() AS DATETIME)))))
WHEN 'CURRENT YEAR' THEN CONVERT(DATE, DATEADD(ms, -2, DATEADD(YEAR, 0, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()) + 1, 0))))
WHEN 'PREVIOUS YEAR' THEN CONVERT(DATE, DATEADD(ms, -2, DATEADD(YEAR, 0, DATEADD(YEAR, DATEDIFF(YEAR, 0, GETDATE()), 0))))
END as [END DATE]
I've found the simplest way to perform this logic is to shift your date by the offset. If 10/1/2018 belongs to FY 2018, subtract the 9 months to convert the date to 1/1/2018. Then get the year value.
YEAR(DATEADD(MONTH,-9,'2018-09-01')) = 2017
YEAR(DATEADD(MONTH,-9,'2019-09-01')) = 2018
YEAR(DATEADD(MONTH,-9,[YourDateField]))
With this, you can also calculate the Fiscal Month
MONTH(DATEADD( month, -9, '2018-09-01 )) = 12
My company use the Year the FY ends, so 10/1/2018 would be part of the FY closing 2019. For my arrangement, I would need to add 3 months instead of subtract to get the calculation to come out right.
This approach leverages date math exclusively so should be able to use an index in most situations.
To find the given day for the datepart you're looking for, try this:
SELECT CurrentWeek = DATEADD( WEEK, DATEDIFF( WEEK, '2018-01-01', GETDATE()), '2018-01-01' ),
CurrentMonth = DATEADD( MONTH, DATEDIFF( MONTH, '2018-01-01', GETDATE()), '2018-01-01' ),
CurrentYear = DATEADD( YEAR, DATEDIFF( YEAR, '2018-01-01', GETDATE()), '2018-01-01' )
;
By basing the calculation on a seed date, you don't need to truncate the smaller time values.
According to your calculations, this will work:
DECLARE #frequency as nvarchar(20);
SET #frequency = 'FINANCIAL YEAR'
SELECT
CASE #frequency
WHEN 'FINANCIAL YEAR' THEN CONVERT(date, DATEADD(month, -1,
DATEADD(year, -1, DATEADD(month, -8, DATEADD(month, 13-MONTH(GETDATE()),
DATEDIFF(month, 0, GETDATE()), 0))))))
END as [START DATE],
CASE #frequency
WHEN 'FINANCIAL YEAR' THEN CONVERT(date, DATEADD(ms, -2,
DATEADD(month, -8, DATEADD(month, 13-MONTH(GETDATE()), DATEADD(month,
DATEDIFF(month, 0, GETDATE()), 0)))))
END as [END DATE];
Play with the -8 in order to change the financial year - e.g. if the finacial year if from 2017-03 to 2018-03, then it will be -9.
I hope that's helpful!
P.S. I used April (from 2017-04 to 2018-04) as financial year

Creating a table of last 12 month in Postgresql

I'm trying to create a table that looks like this (a table of the last 12 months)
month, year
10, 2016
9, 2016
8, 2016
7, 2016
6, 2016
5, 2016
4, 2016
3, 2016
2, 2016
1, 2016
12, 2015
11, 2015
The code I have looks something like this:
select date_part('month', current_date) as order_month,
select date_part('year', current_date) as order_year
union all
select date_part('month', current_date - interval '1 month') as order_month,
select date_part('year', current_date - interval '1 month') as order_year
union all
...
Is there a more concise way of writing this, rather than using 11 unions?
generate_series(start, stop, step) will be useful such that
SELECT
EXTRACT('month' FROM d) AS month,
EXTRACT('year' FROM d) AS year
FROM
GENERATE_SERIES(
now(),
now() - interval '12 months',
interval '-1 month'
) AS d
;

Functions to convert dates between local time and UTC time by considering US day light savings time

I would like to contribute my codes related to the date conversion between local time and UTC time for both current and historical dates, including day light savings time consideration. I reviewed some recent postings but could not find the solution I am looking for so I summarize the solutions I found to develop my own solution. I am running my codes in SQL Server 2008R2. Test cases are provided as comments before each function. Feel free to check out the Credits section for related postings, try out my codes, and let me know if I miss anything. Thanks.
-- Test 1: select dbo.fn_isDayLightSavings(CAST('2014-01-01' AS DATE)), expect 0
-- Test 2: select dbo.fn_isDayLightSavings(CAST('2014-04-14' AS DATE)), expect 1
IF OBJECT_ID(N'fn_isDayLightSavings') IS NOT NULL AND OBJECTPROPERTY(OBJECT_ID(N'fn_isDayLightSavings'),'IsScalarFunction') = 1
DROP FUNCTION fn_isDayLightSavings;
GO
CREATE FUNCTION fn_isDayLightSavings (#dt AS DATETIME)
RETURNS TINYINT
AS
BEGIN
DECLARE #rtn TINYINT, #year INT, #dtsStartMonth DATETIME, #dtsEndMonth DATETIME, #dstStart DATETIME, #dstEnd DATETIME;
SET #year = DATEPART(YEAR, #dt);
-- In year 2007, US day light savings period changes from Apr-Oct to Mar-Nov.
if #year < 2007
BEGIN
-- Last Sunday of April at 2 AM
SET #dtsStartMonth = DATEADD(MONTH, 4, DATEADD(YEAR, #year - 1900, 0));
SET #dstStart = DATEADD(HOUR, 2, DATEADD(day, -(DATEPART(dw, #dtsStartMonth) - 1), #dtsStartMonth));
-- Last Sunday of October at 2 AM
SET #dtsEndMonth = DATEADD(MONTH, 10, DATEADD(YEAR, #year - 1900, 0));
SET #dstEnd = DATEADD(HOUR, 2, DATEADD(day, -(DATEPART(dw, #dtsEndMonth) - 1), #dtsEndMonth));
END
else
BEGIN
-- 2nd Sunday of March at 2 AM
SET #dtsStartMonth = DATEADD(MONTH, 2, DATEADD(YEAR, #year - 1900, 0));
SET #dstStart = DATEADD(HOUR, 2, DATEADD(day, ((15 - DATEPART(dw, #dtsStartMonth)) % 7) + 7, #dtsStartMonth));
-- 1st Sunday of November at 2 AM
SET #dtsEndMonth = DATEADD(MONTH, 10, DATEADD(YEAR, #year - 1900, 0));
SET #dstEnd = DATEADD(HOUR, 2, DATEADD(day, ((8 - DATEPART(dw, #dtsEndMonth)) % 7) + 7, #dtsEndMonth));
END
if #dt BETWEEN #dstStart AND #dstEnd SET #rtn=1 ELSE SET #rtn=0;
RETURN #rtn;
END
GO
-- Test 1: select dbo.fn_DateTime2UTC(CAST('2014-01-01 01:00:00' AS DATETIME), 0), expect '2014-01-01 07:00:00.000'
-- Test 2: select dbo.fn_DateTime2UTC(CAST('2014-01-01 01:00:00' AS DATETIME), 1), expect '2013-01-01 07:00:00.000'
-- Test 3: select dbo.fn_DateTime2UTC(CAST('2014-05-01 01:00:00' AS DATETIME), 0), expect '2014-05-01 06:00:00.000'
-- Test 4: select dbo.fn_DateTime2UTC(CAST('2014-05-01 01:00:00' AS DATETIME), 1), expect '2014-05-01 07:00:00.000'
IF OBJECT_ID(N'fn_DateTime2UTC') IS NOT NULL AND OBJECTPROPERTY(OBJECT_ID(N'fn_DateTime2UTC'),'IsScalarFunction') = 1
DROP FUNCTION fn_DateTime2UTC;
GO
CREATE FUNCTION fn_DateTime2UTC (#dt AS DATETIME, #ignoreDST AS TINYINT = 0)
-- do CAST(? AS DATETIMEOFFSET), if need datetimeoffset type
RETURNS DATETIME
AS
BEGIN
DECLARE #tzOffset INT, #utcDt DATETIME;
-- Get current time zone offset in minutes
SET #tzOffset = DATEPART(TZoffset, SYSDATETIMEOFFSET()) +
(CASE WHEN dbo.fn_isDayLightSavings(#dt)=1 THEN 60 ELSE 0 END);
if dbo.fn_isDayLightSavings(#dt)=0
set #utcDt = DATEADD(MINUTE, -#tzOffset, #dt);
else if #ignoreDST=0
set #utcDt = DATEADD(MINUTE, -#tzOffset, #dt);
else
set #utcDt = DATEADD(MINUTE, -#tzOffset+60, #dt);
return #utcDt;
END
GO
-- Test 1: select dbo.fn_UTC2DateTime(CAST('2014-01-01 07:00:00.000' AS DATETIME), 0), expect '2014-01-01 01:00:00'
-- Test 2: select dbo.fn_UTC2DateTime(CAST('2013-01-01 07:00:00.000' AS DATETIME), 1), expect '2014-01-01 01:00:00'
-- Test 3: select dbo.fn_UTC2DateTime(CAST('2014-05-01 06:00:00.000' AS DATETIME), 0), expect '2014-05-01 01:00:00'
-- Test 4: select dbo.fn_UTC2DateTime(CAST('2014-05-01 07:00:00.000' AS DATETIME), 1), expect '2014-05-01 01:00:00'
IF OBJECT_ID(N'fn_UTC2DateTime') IS NOT NULL AND OBJECTPROPERTY(OBJECT_ID(N'fn_UTC2DateTime'),'IsScalarFunction') = 1
DROP FUNCTION fn_UTC2DateTime;
GO
CREATE FUNCTION fn_UTC2DateTime (#utcDt AS DATETIME, #ignoreDST AS TINYINT = 0)
RETURNS DATETIME
AS
BEGIN
DECLARE #tzOffset INT, #dt DATETIME;
-- Get current time zone offset in minutes
SET #tzOffset = DATEPART(TZoffset, SYSDATETIMEOFFSET()) +
(CASE WHEN dbo.fn_isDayLightSavings(#utcDt)=1 THEN 60 ELSE 0 END);
if dbo.fn_isDayLightSavings(#utcDt)=0
set #dt = DATEADD(MINUTE, #tzOffset, #utcDt);
else if #ignoreDST=0
set #dt = DATEADD(MINUTE, #tzOffset, #utcDt);
else
set #dt = DATEADD(MINUTE, #tzOffset-60, #utcDt);
return #dt;
END
GO
-- Test 1: select dbo.fn_UTC2DateTimeOffset(CAST('2014-01-01 07:00:00.000' AS DATETIME), 0), expect '2014-01-01 01:00:00.0000000 -06:00'
-- Test 2: select dbo.fn_UTC2DateTimeOffset(CAST('2013-01-01 07:00:00.000' AS DATETIME), 1), expect '2014-01-01 01:00:00.0000000 -06:00'
-- Test 3: select dbo.fn_UTC2DateTimeOffset(CAST('2014-05-01 06:00:00.000' AS DATETIME), 0), expect '2014-05-01 01:00:00.0000000 -05:00'
-- Test 4: select dbo.fn_UTC2DateTimeOffset(CAST('2014-05-01 07:00:00.000' AS DATETIME), 1), expect '2014-05-01 00:00:00.0000000 -06:00'
IF OBJECT_ID(N'fn_UTC2DateTimeOffset') IS NOT NULL AND OBJECTPROPERTY(OBJECT_ID(N'fn_UTC2DateTimeOffset'),'IsScalarFunction') = 1
DROP FUNCTION fn_UTC2DateTimeOffset;
GO
CREATE FUNCTION fn_UTC2DateTimeOffset (#utcDt AS DATETIME, #ignoreDST TINYINT = 0)
RETURNS DATETIMEOFFSET
AS
BEGIN
DECLARE #tzOffset INT, #dt DATETIMEOFFSET;
-- Get current time zone offset in minutes
SET #tzOffset = DATEPART(TZoffset, SYSDATETIMEOFFSET()) +
(CASE WHEN dbo.fn_isDayLightSavings(#utcDt)=1 THEN 60 ELSE 0 END);
if dbo.fn_isDayLightSavings(#utcDt)=0
set #dt = SWITCHOFFSET(CAST(#utcDt AS DATETIMEOFFSET), #tzOffset);
else if #ignoreDST=0
set #dt = SWITCHOFFSET(CAST(#utcDt AS DATETIMEOFFSET), #tzOffset);
else
set #dt = SWITCHOFFSET(CAST(#utcDt AS DATETIMEOFFSET), #tzOffset-60);
return #dt;
END
GO
-- Credits:
-- Determination of day light savings start and end time, Jamie F., 11/1/2013
-- (http://stackoverflow.com/questions/19732896/how-to-create-daylight-savings-time-start-and-end-function-in-sql-server)
-- Day light savings time calendar, USNO
-- (http://aa.usno.navy.mil/faq/docs/daylight_time.php)
-- Time zone offset, Microsoft
-- (http://msdn.microsoft.com/en-us/library/ms174420.aspx)
-- Local system date time with time zone, Robert Cantor, 12/7/2013
-- (http://stackoverflow.com/questions/1205142/tsql-how-to-convert-local-time-to-utc-sqlserver2008)
-- Convert date with time zone and day light savings, Eric Z Beard, 8/24/2008
-- (http://stackoverflow.com/questions/24797/effectively-converting-dates-between-utc-and-local-ie-pst-time-in-sql-2005#25073)
Kudos to you for assembling that knowledge into one place. Assuming it is accurate, it's easy to pick up and use immediately.
However, the trouble is that without an extensive set of unit tests you can't be sure of accuracy.
Might I suggest the best option for date/time conversions would be to use an existing highly tested open source library such as http://nodatime.org/. Date/time conversions have so many intracacies and details that could not possibly be contained in a few dozen lines of SQL code.
nodatime is a .NET library that should easily be accessible via the SQL Server CLR user-defined functions feature.

SQL Server 2012: Round to NEAREST(!) start of month (in timestamp format) from a timestamp column)

I need to round to NEAREST start of month (in timestamp format) from a timestamp column.
How do one accomplish this?
Examples:
TimestampColumn A: Rounded to these values
2012-01-07 18:18:29.923 2012-01-01 00:00:00.000
2012-01-14 12:58:13.122 2012-01-01 00:00:00.000
2012-06-09 17:10:30.787 2012-06-01 00:00:00.000
2012-05-31 09:29:43.870 2012-06-01 00:00:00.000
2012-10-22 12:09:47.067 2012-11-01 00:00:00.000
2012-10-15 04:35:11.013 2012-10-01 00:00:00.000
Consider converting to date first
DECLARE #d DATETIME
set #d = CONVERT(DATE, '2012-02-14 12:58:13.122')
SET #d = DATEADD(DAY, 1-datepart(day, #d), #d)
SELECT #d
Here is one way to do it - just subtract all the parts of the date which you don't care about:
DECLARE #d DATETIME
set #d = '2012-02-14 12:58:13.122'
SET #d = DATEADD(DAY, 1-datepart(day, #d), #d)
SET #d = DATEADD(hour, -datepart(hour, #d), #d)
SET #d = DATEADD(minute, -datepart(minute, #d), #d)
SET #d = DATEADD(second, -datepart(second, #d), #d)
SET #d = DATEADD(millisecond, -datepart(millisecond, #d), #d)
SELECT #d
Calculate the length of the applicable month in seconds and then decide whether you are past the middle of the month. Go forward or back as needed.
declare #Foo as DateTime = '2012-10-15 12:35:11.013'
select
DateAdd( month, DateDiff( m, 0, #Foo ), 0 ) as 'Year/Month',
DateDiff( s, DateAdd( month, DateDiff( m, 0, #Foo ), 0), #Foo ) as 'Seconds Into Month',
DateDiff( s, DateAdd( month, DateDiff( m, 0, #Foo ), 0 ), DateAdd( month, DateDiff( m, 0, #Foo ) + 1, 0 ) ) as 'Seconds In Month',
DateDiff( s, DateAdd( month, DateDiff( m, 0, #Foo ), 0 ), DateAdd( month, DateDiff( m, 0, #Foo ) + 1, 0 ) ) / 2 as 'Seconds In Half Month',
DateAdd( month, DateDiff( m, 0, #Foo ) + Round( 1.0 * DateDiff( s, DateAdd( month, DateDiff( m, 0, #Foo ), 0), #Foo ) / DateDiff( s, DateAdd( month, DateDiff( m, 0, #Foo ), 0 ), DateAdd( month, DateDiff( m, 0, #Foo ) + 1, 0 ) ), 0 ), 0 ) as 'Rounded Date'
Thanks for contributing. But I'm going for this one; its good enough.
SELECT CASE
WHEN DATEDIFF(DAY, GETDATE(), DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) + 1, 0)) >
ABS(DATEDIFF(DAY, GETDATE(), DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)))
THEN DATEADD(dd, datediff(dd, 0, DATEADD(DAY, DATEDIFF(DAY, GETDATE(), DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)), GETDATE() ) )+0, 0)
ELSE dateadd(dd, datediff(dd, 0, DATEADD(DAY, DATEDIFF(DAY, GETDATE(), DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) + 1, 0)), GETDATE() ) )+0, 0)
END
Here is an example. You have 5 fields: the date you want to convert, the converted date as you wanted, the first day of the month, the last day of the month, and the first day of the next month.
Just choose what you need in it:
SELECT
BED_Meeting_When,
CASE WHEN DAY(BED_Meeting_When) < 15 THEN (DATEADD(DAY, (-DAY(BED_Meeting_When) + 1), BED_Meeting_When)) ELSE DATEADD(DAY, (-DAY(BED_Meeting_When) + 1), DATEADD(MONTH, 1, BED_Meeting_When)) END,
DATEADD(DAY, (-DAY(BED_Meeting_When) + 1), BED_Meeting_When) AS 'Arrondi au premier du mois',
DATEADD(DAY, -1, DATEADD(MONTH, DATEDIFF(MONTH, 0, BED_Meeting_When) + 1, 0)) AS 'Arrondi au dernier du mois',
DATEADD(DAY, (-DAY(BED_Meeting_When) + 1), DATEADD(MONTH, 1, BED_Meeting_When)) AS 'Arrondi au premier du mois suivant',
BEMR_Titre
FROM bpri_entretien_detail
INNER JOIN bpri_entretien_motif ON (BED_BEMR_Idx = BEMR_Idx)
Hi I know this is late to the party a little simple offering can never offend I hope
Creating a mini temp table I pop'd the dates offered in the original post
and then selected from it as below
create table #DT(
TS_A datetime, TS_B datetime)
insert into #DT (ts_a) values ('2012-01-07 18:18:29.923'),
('2012-01-14 12:58:13.122'),
('2012-06-09 17:10:30.787'),
('2012-05-31 09:29:43.870'),
('2012-10-22 12:09:47.067'),
('2012-10-15 04:35:11.013')
select TS_A, DATEADD(MONTH, DATEDIFF(MONTH, 0,ts_a), 0) TS_B from #DT
Results - I hope this is clear
RESULTS
Amazing how times they are a changing :-)
TX
I hope this turns out alright haven't posted any where for over a decade

Syntax for nested common-table-expression

I asked a question for a complex query yesterday, and I was offered an example. I really would like to get it to work but there was a syntax error in it that I can't figure out. Keep in mind that I was just introduced to CTE's earlier this week so hopefully this is an easy one.
I don't think I need to post the full code on here, so I'll just summarize
with cte as (select dateadd(hour, 1, cast(cast(getdate() -1 as date) as datetime)) as midnnight),
allhours as (
select 0 as hour, midnight as timestart, dateadd(hour, 1, timestart) as timeend from cte union all
select 1 as hour, dateadd(hour, 1, midnight), dateadd(hour, 2, midnight) from cte union all
....
select 23 as hour, dateadd(hour, 23, midnight), dateadd(hour, 24, midnight) from cte union all
)
select ah.hour,...
The (...) denotes unnecessary code that I omitted to make it less messy
But I am getting a syntax error on the parenthesis between select 23 and select ah.hour
"Incorrect syntax near ')'. Expecting SELECT, or '('.
Any help is greatly appreciated.
-J
You had a few syntax errors including a UNION ALL that was not needed at the bottom, and your first SELECT in the allhours was referencing an alias, so try this:
;with cte as
(
select dateadd(hour, 1, cast(cast(getdate() -1 as date) as datetime)) as midnight
),
allhours as
(
select 0 as hour, midnight as timestart, dateadd(hour, 1, midnight) as timeend
from cte
union all
select 1 as hour, dateadd(hour, 1, midnight), dateadd(hour, 2, midnight)
from cte
union all
select 23 as hour, dateadd(hour, 23, midnight), dateadd(hour, 24, midnight)
from cte
)
select *
from allhours
see SQL Fiddle with Demo
You should get rid of the last Union all here
hour, dateadd(hour, 23, midnight), dateadd(hour, 24, midnight) from cte union all
)
select ah.hour,...
Also, spell midnight right in the first line