In SQL how to calculate days in a year based on a start date and the number of days lapsed - sql

What would be the SQL to calculate the number of days in each year if I had a start date and the number of days that have lapsed?
For example, the date (ymd) 2013-01-01 and the days lapsed is 1000.
I would like the result to look like this
2013 = 365
2014 = 365
2015 = 270
Can this be written as a function like datediff?
I have tried using a calendar table, but of course, linking to this just gives me 2013 = 1000
My calendar table looks like this.
DATE_ID | DATE | CALENDAR_YEAR | FINANCIAL_YEAR
-----------------------------------------------
20130101 | 2013-01-01 | 2013 | 2013/14
This is what i have tried.
SELECT
D.FISCAL_YEAR, SUM([DAYS]) AS NUMBER_OF_DAYS
FROM [dbo].[FACT] F
LEFT JOIN [dbo].[DIM_DATE] D ON D.DATE_ID = F.DATE_ID
GROUP BY
D.FISCAL_YEAR
The result for this is.
FISCAL_YEAR | NUMBER_OF_DAYS
----------------------------
2013/14 |2820
2014/15 |6635
2015/16 |2409

I would personally build a tally table to do this. Once you build that, you can easly get every date and count the number of days in each year:
DECLARE #YMD date = '20130101',
#Lapsed int = 1000;
--Build a Tally table
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3, N N4), --10,000 should be enough
--Build the dates table
Dates AS(
SELECT DATEADD(DAY, T.I, #YMD) AS CalendarDate
FROM Tally T
WHERE T.I <= #Lapsed - 1)
--And count the days
SELECT DATEPART(YEAR, CalendarDate) AS Year,
COUNT(CalendarDate) AS Days
FROM Dates D
GROUP BY DATEPART(YEAR, CalendarDate);
As a function:
CREATE FUNCTION CountDays (#YMD date, #Lapsed int)
RETURNS table
AS RETURN
--Build a Tally table
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3, N N4), --10,000 should be enough
--Build the dates table
Dates AS(
SELECT DATEADD(DAY, T.I, #YMD) AS CalendarDate
FROM Tally T
WHERE T.I <= #Lapsed - 1)
--And count the days
SELECT DATEPART(YEAR, CalendarDate) AS Year,
COUNT(CalendarDate) AS Days
FROM Dates D
GROUP BY DATEPART(YEAR, CalendarDate);
GO
SELECT *
FROM (VALUES('20130101',1000),
('20150501',755))V(YMD, Lapsed)
CROSS APPLY dbo.CountDays(V.YMD,V.Lapsed) CD;

One method is a recursive CTE:
with dates as (
select v.d, 1000 - datediff(day, v.d, dateadd(year, 1, v.d)) as days
from (values (datefromparts(2013, 1, 1))) v(d)
union all
select dateadd(year, 1, d), days - datediff(day, d, dateadd(year, 1, d))
from dates
where days > 0
)
select d,
(case when days > 0 then datediff(day, d, dateadd(year, 1, d))
else datediff(day, d, dateadd(year, 1, d)) + days
end)
from dates;
Here is a db<>fiddle.

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

Get an interval of dates from a range of dates

I have two dates 21/10/2019 and 26/6/2031, and from these two I need a list of dates with three months interval starting from the first date like this:
22/10/2019 | 21/01/2020
22/01/2020 | 21/04/2020
22/04/2020 | 21/07/2020
22/07/2020 | 21/10/2020
...
22/01/2031 | 21/04/2031
22/04/2031 | 26/06/2031
I've tried using ROW_NUMBER() and DENSE_RANK() and LAG() to group a complete list of dates between the two dates, but I can't seem to figure it out. I think I might need to partition this somehow, but I can't get it right.
If you don't understand, please let me know. I'm pretty new at this :)
You can use a recursive query:
with cte (dt, end_dt) as (
select #start_dt, #end_dt
union all
select dateadd(month, 3, dt), end_dt from cte where dt < end_dt
)
select dt,
case when dateadd(month, 3, dt) < end_dt
then dateadd(day, -1, dateadd(month, 3, dt))
else end_dt
end as end_dt
from cte
order by dt;
If you need to generate more than 100 quarters, you need to add option (maxrecursion 0) at the very end of the query.
Demo on DB Fiddle
This could also be done using a 'tally' or numbers based approach. The upper limit of the tally_cte is 12^5=248,832 (more than recursion can produce and it could be increased as much as needed).
declare
#start_dt datetime='2019-10-21',
#end_dt datetime='2031-06-26'
;with
n(n) as (select * from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) v(n)),
tally_cte(n) as (
select row_number() over (order by (select null))
from n n1 cross join n n2 cross join n n3 cross join n n4 cross join n n5)
select t.*, cast(dt.dt as date) start_dt, cast(dateadd(MONTH, 3, dt.dt) as date) end_dt
from tally_cte t
cross apply (select dateadd(month, (n-1)*3, #start_dt) dt) dt
where n<datediff(month, #start_dt, #end_dt)/3;
N start_dt end_dt
1 2019-10-21 2020-01-21
2 2020-01-21 2020-04-21
3 2020-04-21 2020-07-21
...
45 2030-10-21 2031-01-21

How to get previous 7 days' data from today in SQL Server

I have a DataEntry Table called GuestAddressData(UserId INT, EDate DateTime) with users data. I need to fetch the count of users for today to previous 7 Days. My Query:
SELECT
row_number() over (order by (SELECT 1)) ID,
count(*) Total,
LEFT(Datename(weekday, Cast(EDate as date)), 3) Day
FROM
CRM0001GuestAddressData
WHERE
EDate >= dateadd(week, datediff(d, -1, getdate()-2)/7, -1)
GROUP BY
Cast(EDate as date)
ORDER BY
Cast(EDate as date)
For example if today is Friday then my expected output is:
ID | TOTAL | DAY
------------------------
1 | 78 | Sat
2 | 23 | Sun
3 | 54 | Mon
4 | 17 | Tues
5 | 56 | Wed
6 | 45 | Thus
7 | 78 | Fri - Today
but this is not correct. How to solve it?
You can "generate" a list of seven numbers and use it to build the desired dates. Then left join with your data to get the counts, including zeros:
WITH datelist(num, a, b) AS (
SELECT num, DATEADD(DAY, -num, CAST(CURRENT_TIMESTAMP AS DATE)), DATEADD(DAY, -num + 1, CAST(CURRENT_TIMESTAMP AS DATE))
FROM (VALUES (0), (1), (2), (3), (4), (5), (6)) AS v(num)
)
SELECT 7 - num AS ID, datelist.a AS Day, COUNT(IDBooking)
FROM datelist
LEFT JOIN T_Bookings ON Opened >= datelist.a AND Opened < datelist.b
GROUP BY datelist.a, datelist.num
ORDER BY datelist.a
SELECT
row_number() over (order by dDate) ID,
cnt,
LEFT(Datename(weekday, dDate), 3) Day
from
(Select cast(EDate as Date) as dDate,
count(*) as cnt
FROM (values (0),(1),(2),(3),(4),(5),(6)) t(v)
inner join
CRM0001GuestAddressData gd on datediff(d, gd.Edate, getdate()) = t.v
WHERE
EDate >= dateadd(d, -6, cast(getdate() as date)) and EDate < dateadd(d,1,cast(getdate() as date))
GROUP BY
Cast(EDate as date)) tmp;
Note: You meant to get 7 days from yesterday, right? Nevermind, corrected based on your sample.
DBFiddle demo
EDIT: Having all days:
SELECT
row_number() over (order by dDate) ID,
cnt,
LEFT(Datename(weekday, dDate), 3) Day
from
(Select dateadd(d,-v,cast(getdate() as date)) as dDate,
count(Edate) as cnt
FROM (values (0),(1),(2),(3),(4),(5),(6)) t(v)
left join
CRM0001GuestAddressData gd on Datediff(d,gd.EDate, getdate()) = t.v
GROUP BY
dateadd(d,-v,cast(getdate() as date))) tmp;
DBFiddle Demo

SQL Server Group by date and by time of day over a date range

I'm not even sure if this can/should be done is SQL but here goes.
I have a table that stores a start date and an end date like so
userPingId createdAt lastUpdatedAt
1 2017-10-17 11:31:52.160 2017-10-18 14:31:52.160
I want to return a result set that groups the results by date and if they were active between different points between the two date.
The different points are
Morning - Before 12pm
Afternoon - Between 12pm and 5pm
Evening - After 5pm
So for example I would get the following results
sessionDate morning afternoon evening
2017-10-17 1 1 1
2017-10-18 1 1 0
Here is what I have so far and I believe that it's quite close but the fact I can't get the results I need make me think that this might not be possible in SQL (btw i'm using a numbers lookup table in my query which I saw on another tutorial)
DECLARE #s DATE = '2017-01-01', #e DATE = '2018-01-01';
;WITH d(sessionDate) AS
(
SELECT TOP (DATEDIFF(DAY, #s, #e) + 1) DATEADD(DAY, n-1, #s)
FROM dbo.Numbers ORDER BY n
)
SELECT
d.sessionDate,
sum(case when
(CONVERT(DATE, createdAt) = d.sessionDate AND datepart(hour, createdAt) < 12)
OR (CONVERT(DATE, lastUpdatedAt) = d.sessionDate AND datepart(hour, lastUpdatedAt) < 12)
then 1 else 0 end) as Morning,
sum(case when
(datepart(hour, createdAt) >= 12 and datepart(hour, createdAt) < 17)
OR (datepart(hour, lastUpdatedAt) >= 12 and datepart(hour, lastUpdatedAt) < 17)
OR (datepart(hour, createdAt) < 12 and datepart(hour, lastUpdatedAt) >= 17)
then 1 else 0 end) as Afternoon,
sum(case when datepart(hour, createdAt) >= 17 OR datepart(hour, lastUpdatedAt) >= 17 then 1 else 0 end) as Evening
FROM d
LEFT OUTER JOIN MYTABLE AS s
ON s.createdAt >= #s AND s.lastUpdatedAt <= #e
AND (CONVERT(DATE, s.createdAt) = d.sessionDate OR CONVERT(DATE, s.lastUpdatedAt) = d.sessionDate)
WHERE d.sessionDate >= #s AND d.sessionDate <= #e
AND userPingId = 49
GROUP BY d.sessionDate
ORDER BY d.sessionDate;
Building on what you started with the numbers table, you can add the time ranges to your adhoc calendar table using another common table expression using cross apply()
and the table value constructor (values (...),(...)).
From there, you can use an inner join based on overlapping date ranges along with conditional aggregation to pivot the results:
declare #s datetime = '2017-01-01', #e datetime = '2018-01-01';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, d as ( /* adhoc date/numbers table */
select top (datediff(day, #s, #e)+1)
SessionDate=convert(datetime,dateadd(day,row_number() over(order by (select 1))-1,#s))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by SessionDate
)
, h as ( /* add time ranges to date table */
select
SessionDate
, StartDateTime = dateadd(hour,v.s,SessionDate)
, EndDateTime = dateadd(hour,v.e,SessionDate)
, v.point
from d
cross apply (values
(0,12,'morning')
,(12,17,'afternoon')
,(17,24,'evening')
) v (s,e,point)
)
select
t.userPingId
, h.SessionDate
, morning = count(case when point = 'morning' then 1 end)
, afternoon = count(case when point = 'afternoon' then 1 end)
, evening = count(case when point = 'evening' then 1 end)
from t
inner join h
on t.lastupdatedat >= h.startdatetime
and h.enddatetime > t.createdat
group by t.userPingId, h.SessionDate
rextester demo: http://rextester.com/MVB77123
returns:
+------------+-------------+---------+-----------+---------+
| userPingId | SessionDate | morning | afternoon | evening |
+------------+-------------+---------+-----------+---------+
| 1 | 2017-10-17 | 1 | 1 | 1 |
| 1 | 2017-10-18 | 1 | 1 | 0 |
+------------+-------------+---------+-----------+---------+
Alternately, you could use pivot() instead of conditional aggregation in the final select:
select UserPingId, SessionDate, Morning, Afternoon, Evening
from (
select
t.userPingId
, h.SessionDate
, h.point
from t
inner join h
on t.lastupdatedat >= h.startdatetime
and h.enddatetime > t.createdat
) t
pivot (count(point) for point in ([Morning], [Afternoon], [Evening])) p
rextester demo: http://rextester.com/SKLRG63092
You can using PIVOT on CTE's to derive solution to this problem.
Below is the test table
select * from ping
Below is the sql query
;with details as
(
select userPingId, createdAt as presenceDate , convert(date, createdAt) as
onlyDate,
datepart(hour, createdAt) as onlyHour
from ping
union all
select userPingId, lastUpdatedAt as presenceDate , convert(date,
lastUpdatedAt) as onlyDate,
datepart(hour, lastUpdatedAt) as onlyHour
from ping
)
, cte as
(
select onlyDate,count(*) as count,
case
when onlyHour between 0 and 12 then 'morning'
when onlyHour between 12 and 17 then 'afternoon'
when onlyHour>17 then 'evening'
end as 'period'
from details
group by onlyDate,onlyHour
)
select onlyDate, coalesce(morning,0) as morning,
coalesce(afternoon,0) as afternoon , coalesce(evening,0) as evening from
(
select onlyDate, count,period
from cte ) src
pivot
(
sum(count)
for period in ([morning],[afternoon],[evening])
) p
Below is the final result
This is a fairly similar answer to the one already posted, I just wanted the practice with PIVOT :)
I use a separate table with the time sections in it. this is then cross joined with the number table to create a date and time range for bucketing. i join this to the data and then pivot it (example: https://data.stackexchange.com/stackoverflow/query/750496/bucketing-data-into-date-am-pm-evening-and-pivoting-results)
SELECT
*
FROM (
SELECT
[userPingId],
dt,
[desc]
FROM (
SELECT
DATEADD(D, number, #s) AS dt,
CAST(DATEADD(D, number, #s) AS datetime) + CAST(s AS datetime) AS s,
CAST(DATEADD(D, number, #s) AS datetime) + CAST(e AS datetime) AS e,
[desc]
FROM #numbers
CROSS JOIN #times
WHERE number < DATEDIFF(D, #s, #e)
) ts
INNER JOIN #mytable AS m
ON m.createdat < ts.e
AND m.[lastUpdatedAt] >= ts.s
) src
PIVOT
(
COUNT([userPingId])
FOR [desc] IN ([am], [pm], [ev])
) piv;
the #times table is just:
s e desc
00:00:00.0000000 12:00:00.0000000 am
12:00:00.0000000 17:00:00.0000000 pm
17:00:00.0000000 23:59:59.0000000 ev

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