How do I calculate Workdays except Weekends in SQL query? - sql

I want to calculate which of my holidays are conflict with working days.
I have a Holidays table which is below.
NameOfHoliday StartDate DurationByDay
Christmas 26.12.2015 5
26 and 27 are at weekend. So this shouldt be calculated. So I have to get only 4 days as result.

DECLARE #t TABLE(
NameOfHoliday VARCHAR(10),
StartDate DATE,
DurationByDay SMALLINT
)
INSERT INTO #t
VALUES ('Christmas', '20151226', 5)
;WITH cte AS
(
SELECT *, cnt = 0
FROM #t
UNION ALL
SELECT t2.NameOfHoliday, DATEADD(DAY, t1.cnt + 1, t2.StartDate), t2.DurationByDay, t1.cnt + 1
FROM cte t1
JOIN #t t2 ON t1.NameOfHoliday = t2.NameOfHoliday
WHERE t1.cnt < t1.DurationByDay
)
SELECT NameOfHoliday, StartDate, DT
FROM (
SELECT *
, DT = DATENAME(DW, StartDate)
, RowNum = ROW_NUMBER() OVER (PARTITION BY StartDate ORDER BY NameOfHoliday)
FROM cte
) t
WHERE RowNum = 1
AND DT NOT IN ('Saturday', 'Sunday')
OPTION (MAXRECURSION 0)
output -
NameOfHoliday StartDate DT
------------- ---------- ------------------
Christmas 2015-12-28 Monday
Christmas 2015-12-29 Tuesday
Christmas 2015-12-30 Wednesday
Christmas 2015-12-31 Thursday

I Hope your requirement is like this..
if object_id('tempdb..#Holidays') is not null drop table #Holidays
create table #Holidays(id int identity(1,1),Holiday date)
insert into #Holidays values ('2015-12-25'),('2015-12-28')
set dateformat ymd
declare #StartDate date = '2015-12-01'
declare #EndtDate date = '2015-12-31'
--some times #EndtDate might be NULL so, set to getdate()
set #EndtDate = isnull(#EndtDate,cast(getdate() as date))
declare #Holiday int
set #Holiday = (select count(*) from #Holidays where Holiday between #StartDate and #EndtDate)
SELECT
(DATEDIFF(dd, #StartDate, #EndtDate) + 1)
-(DATEDIFF(wk, #StartDate, #EndtDate) * 2)
-(CASE WHEN DATENAME(dw, #StartDate) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, #EndtDate) = 'Saturday' THEN 1 ELSE 0 END)
-#Holiday

Related

Keep last n business days records from today date in SQL Server

How can we keep last n business days records from today date in this table:
Suppose n = 7
Sample Data:
Table1:
Date
----------
2021-11-29
2021-11-30
2021-12-01
2021-12-02
2021-12-03
2021-12-04
2021-12-05
2021-12-06
2021-12-07
2021-12-08
2021-12-09
2021-12-10
2021-12-11
2021-12-12
2021-12-13
Based on this table data we want output like below. It should delete all the rows before the 03-Dec or data for last 7 business days.
Date
-------
2021-12-03
2021-12-06
2021-12-07
2021-12-08
2021-12-09
2021-12-10
2021-12-13
Note: It's fine if we keep data for Saturday, Sunday in between business days.
I tried this query
DECLARE #n INT = 7
SELECT * FROM Table1
WHERE [date] < Dateadd(day, -((#n + (#n / 5) * 2)), Getdate())
but Saturday, Sunday logic doesn't fit here with my logic. Please suggest better approach.
You can get the 7th working day from today as
select top(1) cast(dateadd(d, -n + 1, getdate()) as date) d
from (
select n
, sum (case when datename(dw, dateadd(d, -n + 1, getdate())) not in ('Sunday', 'Saturday') then 1 end) over(order by n) wdn
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)
)t0(n)
) t
where wdn = 7
order by n;
Generally using on-the-fly tally for a #n -th day
declare #n int = 24;
with t0(n) as (
select n
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
) t(n)
), tally as (
select top(#n + (#n/5 +1)*2) row_number() over(order by t1.n) n
from t0 t1, t0 t2, t0 t3
)
select top(1) cast(dateadd(d, -n + 1, getdate()) as date) d
from (
select n
, sum (case when datename(dw, dateadd(d, -n + 1, getdate())) not in ('Sunday', 'Saturday') then 1 end) over(order by n) wdn
from tally
) t
where wdn = #n
order by n;
You can use CTE to mark target dates and then delete all the others from the table as follows:
; With CTE As (
Select [Date], Row_number() Over (Order by [Date] Desc) As Num
From tbl
Where DATEPART(weekday, [Date]) Not In (6,7)
)
Delete From tbl
Where [Date] Not In (Select [Date] From CTE Where Num<=7)
If the number of business days in the table may be less than 7 and you need to bring the total number of days to 7 by adding days off, try this:
Declare #n Int = 7
; With CTE As (
Select [Date], IIF(DATEPART(weekday, [Date]) In (6,7), 0, 1) As IsBusinessDay
From tbl
)
Delete From tbl
Where [Date] Not In (Select Top(#n) [Date] From CTE Order By IsBusinessDay Desc, [Date] Desc)
If there is only one date for each day, you can simply do this:
SELECT TOP 7 [Date] FROM Table1
WHERE
[Date] < GETDATE() AND DATENAME(weekday, [DATE]) NOT IN ('Saturday', 'Sunday')
ORDER BY
[DATE] DESC

Count of days for rest of month returning incorrect value for EOM date

The below code when run for the last day of the month it is giving me a week day count of 1 when it should be 0 - how can I fix it?
Declare #EndDate DateTime = '03-31-2021'
;WITH mycte AS (
SELECT #EndDate + 1 DateValue
UNION ALL
SELECT DateValue + 1
FROM mycte
WHERE DateValue < EOMONTH(#EndDate)
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SunCount
, count(case when datepart(dw, DateValue) = 7 then 1 end) SatCount
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WeekCount
from mycte
Your initial CTE is actually creating a date of 2021-04-01 which is a week day, so that's where your count of 1 is coming from. If you want to restrict the counts to just the month in question, you could add a WHERE clause to your end query like this. This way, you get zeros for all counts.
Declare #EndDate DateTime = '03-31-2021'
;WITH mycte AS (
SELECT #EndDate + 1 DateValue
UNION ALL
SELECT DateValue +1
FROM mycte
WHERE DateValue < EOMONTH(#EndDate)
)
select
count(case when datepart(dw, DateValue) = 1 then 1 end) SunCount
, count(case when datepart(dw, DateValue) = 7 then 1 end) SatCount
, count(case when datepart(dw, DateValue) between 2 and 6 then 1 end) WeekDayCount
from mycte
where DATEPART(MM,DateValue)=DATEPART(MM,#EndDate)

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;

Generate list of dates based on the day and occurrence of the month

I want to generate based on the day of the week and number of the occurrence in the month of a date, a list of dates for each month between two dates. Assuming I have a #StartDate = 2016/04/01 and #EndDate = 2016/09/01, i check that #StartDate is on a first Friday of April, then to #EndDate will create dates for all first Friday of each month:
2016/05/06
2016/06/03
2016/07/01
2016/08/05
In case #StartDate = 2016/04/12 and #EndDate = 2016/09/01, I note that the #StartDate is the second Tuesday of April, then went to get every second Tuesday Tuesday of each month :
2016/05/10
2016/06/14
2016/07/12
2016/08/09
In case#StartDate = 2016/04/28 and #EndDate = 2016/09/01, I note that the #StartDate is on the last Thursday of the month of April:
2016/05/26
2016/06/30
2016/07/28
2016/08/25
In the last case, i need to verify the number of weeks of each month, because exists months only with 4 weeks or with 5 weeks and i want the last occurrence.
What I have done? I found a code that gives me every Monday in the third week of the month, and i adopted a little to get a #StartDate and #EndDate:
;with
filler as (select row_number() over (order by a) a from (select top 100 1 as a from syscolumns) a cross join (select top 100 1 as b from syscolumns) b),
dates as (select dateadd(month, a-1, #StartDate ) date from filler where a <= 1000 and dateadd(month, a-1, #StartDate) < #EndDate),
FirstMonday as (
select dateadd(day, case datepart(weekday,Date) /*this is the case where verify the week day*/
when 1 then 1
when 2 then 0
when 3 then 6
when 4 then 5
when 5 then 4
when 6 then 3
when 7 then 2
end, Date) as Date
,case when datepart(weekday, #StartDate) = 1 then 3 else 2 end as Weeks /*here i verify the number of weeks to sum in the next date*/
from dates
)
select dateadd(week, Weeks, Date) as ThirdMonday
from FirstMonday
So, it is:
set #NumSemana = datepart(day, datediff(day, DATEADD(mm, DATEDIFF(mm,0,#StartDate), 0), #StartDate)/7 * 7)/7 + 1;
WITH AllDays
AS ( SELECT #StartDate AS [Date], DATEPART(month, #StartDate) as validMonth
UNION ALL
SELECT DATEADD(week, 1, [Date]),
iif(DATEPART(month,DATEADD(week, 1, [Date])) < validMonth + #PeriodicityRepeat, validMonth, validMonth + #PeriodicityRepeat)
FROM AllDays
WHERE
DATEPART(month,[Date]) <= DATEPART(month,#EndDate)
and DATEPART(year,[Date]) <= DATEPART(year,#EndDate)
),
rankedDays
AS(
SELECT [Date], validMonth,
row_number() over ( partition by DATEPART( month, [Date]) order by [Date]) ascOrder,
row_number() over ( partition by DATEPART( month, [Date]) order by [Date] desc) descOrder
FROM AllDays
WHERE DATEPART(month, [Date]) = validMonth
)
select [Date]
from rankedDays
where ((ascOrder = #NumSemana and #NumSemana <=4 )
or (descOrder = 1 and #NumSemana = 5)
or [Date] = #StartDate )
and [Date] < #EndDate
OPTION (MAXRECURSION 0)
Query:
DECLARE #StartDate DATE = '2016-04-28',
#EndDate DATE = '2016-09-01'
;WITH dates AS (
SELECT DATEADD(week, -5, #StartDate) as date_
UNION ALL
SELECT DATEADD(week,1,date_)
FROM dates
WHERE DATEADD(week,1,date_) < #enddate
), final AS (
SELECT ROW_NUMBER() OVER (PARTITION BY DATEPART(year,date_), DATEPART(month,date_) ORDER BY date_ ASC) as RN,
date_
FROM dates
), weeks AS (
SELECT *
FROM (VALUES
(1,1),
(2,2),
(3,3),
(4,4),
(4,5),
(5,4),
(5,5)
) as t(w1,w2)
WHERE w1 = (SELECT RN FROM final WHERE date_ = #StartDate)
)
SELECT MAX(date_) as date_
FROM final f
INNER JOIN weeks w ON f.RN = w.w2
WHERE date_ between #StartDate and #EndDate AND date_ != #StartDate
GROUP BY DATEPART(YEAR,date_), DATEPART(MONTH,date_)
ORDER BY MAX(date_) ASC
Outputs:
For #StartDate = 2016/04/01 and #EndDate = 2016/09/01
date_
----------
2016-05-06
2016-06-03
2016-07-01
2016-08-05
(4 row(s) affected)
For #StartDate = 2016/04/12 and #EndDate = 2016/09/01
date_
----------
2016-05-10
2016-06-14
2016-07-12
2016-08-09
(4 row(s) affected)
For #StartDate = 2016/04/28 and #EndDate = 2016/09/01
date_
----------
2016-05-26
2016-06-30
2016-07-28
2016-08-25
(4 row(s) affected)

Working days from a given date

User will select a date in frontend and flexibledays, say for example if they have selected '2014-07-17' as date and flexibledays as 2, then we need to display both 2 previous and next 2 working days as like below,
2014-07-15
2014-07-16
2014-07-17
2014-07-20
2014-07-21
excluding weekends (friday and saturday), for use weekends is friday and saturday.
I have used the below query
DECLARE #MinDate DATE, #MaxDate DATE;
SELECT #MinDate = DATEADD(Day, -#inyDays ,#dtDate), #MaxDate = DATEADD(Day,#inyDays ,#dtDate)
DECLARE #DayExclusionValue VARCHAR(20)
SELECT #DayExclusionValue = dbo.UDF_GetConfigSettingValue('DaysToExclude')
DECLARE #NumOfWeekends INT
SELECT #NumOfWeekends= (DATEDIFF(wk, #MinDate, #MaxDate) * 2) +(CASE WHEN DATENAME(dw, #MinDate) = 'Friday' THEN 1 ELSE 0 END) +(CASE WHEN DATENAME(dw, #MaxDate) = 'Saturday' THEN 1 ELSE 0 END)
SET #MaxDate = DATEADD(Day,#inyDays + #NumOfWeekends ,#dtDate)
;WITH CalculatedDates AS
(
SELECT dates = #MinDate
UNION ALL
SELECT DATEADD(day, 1, dates)
FROM CalculatedDates
WHERE DATEADD(day, 1, dates) <= #MaxDate
)
SELECT dates FROM CalculatedDates
WHERE dates >= CAST(GETDATE() AS DATE)
AND DATENAME(DW, dates) NOT IN (SELECT Value FROM UDF_GetTableFromString(#DayExclusionValue))
OPTION (MAXRECURSION 0);
but the above query is not working properly.
Can you pls suggest me any other solution.
This example will work for Oracle, you did not say what DB you were using. If you have a list of vacations you need to join that in as indicated. It would need to be a outerjoin, and you need to add a case or something so that the vacation tables 'exclude' days override the generated days.
Also I chose a multiplier on random. When only dealing with weekend 8 was more than enough, but if your vacation table includes a lot of consecutive vacation days it might no longer be.
select d from(
select rownum nn, d, sysdate - d, first_value (rownum) over (order by abs(sysdate-d)) zero_valu
from (
select sysdate+n d, to_char(sysdate+n,'DAY'), CASE to_char(sysdate+n,'D') WHEN '6' THEN 'exclude' WHEN '7' THEN 'exclude' ELSE 'include' END e_or_i from
(SELECT ROWNUM-9 n -- 9=flexibleday*8/2 +1
FROM ( SELECT 1 just_a_column
FROM dual
CONNECT BY LEVEL <= 16 -- 8=flexibleday * 8
)
)
) where e_or_i = 'include' -- in this step you need to join in a table of holidays or such if you need that.
) where abs(nn-7) <= 2 -- 2=flexiday
order by d
DECLARE #StartDate DATE = '2014-07-17';
SELECT *
FROM
(
--Show Closest Previous 2 Days Not In Friday or Saturday
SELECT TOP 2
DATEADD(DAY, -nr, #StartDate) CheckDate,
DATENAME(DW, DATEADD(DAY, -nr, #StartDate)) CheckName,
-nr CheckCount
FROM (VALUES(1),(2),(3),(4)) AS Numbers(nr)
WHERE DATENAME(DW, DATEADD(DAY, -nr, #StartDate)) NOT IN ('Friday','Saturday')
UNION ALL
--Show Todays Date If Not Friday or Saturday
SELECT TOP 1
DATEADD(DAY, +nr, #StartDate) CheckDate,
DATENAME(DW, DATEADD(DAY, +nr, #StartDate)) CheckName,
nr CheckCount
FROM (VALUES(0)) AS Numbers(nr)
WHERE DATENAME(DW, DATEADD(DAY, +nr, #StartDate)) NOT IN ('Friday','Saturday')
UNION ALL
--Show Closest Next 2 Days Not In Friday or Saturday
SELECT TOP 2
DATEADD(DAY, +nr, #StartDate) CheckDate,
DATENAME(DW, DATEADD(DAY, +nr, #StartDate)) CheckName,
nr CheckCount
FROM (VALUES(1),(2),(3),(4)) AS Numbers(nr)
WHERE DATENAME(DW, DATEADD(DAY, +nr, #StartDate)) NOT IN ('Friday','Saturday')
) d
ORDER BY d.CheckDate
I break it into 3 parts, previous 2 days, today (if applicable) and next 2 days
Here is the output:
CheckDate CheckName CheckCount
2014-07-15 Tuesday -2
2014-07-16 Wednesday -1
2014-07-17 Thursday 0
2014-07-20 Sunday 3
2014-07-21 Monday 4
I use the datename since not sure what ##datefirst your server is set to. the values() section is just a numbers table (you should create a numbers table as big as the amount of records you want to return plus any weekends you are crossing over) and then the TOP 2 in the first and last sections would be replaced with the number of days you wanted to return before and after.
**** Update with generic numbers table functionality added:
Here we declare the starting date and the number of previous and next days we would like to pull:
DECLARE #StartDate DATE = '2014-07-20';
DECLARE #MaxBusDays INT = 5
This next section creates a numbers table (can be easily found via google)
DECLARE #number_of_numbers INT = 100000;
;WITH
a AS (SELECT 1 AS i UNION ALL SELECT 1),
b AS (SELECT 1 AS i FROM a AS x, a AS y),
c AS (SELECT 1 AS i FROM b AS x, b AS y),
d AS (SELECT 1 AS i FROM c AS x, c AS y),
e AS (SELECT 1 AS i FROM d AS x, d AS y),
f AS (SELECT 1 AS i FROM e AS x, e AS y),
numbers AS
(
SELECT TOP(#number_of_numbers)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS number
FROM f
)
Now we use the numbers table and a row_number setting to pull only the number of rows before and after (plus the date of, if it's not fri/sat as wanted) that are working days (not fri/sat)
SELECT *
FROM
(
--Show Closest Previous x Working Days (Not Friday or Saturday)
SELECT * FROM
(
SELECT
DATEADD(DAY, -number, #StartDate) CheckDate,
DATENAME(DW, DATEADD(DAY, -number, #StartDate)) CheckName,
-number CheckCount,
ROW_NUMBER() OVER (ORDER BY number ASC) AS RowCounter
FROM Numbers
WHERE DATENAME(DW, DATEADD(DAY, -number, #StartDate)) NOT IN ('Friday','Saturday')
) a
WHERE a.RowCounter <= #MaxBusDays
UNION ALL
--Show Todays Date If Working Day (Not Friday or Saturday)
SELECT TOP 1
#StartDate CheckDate,
DATENAME(DW, #StartDate) CheckName,
0 CheckCount,
0 RowCounter
WHERE DATENAME(DW, #StartDate) NOT IN ('Friday','Saturday')
UNION ALL
--Show Closest Next x Working Days (Not Friday or Saturday)
SELECT * FROM
(
SELECT
DATEADD(DAY, +number, #StartDate) CheckDate,
DATENAME(DW, DATEADD(DAY, +number, #StartDate)) CheckName,
number CheckCount,
ROW_NUMBER() OVER (ORDER BY number ASC) AS RowCounter
FROM Numbers
WHERE DATENAME(DW, DATEADD(DAY, +number, #StartDate)) NOT IN ('Friday','Saturday')
) b
WHERE b.RowCounter <= #MaxBusDays
) c
ORDER BY c.CheckDate
Here is the output: (2014-07-20 is the middle row)
CheckDate CheckName CheckCount RowCounter
2014-07-13 Sunday -7 5
2014-07-14 Monday -6 4
2014-07-15 Tuesday -5 3
2014-07-16 Wednesday -4 2
2014-07-17 Thursday -3 1
2014-07-20 Sunday 0 0
2014-07-21 Monday 1 1
2014-07-22 Tuesday 2 2
2014-07-23 Wednesday 3 3
2014-07-24 Thursday 4 4
2014-07-27 Sunday 7 5