Calculating RSI for Multiple Dates and Tickers in SQL Server 2012 - sql-server-2012

I can calculate RSI for a specific end date:
DECLARE #StartingDate smalldatetime
DECLARE #EndingDate smalldatetime
DECLARE #StockID char(15)
DECLARE #DAYS INT
DECLARE #AG FLOAT
DECLARE #AL FLOAT
DECLARE #RS FLOAT
SET #StartingDate = '20180301'
SET #EndingDate = '20180403'
SET #StockID = 'ACE'
SET #DAYS = 14
SET #AG =(
SELECT SUM([px_close]-[px_open])
FROM [dbo].[daily_data]
WHERE [Ticker] = #STOCKID
AND ([Date] BETWEEN #StartingDate AND #EndingDate)
AND ([px_close]-[px_open])>0)/#DAYS
SET #AL =(
SELECT SUM([px_close]-[px_open])
FROM [dbo].[daily_data]
WHERE [Ticker] = #STOCKID
AND ([Date] BETWEEN #StartingDate AND #EndingDate)
AND ([px_close]-[px_open])<0)/#DAYS
SET #RS = #AG/ABS(#AL)
SELECT #StockID AS Ticker, #EndingDate AS Date, 100 - (100/(1+#RS)) RSI
Here's my output:
Ticker Date RSI
ACE 2018-04-03 48.7307
How can I calculate RSI for multiple dates and multiple tickers?

You don't need to set all of these to variables. You can just add the date and ticker to the group by and avoid the redundant subqueries... something like:
SELECT
[Ticker]
,[Date]
,AG = SUM(case when (isnull([px_close],0)-isnull([px_open],0))>0 then (isnull([px_close],0)-isnull([px_open],0)) end) / #days
,AL = SUM(case when (isnull([px_close],0)-isnull([px_open],0))<0 then (isnull([px_close],0)-isnull([px_open],0)) end) / #days
,RS = (SUM(case when (isnull([px_close],0)-isnull([px_open],0))>0 then (isnull([px_close],0)-isnull([px_open],0)) end) / #days) / ABS(SUM(case when (isnull([px_close],0)-isnull([px_open],0))<0 then (isnull([px_close],0)-isnull([px_open],0)) end) / #days)
,RSI = 100 - (100/(1+(SUM(case when (isnull([px_close],0)-isnull([px_open],0))>0 then (isnull([px_close],0)-isnull([px_open],0)) end) / #days) / ABS(SUM( case when (isnull([px_close],0)-isnull([px_open],0))<0 then (isnull([px_close],0)-isnull([px_open],0)) end) / #days)))
FROM
[dbo].[daily_data]
WHERE
[Ticker] = #STOCKID
AND ([Date] BETWEEN #StartingDate AND #EndingDate)
group by
[Ticker],[Date]
Remove [Ticker] = #STOCKID from the where clause to return all Tickers

Related

SELECT Multiple Columns for Output in SQL Server 2012

I have a table of stock quotes, where I loop through days to calculate a 14day RSI for a ticker:
DECLARE #StartingDate smalldatetime
DECLARE #EndingDate smalldatetime
DECLARE #FinalDate smalldatetime
DECLARE #StockID char(15)
DECLARE #DAYS INT
DECLARE #AG FLOAT(4)
DECLARE #AL FLOAT(4)
DECLARE #RS FLOAT(4)
SET #StartingDate = '20180101'
SET #FinalDate='20180405'
--SET #EndingDate = DATEADD(day, 14, #StartingDate);
SET #EndingDate = '20180403'
SET #StockID = 'ACE'
SET #DAYS = 14
WHILE (#EndingDate < #FinalDate)
BEGIN
SET #EndingDate = DATEADD(day, 13, #StartingDate);
SET #AG =(
--SELECT SUM([px_close]-[px_open])
SELECT SUM([px_close]-[px_open])
FROM [Coinmarketcap].[dbo].[daily_data]
WHERE [Ticker] = #STOCKID
AND ([Date] BETWEEN #StartingDate AND #EndingDate)
--AND ([px_close]-[px_open])>0)/#DAYS
AND ([px_close]-[px_open])>0)/#DAYS
SET #AL =(
--SELECT SUM([px_close]-[px_open])
SELECT SUM([px_close]-[px_open])
FROM [Coinmarketcap].[dbo].[daily_data]
WHERE [Ticker] = #STOCKID
AND ([Date] BETWEEN #StartingDate AND #EndingDate)
AND ([px_close]-[px_open])<0)/#DAYS
SET #RS = #AG/ABS(#AL)
SELECT #STOCKID AS [Ticker], #EndingDate AS Date, #AG AS AvGain, #AL AS AvLoss, #RS As RS, 100 - (100/(1+#RS)) RSI
SET #StartingDate = DATEADD(day, 1, #StartingDate);
END;
My loop produces RSI values for each day as individual outputs:
Ticker Date AvGain AvLoss RS RSI
ACE 2018-01-14 0.09985857 -0.07670186 1.301906 56.55773
Ticker Date AvGain AvLoss RS RSI
ACE 2018-01-15 0.1158097 -0.0737355 1.57061 61.09873
Ticker Date AvGain AvLoss RS RSI
ACE 2018-01-16 0.1150289 -0.1010219 1.138653 53.2416
How do I combine the output into one table, instead of separate tables for each date?

Quarterly totals using loop in SQL server

----I am trying to get yearly and quarterly totals for 2013 and 2014 for San Francisco and San mateo counties seperately. I know it is probably very easy but having difficulty with the loop. I can do it without looping but it is lengthy and would like to do it in a cleaner neater fashion. Any help would be greatly appreciated. New to programming------
DECLARE #Qtotal int
DECLARE #Q1total int
DECLARE #Q2total int
DECLARE #Q3total int
DECLARE #Q4total int
DECLARE #year int
DECLARE #County int
DECLARE #CountyName varchar(40)
DECLARE #startmonth nvarchar
DECLARE #endmonth nvarchar
SET #startmonth = '1'
SET #endmonth = '3'
SET #year = '2013'
SET #county = '038'
SET #countyName = 'San Francisco'
Begin
SELECT #Qtotal = (select COUNT(*) FROM #tCounty
where year(cast(dDate as date)) = #year
and countycode = #County
and month(cast(deathDate as date)) between #startmonth and #endmonth)--get quarter total
if #startmonth=1 SET #Q1total = #Qtotal
if #startmonth=4 SET #Q2total = #Qtotal
if #startmonth=7 SET #Q3total = #Qtotal
if #startmonth=10 SET #Q4total = #Qtotal
Set #startmonth = #startmonth + 3
Set #startmonth = #endmonth + 3
if #startmonth > 10
end
--------insert into table created before and not shown in code above
INSERT INTO #Totals([County],[referenceYear],[Total],[Q1],[Q2],[Q3],[Q4]) Values (#countyName,#year,#yrtotal,#Q1total,#Q2total,#Q3total,#Q4total)
this should do it
select County,
Year(dDate) as Year,
count(*) as Total,
sum(case when DATEPART(q, dDate)=1 then 1 else 0 end) as Q1,
sum(case when DATEPART(q, dDate)=2 then 1 else 0 end) as Q2,
sum(case when DATEPART(q, dDate)=3 then 1 else 0 end) as Q3,
sum(case when DATEPART(q, dDate)=4 then 1 else 0 end) as Q4
from #tCounty
group by County,
Year(dDate)

Converting query to a stored procedure

I have the following query which works as is (people will probably cringe, but try to ignore how bad it is):
DECLARE #submit_day DATETIME;
DECLARE #meeting_day DATETIME;
DECLARE #start_time_of_business_day DATETIME;
DECLARE #business_day_hours FLOAT;
DECLARE #submit_time DATETIME;
DECLARE #meeting_time DATETIME;
DECLARE #num1 FLOAT
DECLARE #num2 FLOAT
DECLARE #num3 FLOAT
SET #meeting_day = '2013-06-24'; -- USER GENERATED VARIABLE
SET #meeting_time = '15:45'; -- USER GENERATED
SET #submit_day = CONVERT(VARCHAR(10),GETDATE(),101);
SET #submit_time = CONVERT(VARCHAR(8),GETDATE(),108);
SET #start_time_of_business_day = '09:00';
SET #business_day_hours = 8.5;
SET #num1 = ((DATEDIFF(dd, #submit_day, #meeting_day))
-(DATEDIFF(wk, #submit_day, #meeting_day) * 2)
-(CASE WHEN DATEPART(dw, #submit_day) = 1 THEN 1 ELSE 0 END)
-(CASE WHEN DATEPART(dw, #meeting_day) = 7 THEN 1 ELSE 0 END)
-(SELECT COUNT(*) FROM intranet.dbo.bank_holiday WHERE the_date BETWEEN #submit_day AND #meeting_day)) * #business_day_hours
SET #num2 = (select datediff(minute, #start_time_of_business_day, #submit_time)) / 60.0
SET #num3 = (select datediff(minute, #start_time_of_business_day, #meeting_time)) / 60.0
select #num1 - #num2 + #num3 as [hours]
So I want to set this up as a stored procedure, so I tried the following:
USE [INTRANET]
GO
/****** Object: StoredProcedure [dbo].[BusinessHours] Script Date: 06/21/2013 15:19:47 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[BusinessHours]
#meeting_date DATETIME,
#meeting_time DATETIME
AS
DECLARE #submit_day DATETIME;
DECLARE #submit_time DATETIME;
DECLARE #start_time_of_business_day DATETIME;
DECLARE #business_day_hours FLOAT;
DECLARE #num1 FLOAT
DECLARE #num2 FLOAT
DECLARE #num3 FLOAT
SET #submit_day = CONVERT(VARCHAR(10),GETDATE(),101);
SET #submit_time = CONVERT(VARCHAR(8),GETDATE(),108);
SET #start_time_of_business_day = '09:00';
SET #business_day_hours = 8.5;
SET #num1 = ((DATEDIFF(dd, #submit_day, #meeting_day))
-(DATEDIFF(wk, #submit_day, #meeting_day) * 2)
-(CASE WHEN DATEPART(dw, #submit_day) = 1 THEN 1 ELSE 0 END)
-(CASE WHEN DATEPART(dw, #meeting_day) = 7 THEN 1 ELSE 0 END)
-(SELECT COUNT(*) FROM intranet.dbo.bank_holiday WHERE the_date BETWEEN #submit_day AND #meeting_day)) * #business_day_hours
SET #num2 = (select datediff(minute, #start_time_of_business_day, #submit_time)) / 60.0
SET #num3 = (select datediff(minute, #start_time_of_business_day, #meeting_time)) / 60.0
select #num1 - #num2 + #num3 as [hours]
This gives me an error:
Msg 137, Level 15, State 2, Procedure BusinessHours, Line 25
Must declare the scalar variable "#meeting_day".
Msg 137, Level 15, State 2, Procedure BusinessHours, Line 29
Must declare the scalar variable "#meeting_day".
Tried searching, but can't figure out how to get this to work.
Yes. You renamed your DECLAREd variable #meeting_day to the parameter #meeting_date.
Fix that.
You typed "meeting_date" instead of "meeting_day" in your parameter declaration.
CREATE PROCEDURE [dbo].[BusinessHours]
#meeting_day DATETIME, --ERROR IS HERE
#meeting_time DATETIME
AS

How to use IF statement in SQL Server 2005?

This is the scenario I would like to have in my INSERT in a stored procedure.
Tables:
tblRate
RateID (pk)
Rate money
Days int
isDailyRate bit
tblBooking
Totals money
In my vb app this is the statement. How would I translate this into T-SQL?
if !isDaily = True then
!Totals = (!Days * !Rate)
else
!Totals = !Rate
end if
This is my stored procedure:
Create PROCEDURE [dbo].[sp_tblBooking_Add]
(
#RateID bigint,
#Rate money,
#Days int,
#CheckOUT datetime
)
AS
BEGIN
--Below is the logic I want. I can't get the right syntax
--Declare #myTotals as money
--Declare #myCheckOut as DateTime
--if (Select isDailyRate FROM tblRates WHERE (RateID = #RateID)) = True THEN
-- set myTotals = (#Rate * #Days)
-- set #CheckOUT = DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()) + #Days, '12:00')
--Else
-- set myTotals = #Rate
-- set #CheckOUT = GETDATE()
--End if
INSERT INTO tblBooking(Totals, CheckOUT)
VALUES(#myTotals, #myCheckOut);
END
Use the CASE expression:
INSERT INTO tblBooking (Totals, CheckOUT)
SELECT
CASE
WHEN idDailyRate = 1 THEN #Rate * #Days
ELSE #rate
END,
CASE
WHEN idDailyRate = 1 THEN DATEADD(DAY,
DATEDIFF(DAY, 0, GETDATE()) + #Days,
'12:00')
ELSE GETDATE()
END
FROM tblRates
WHERE RateID = #RateID;
Or, if they are scalar values, then you can select them into a variables and insert them instead of INSERT ... INTO ... SELECT.
Update 1
Like this:
Declare #myTotals as money;
Declare #myCheckOut as DateTime;
SELECT
#myTotals = CASE
WHEN idDailyRate = 1 THEN #Rate * #Days
ELSE #rate
END,
#myCheckOut = CASE
WHEN idDailyRate = 1 THEN DATEADD(DAY,
DATEDIFF(DAY, 0, GETDATE()) + #Days,
'12:00')
ELSE GETDATE()
END
FROM tblRates
WHERE RateID = #RateID;
INSERT INTO tblBooking (Totals, CheckOUT) VALUES(#myTotals, #myCheckOut );
But this will give you an error, if there is more than value returned from this table tblRates into those variables.

T-SQL get number of working days between 2 dates

I want to calculate the number of working days between 2 given dates. For example if I want to calculate the working days between 2013-01-10 and 2013-01-15, the result must be 3 working days (I don't take into consideration the last day in that interval and I subtract the Saturdays and Sundays). I have the following code that works for most of the cases, except the one in my example.
SELECT (DATEDIFF(day, '2013-01-10', '2013-01-15'))
- (CASE WHEN DATENAME(weekday, '2013-01-10') = 'Sunday' THEN 1 ELSE 0 END)
- (CASE WHEN DATENAME(weekday, DATEADD(day, -1, '2013-01-15')) = 'Saturday' THEN 1 ELSE 0 END)
How can I accomplish this? Do I have to go through all the days and check them? Or is there an easy way to do this.
Please, please, please use a calendar table. SQL Server doesn't know anything about national holidays, company events, natural disasters, etc. A calendar table is fairly easy to build, takes an extremely small amount of space, and will be in memory if it is referenced enough.
Here is an example that creates a calendar table with 30 years of dates (2000 -> 2029) but requires only 200 KB on disk (136 KB if you use page compression). That is almost guaranteed to be less than the memory grant required to process some CTE or other set at runtime.
CREATE TABLE dbo.Calendar
(
dt DATE PRIMARY KEY, -- use SMALLDATETIME if < SQL Server 2008
IsWorkDay BIT
);
DECLARE #s DATE, #e DATE;
SELECT #s = '2000-01-01' , #e = '2029-12-31';
INSERT dbo.Calendar(dt, IsWorkDay)
SELECT DATEADD(DAY, n-1, '2000-01-01'), 1
FROM
(
SELECT TOP (DATEDIFF(DAY, #s, #e)+1) ROW_NUMBER()
OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
) AS x(n);
SET DATEFIRST 1;
-- weekends
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE DATEPART(WEEKDAY, dt) IN (6,7);
-- Christmas
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE MONTH(dt) = 12
AND DAY(dt) = 25
AND IsWorkDay = 1;
-- continue with other holidays, known company events, etc.
Now the query you're after is quite simple to write:
SELECT COUNT(*) FROM dbo.Calendar
WHERE dt >= '20130110'
AND dt < '20130115'
AND IsWorkDay = 1;
More info on calendar tables:
http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html
More info on generating sets without loops:
http://www.sqlperformance.com/tag/date-ranges
Also beware of little things like relying on the English output of DATENAME. I've seen several applications break because some users had a different language setting, and if you're relying on WEEKDAY be sure you set your DATEFIRST setting appropriately...
For stuff like this i tend to maintain a calendar table that also includes bank holidays etc.
The script i use for this is as follows (Note that i didnt write it # i forget where i found it)
SET DATEFIRST 1
SET NOCOUNT ON
GO
--Create ISO week Function (thanks BOL)
CREATE FUNCTION ISOweek ( #DATE DATETIME )
RETURNS INT
AS
BEGIN
DECLARE #ISOweek INT
SET #ISOweek = DATEPART(wk, #DATE) + 1 - DATEPART(wk, CAST(DATEPART(yy, #DATE) AS CHAR(4)) + '0104')
--Special cases: Jan 1-3 may belong to the previous year
IF ( #ISOweek = 0 )
SET #ISOweek = dbo.ISOweek(CAST(DATEPART(yy, #DATE) - 1 AS CHAR(4)) + '12' + CAST(24 + DATEPART(DAY, #DATE) AS CHAR(2))) + 1
--Special case: Dec 29-31 may belong to the next year
IF ( ( DATEPART(mm, #DATE) = 12 )
AND ( ( DATEPART(dd, #DATE) - DATEPART(dw, #DATE) ) >= 28 )
)
SET #ISOweek = 1
RETURN(#ISOweek)
END
GO
--END ISOweek
--CREATE Easter algorithm function
--Thanks to Rockmoose (http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=45689)
CREATE FUNCTION fnDLA_GetEasterdate ( #year INT )
RETURNS CHAR(8)
AS
BEGIN
-- Easter date algorithm of Delambre
DECLARE #A INT ,
#B INT ,
#C INT ,
#D INT ,
#E INT ,
#F INT ,
#G INT ,
#H INT ,
#I INT ,
#K INT ,
#L INT ,
#M INT ,
#O INT ,
#R INT
SET #A = #YEAR % 19
SET #B = #YEAR / 100
SET #C = #YEAR % 100
SET #D = #B / 4
SET #E = #B % 4
SET #F = ( #B + 8 ) / 25
SET #G = ( #B - #F + 1 ) / 3
SET #H = ( 19 * #A + #B - #D - #G + 15 ) % 30
SET #I = #C / 4
SET #K = #C % 4
SET #L = ( 32 + 2 * #E + 2 * #I - #H - #K ) % 7
SET #M = ( #A + 11 * #H + 22 * #L ) / 451
SET #O = 22 + #H + #L - 7 * #M
IF #O > 31
BEGIN
SET #R = #O - 31 + 400 + #YEAR * 10000
END
ELSE
BEGIN
SET #R = #O + 300 + #YEAR * 10000
END
RETURN #R
END
GO
--END fnDLA_GetEasterdate
--Create the table
CREATE TABLE MyDateTable
(
FullDate DATETIME NOT NULL
CONSTRAINT PK_FullDate PRIMARY KEY CLUSTERED ,
Period INT ,
ISOWeek INT ,
WorkingDay VARCHAR(1) CONSTRAINT DF_MyDateTable_WorkDay DEFAULT 'Y'
)
GO
--End table create
--Populate table with required dates
DECLARE #DateFrom DATETIME ,
#DateTo DATETIME ,
#Period INT
SET #DateFrom = CONVERT(DATETIME, '20000101')
--yyyymmdd (1st Jan 2000) amend as required
SET #DateTo = CONVERT(DATETIME, '20991231')
--yyyymmdd (31st Dec 2099) amend as required
WHILE #DateFrom <= #DateTo
BEGIN
SET #Period = CONVERT(INT, LEFT(CONVERT(VARCHAR(10), #DateFrom, 112), 6))
INSERT MyDateTable
( FullDate ,
Period ,
ISOWeek
)
SELECT #DateFrom ,
#Period ,
dbo.ISOweek(#DateFrom)
SET #DateFrom = DATEADD(dd, +1, #DateFrom)
END
GO
--End population
/* Start of WorkingDays UPDATE */
UPDATE MyDateTable
SET WorkingDay = 'B' --B = Bank Holiday
--------------------------------EASTER---------------------------------------------
WHERE FullDate = DATEADD(dd, -2, CONVERT(DATETIME, dbo.fnDLA_GetEasterdate(DATEPART(yy, FullDate)))) --Good Friday
OR FullDate = DATEADD(dd, +1, CONVERT(DATETIME, dbo.fnDLA_GetEasterdate(DATEPART(yy, FullDate))))
--Easter Monday
GO
UPDATE MyDateTable
SET WorkingDay = 'B'
--------------------------------NEW YEAR-------------------------------------------
WHERE FullDate IN ( SELECT MIN(FullDate)
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 1
AND DATEPART(dw, FullDate) NOT IN ( 6, 7 )
GROUP BY DATEPART(yy, FullDate) )
---------------------MAY BANK HOLIDAYS(Always Monday)------------------------------
OR FullDate IN ( SELECT MIN(FullDate)
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 5
AND DATEPART(dw, FullDate) = 1
GROUP BY DATEPART(yy, FullDate) )
OR FullDate IN ( SELECT MAX(FullDate)
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 5
AND DATEPART(dw, FullDate) = 1
GROUP BY DATEPART(yy, FullDate) )
--------------------AUGUST BANK HOLIDAY(Always Monday)------------------------------
OR FullDate IN ( SELECT MAX(FullDate)
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 8
AND DATEPART(dw, FullDate) = 1
GROUP BY DATEPART(yy, FullDate) )
--------------------XMAS(Move to next working day if on Sat/Sun)--------------------
OR FullDate IN ( SELECT CASE WHEN DATEPART(dw, FullDate) IN ( 6, 7 ) THEN DATEADD(dd, +2, FullDate)
ELSE FullDate
END
FROM MyDateTable
WHERE DATEPART(mm, FullDate) = 12
AND DATEPART(dd, FullDate) IN ( 25, 26 ) )
GO
---------------------------------------WEEKENDS--------------------------------------
UPDATE MyDateTable
SET WorkingDay = 'N'
WHERE DATEPART(dw, FullDate) IN ( 6, 7 )
GO
/* End of WorkingDays UPDATE */
--SELECT * FROM MyDateTable ORDER BY 1
DROP FUNCTION fnDLA_GetEasterdate
DROP FUNCTION ISOweek
--DROP TABLE MyDateTable
SET NOCOUNT OFF
Once you have created the table, finding the number of working days is easy peasy:
SELECT COUNT(FullDate) AS WorkingDays
FROM dbo.tbl_WorkingDays
WHERE WorkingDay = 'Y'
AND FullDate >= CONVERT(DATETIME, '10/01/2013', 103)
AND FullDate < CONVERT(DATETIME, '15/01/2013', 103)
Note that this script includes UK bank holidays, i'm not sure what region you're in.
Here's a simple function that counts working days not including Saturday and Sunday (when counting holidays isn't necessary):
CREATE FUNCTION dbo.udf_GetBusinessDays (
#START_DATE DATE,
#END_DATE DATE
)
RETURNS INT
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #NUMBER_OF_DAYS INT = 0;
DECLARE #DAY_COUNTER INT = 0;
DECLARE #BUSINESS_DAYS INT = 0;
DECLARE #CURRENT_DATE DATE;
DECLARE #DAYNAME NVARCHAR(9)
SET #NUMBER_OF_DAYS = DATEDIFF(DAY, #START_DATE, #END_DATE);
WHILE #DAY_COUNTER <= #NUMBER_OF_DAYS
BEGIN
SET #CURRENT_DATE = DATEADD(DAY, #DAY_COUNTER, #START_DATE)
SET #DAYNAME = DATENAME(WEEKDAY, #CURRENT_DATE)
SET #DAY_COUNTER += 1
IF #DAYNAME = N'Saturday' OR #DAYNAME = N'Sunday'
BEGIN
CONTINUE
END
ELSE
BEGIN
SET #BUSINESS_DAYS += 1
END
END
RETURN #BUSINESS_DAYS
END
GO
This is the method I normally use (When not using a calendar table):
DECLARE #T TABLE (Date1 DATE, Date2 DATE);
INSERT #T VALUES ('20130110', '20130115'), ('20120101', '20130101'), ('20120611', '20120701');
SELECT Date1, Date2, WorkingDays
FROM #T t
CROSS APPLY
( SELECT [WorkingDays] = COUNT(*)
FROM Master..spt_values s
WHERE s.Number BETWEEN 1 AND DATEDIFF(DAY, t.date1, t.Date2)
AND s.[Type] = 'P'
AND DATENAME(WEEKDAY, DATEADD(DAY, s.number, t.Date1)) NOT IN ('Saturday', 'Sunday')
) wd
If like I do you have a table with holidays in you can add this in too:
SELECT Date1, Date2, WorkingDays
FROM #T t
CROSS APPLY
( SELECT [WorkingDays] = COUNT(*)
FROM Master..spt_values s
WHERE s.Number BETWEEN 1 AND DATEDIFF(DAY, t.date1, t.Date2)
AND s.[Type] = 'P'
AND DATENAME(WEEKDAY, DATEADD(DAY, s.number, t.Date1)) NOT IN ('Saturday', 'Sunday')
AND NOT EXISTS
( SELECT 1
FROM HolidayTable ht
WHERE ht.Date = DATEADD(DAY, s.number, t.Date1)
)
) wd
The above will only work if your dates are within 2047 days of each other, if you are likely to be calculating larger date ranges you can use this:
SELECT Date1, Date2, WorkingDays
FROM #T t
CROSS APPLY
( SELECT [WorkingDays] = COUNT(*)
FROM ( SELECT [Number] = ROW_NUMBER() OVER(ORDER BY s.number)
FROM Master..spt_values s
CROSS JOIN Master..spt_values s2
) s
WHERE s.Number BETWEEN 1 AND DATEDIFF(DAY, t.date1, t.Date2)
AND DATENAME(WEEKDAY, DATEADD(DAY, s.number, t.Date1)) NOT IN ('Saturday', 'Sunday')
) wd
I did my code in SQL SERVER 2008 (MS SQL) . It works fine for me. I hope it will help you.
DECLARE #COUNTS int,
#STARTDATE date,
#ENDDATE date
SET #STARTDATE ='01/21/2013' /*Start date in mm/dd/yyy */
SET #ENDDATE ='01/26/2013' /*End date in mm/dd/yyy */
SET #COUNTS=0
WHILE (#STARTDATE<=#ENDDATE)
BEGIN
/*Check for holidays*/
IF ( DATENAME(weekday,#STARTDATE)<>'Saturday' and DATENAME(weekday,#STARTDATE)<>'Sunday')
BEGIN
SET #COUNTS=#COUNTS+1
END
SET #STARTDATE=DATEADD(day,1,#STARTDATE)
END
/* Display the no of working days */
SELECT #COUNTS
By Combining #Aaron Bertrand's answer and the Easter Calculation from #HeavenCore's and adding some code of my own, this code creates a calendar from 2000 to 2049 that includes UK (England) Bank Holidays. Usage and notes as per Aaron's answer:
DECLARE #s DATE, #e DATE;
SELECT #s = '2000-01-01' , #e = '2049-12-31';
-- Insert statements for procedure here
CREATE TABLE dbo.Calendar
(
dt DATE PRIMARY KEY, -- use SMALLDATETIME if < SQL Server 2008
IsWorkDay BIT
);
INSERT dbo.Calendar(dt, IsWorkDay)
SELECT DATEADD(DAY, n-1, '2000-01-01'), 1
FROM
(
SELECT TOP (DATEDIFF(DAY, #s, #e)+1) ROW_NUMBER()
OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
) AS x(n);
SET DATEFIRST 1;
-- weekends
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE DATEPART(WEEKDAY, dt) IN (6,7);
-- Christmas
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE IsWorkDay = 1 and MONTH(dt) = 12 and
(DAY(dt) in (25,26) or
(DAY(dt) in (27, 28) and DATEPART(WEEKDAY, dt) IN (1,2)) );
-- New Year
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE IsWorkDay = 1 and MONTH(dt) = 1 AND
( DAY(dt) = 1 or (DAY(dt) IN (2,3) AND DATEPART(WEEKDAY, dt)=1 ));
-- Easter
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE dt = DATEADD(dd, -2, CONVERT(DATETIME, dbo.fnDLA_GetEasterdate(DATEPART(yy, dt)))) --Good Friday
OR dt = DATEADD(dd, +1, CONVERT(DATETIME, dbo.fnDLA_GetEasterdate(DATEPART(yy, dt)))) --Easter Monday
-- May Day (first Monday in May)
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE MONTH(dt) = 5 AND DATEPART(WEEKDAY, dt)=1 and DAY(DT)<8;
-- Spring Bank Holiday (last Monday in May apart from 2022 when moved to include Platinum Jubilee bank holiday)
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE
(YEAR(dt)=2022 and MONTH(dt) = 6 AND DAY(dt) IN (2,3)) OR
(YEAR(dt)<>2022 and MONTH(dt) = 5 AND DATEPART(WEEKDAY, dt)=1 and DAY(DT)>24);
-- Summer Bank Holiday (last Monday in August)
UPDATE dbo.Calendar SET IsWorkDay = 0
WHERE MONTH(dt) = 8 AND DATEPART(WEEKDAY, dt)=1 and DAY(DT)>24;