SQL: Can't insert a date loop into a column - sql-server

I have a table like this:
id: PK bigint
RatePercent: decimal(4, 4)
DateRange: date
I am trying to populate the table as follows:
RatePercentage with all of them 0.12
Date starting from '01-01-2015' to '12-31-2099'
Unfortunately with my query it won't do that and it keeps saying that
Operand type clash: date is incompatible with int
I haven't assigned an int datatype asides from the id bigint. I'm a bit confused.
Here is my query so far:
DECLARE #Date Date
SET #Date = '01-01-2015'
WHILE #Date <= '12-31-2099'
BEGIN
INSERT INTO [dbo].[IMF_Main_VATHistory] (VATRate, VATDate)
VALUES (0.12, #Date + 1);
END

Try this:
DECLARE #Date Date
SET #Date = '01-01-2015'
WHILE #Date <= '12-31-2099'
BEGIN
INSERT INTO [dbo].[IMF_Main_VATHistory] (VATRate, VATDate)
VALUES (0.12, DATEADD(DAY, 1, #Date));
SET #Date = DATEADD(DAY, 1, #Date);
END
You can't issue a direct addition to a DATE datatype, in SQL Server (for reference, I think you can in Oracle). You have to use functions in order to modify a DATE/DATETIME variable (or column).
Here is an example SQLFiddle.

The problem is in you "#Date + 1" I think - The SQL-Server likes to try and convert to INT :)
Use DATEADD that should work
DECLARE #Date Date
SET #Date = '01-01-2015'
WHILE #Date <= '12-31-2099'
BEGIN
INSERT INTO [dbo].[IMF_Main_VATHistory] (VATRate, VATDate)
VALUES (0.12, #Date);
SET #Date = DATEADD(DAY, 1, #Date);
END

I'll advise against using any loop-based or RBAR solution. You can do this using a set-based approach with the help of a Tally Table.
DECLARE #startDate DATE
DECLARE #endDate DATE
SET #startDate = '20150101'
SET #endDate = '20991231';
WITH E1(N) AS(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
),
E2(N) AS(SELECT 1 FROM E1 a, E1 b),
E4(N) AS(SELECT 1 FROM E2 a, E2 b),
E8(N) AS(SELECT 1 FROM E4 a, E4 b),
Tally(n) AS(
SELECT TOP(DATEDIFF(DAY, #startDate, #endDate) + 1)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E8
)
INSERT INTO IMF_Main_VATHistory(RatePercent, DateRange)
SELECT
0.02, DATEADD(DAY, N-1, #startDate)
FROM Tally
It's faster compared to using CURSORs or WHILE loops.

Related

Stored procedure to add 30 days using DATEDIFF within while loop condition in Date Dimension table

I want to add 30 consecutive days of data in my Date Dimension table using DATEDIFF() but I am getting blank result. Can you please help me correct the code below to get the desired result?
CREATE TABLE dbo.dateDimension (
DateKey INT NOT NULL
,DateValue DATE NOT NULL
,CYear SMALLINT NOT NULL
,CMonth TINYINT NOT NULL
,CONSTRAINT PK_DimDate PRIMARY KEY ( DateKey )
);
GO
CREATE PROC dbo.dateTest
#StartDate DATETIME
AS
WHILE (DATEDIFF(day, #StartDate, GETDATE()) <=30)
BEGIN
INSERT into dbo.dateDimension
SELECT CAST( YEAR(#StartDate) * 10000 + MONTH(#StartDate) * 100 + DAY(#StartDate) AS INT)
,#StartDate
,YEAR(#StartDate)
,MONTH(#StartDate)
SET #StartDate = DATEADD(d,1,#StartDate)
END;
GO
EXECUTE dbo.dateTest '2010-01-01'
SELECT * FROM dbo.dateDimension
The issue is that this logic:
DATEDIFF(day, #StartDate, GETDATE())
gives 3739 days with your current start date, so its never less than 30. Personally I would simply count it as follows:
DECLARE #StartDate DATETIME = '2010-01-01', #Count INT = 0;
WHILE #Count <= 30 BEGIN
INSERT into dbo.dateDimension
SELECT CAST( YEAR(#StartDate) * 10000 + MONTH(#StartDate) * 100 + DAY(#StartDate) AS INT)
, #StartDate
, YEAR(#StartDate)
, MONTH(#StartDate);
SET #StartDate = DATEADD(d,1,#StartDate);
set #Count = #Count + 1;
END;
SELECT *
FROM dbo.dateDimension;
If you are using SQL Server 2016 or above, this solution will not use a while loop, instead it uses a CTE to generate 30 rows numbered I to 30 and then uses the date to convert to yyyymmdd.
DECLARE #NUM_DAYS INT=30;
DECLARE #STARTDATE DATETIME='2020-01-01';
WITH CTE AS(
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS SRL
FROM STRING_SPLIT(REPLICATE(',',#num_days-1), ',') AS A
)
INSERT INTO dbo.dateDimension
SELECT
CONVERT(INT, CONVERT(CHAR(8), DATEADD(DAY, SRL-1, #STARTDATE), 112))
, #STARTDATE
, YEAR(#STARTDATE)
, MONTH(#STARTDATE)
FROM CTE
SELECT * FROM dbo.dateDimension

Date function in Stored Procedure

I created a stored procedure to get the weekly result. So, with two input parameters(startDate, endDate) I can get the result which I expected. But the problem here is, when the week starts from 28/08/2015 and end at 03/09/2015 am getting 28, 29, 30, 31, 32, 33, 34 as days of the week. But expected result is 28, 29, 30, 31, 01, 02, 03, 04.
Here is my code for the above details. Help me to overcome this problem
ALTER PROCEDURE [dbo].[usp_Get_TimesheetDetails]
#UserID int, #startdate datetime, #enddate datetime
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
declare #intStartDate int
declare #intEndDate int
declare #strMonth varchar(50)
SELECT #intStartDate = DATEPART(day,#startDate)
Select #intEndDate = DATEPART(day,#endDate)
select #strMonth = DATENAME(MONTH, GETDATE())
declare #temptable table (num int)
declare #columns varchar(max)
DECLARE #sqlText nvarchar(1000);
DECLARE #startnum INT=#intStartDate-1
DECLARE #endnum INT=#intEndDate
;
WITH gen AS (
SELECT #startnum AS num
UNION ALL
SELECT num+1 FROM gen WHERE num+1<=#endnum
)
You need to pass start date and end date values as dates and not as Integers.
Try this...
DECLARE #startnum DATE = '20150828' --<-- Use ANSI date `YYYYMMDD`
DECLARE #endnum DATE = '20150903'
;WITH gen AS (
SELECT #startnum AS num
UNION ALL
SELECT DATEADD(DAY,1,num) FROM gen
WHERE DATEADD(DAY,1,num) <= #endnum
)
SELECT * FROM Gen
Result:
num
2015-08-28
2015-08-29
2015-08-30
2015-08-31
2015-09-01
2015-09-02
2015-09-03
Here's another solution using a strongly-typed date but without the recursive CTE loop.
WITH
t4 AS (SELECT n FROM (VALUES(0),(0),(0),(0)) t(n))
,t64 AS (SELECT ROW_NUMBER() OVER (ORDER BY (a.n)) - 1 AS num FROM t4 AS a, t4 AS b, t4 AS c)
SELECT DATEADD(day, num, #StartDate)
FROM t64
WHERE num <= DATEDIFF(day, #StartDate, #EndDate);
You need to pass start date and end date instead of integer values. Try this
DECLARE #startnum DATE = '08/28/2015'
DECLARE #endnum DATE ='09/03/2015';
DECLARE #startnum DATE = '08/28/2015'
DECLARE #endnum DATE ='09/03/2015';
WITH gen
AS (SELECT #startnum AS DAT
UNION ALL
SELECT Dateadd(DD, 1, DAT)
FROM gen
WHERE DAT <= #endnum)
SELECT RIGHT('0' + Cast(Day(DAT) AS VARCHAR(2)), 2)
FROM GEN
Note : Since here the date range is very small i have used recursive cte approach else you can use tally table to generate dates

How to efficiently loop through using Sql query

As I have From and To date. Something like below,
BeginDate End Date
1989-01-01 00:00:00.000 2015-12-31 00:00:00.000
I need to loop through until i get the list of all the Date's between those 2 (Begin & End Date's) records. I need to know what will be the efficient way of doing this. I have no clue on how to do this. Any help to this will be highly appreciated.
Thanks
This method uses a generated numbers table and is probably faster than looping.
DECLARE #BeginDate DATETIME = '19890101';
DECLARE #EndDate DATETIME = '20151231';
WITH
E1(N) AS ( SELECT 1 FROM ( VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) DT(N)),
E2(N) AS ( SELECT 1 FROM E1 A, E1 B),
E4(N) AS ( SELECT 1 FROM E2 A, E2 B),
Numbers(N) AS
(
SELECT ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL)) - 1 FROM E4
)
SELECT
N,
DATEADD(D, N, #BeginDate) AS TheDate
FROM Numbers
WHERE N <= DATEDIFF(D, #BeginDate, #EndDate)
You can do this with WHILE loop:
DECLARE #sdt DATE = '1989-01-01'
DECLARE #edt DATE = '2015-12-31'
WHILE #sdt <= #edt
BEGIN
PRINT #sdt
SET #sdt = DATEADD(dd, 1, #sdt )
END
Or with recursive CTE:
DECLARE #sdt DATE = '1989-01-01'
DECLARE #edt DATE = '2015-12-31';
WITH cte
AS ( SELECT #sdt AS sdt
UNION ALL
SELECT DATEADD(dd, 1, sdt)
FROM cte
WHERE DATEADD(dd, 1, sdt) <= #edt
)
SELECT *
FROM cte
OPTION ( MAXRECURSION 10000 )
There is also tally table method as in link provided by #Bridge
Actually the answer is tally tables. But if there is not a big interval the difference will be insignificant.
Something like this should work for your purposes:
DECLARE #sd date = '1989-01-01 00:00:00.000'
, #ed date = '2015-12-31 00:00:00.000'
DECLARE #tt TABLE(
[Date] date
)
WHILE(#sd <= #ed) --Loop which checks each iteration if the date has reached the end
BEGIN
INSERT INTO #tt
SELECT #sd AS Date
SET #sd = DATEADD(dd,1,#sd) --This willl increment the date so you actually advance the loop
END
SELECT * FROM #tt

SQL server find the date between two selected date [duplicate]

This question already has answers here:
Get a list of dates between two dates using a function
(21 answers)
Closed 8 years ago.
I need the sql query which gives result between all date by passing two dates. For example, suppose I sent 01/01/2015 and 10/01/2015
I need the date between passed dates
The result should be :
01/01/2015
02/01/2015
03/01/2015
04/01/2015
05/01/2015
..
10/01/2015
How can I achieve that?
You can use a recursive CTE for this:
DECLARE #startDate DATETIME = '2015-01-01',
#endDate DATETIME = '2015-01-10'
;WITH dates AS(
SELECT #startDate AS date
UNION ALL
SELECT DATEADD(dd,1,date)
FROM dates
WHERE date<#endDate
)
SELECT * FROM dates
Try this :
declare #strt date;
declare #end date;
select #strt = '20150101';
select #end = '20150110';
with dates as
(
select dt = dateadd(dd, 0, #strt)
where dateadd(dd, 1, #strt) <= #end
union all
select dateadd(dd, 1, dt)
from dates
where dateadd(dd, 1, dt) <= #end
)
select * from dates
Use a Numbers table to generate dates.
DECLARE #StartDate DATETIME = convert(date,'01/01/2015',103) -- First Calendar date to include in table
DECLARE #EndDate DATETIME = convert(date,'10/01/2015',103) --Last calendar date to include in the table
;WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
E02(N) AS (SELECT 1 FROM E00 a, E00 b),
E04(N) AS (SELECT 1 FROM E02 a, E02 b),
E08(N) AS (SELECT 1 FROM E04 a, E04 b),
E16(N) AS (SELECT 1 FROM E08 a, E08 b),
E32(N) AS (SELECT 1 FROM E16 a, E16 b),
cteTally(N)
AS (SELECT Row_number()OVER ( ORDER BY N)
FROM E32),
CalendarBase
AS (SELECT CalendarDate = Dateadd(day, n - 1, #StartDate)
FROM cteTally
WHERE N <= Datediff(day, #StartDate, #EndDate + 1))
SELECT convert(varchar(30),CalendarDate ,103) as CalendarDate
FROM CalendarBase
Referred from this link
#Saurabh Dhakate
Use the below code:
declare #date1 date ='2015-01-10'
declare #date2 date ='2015-01-01'
declare #lv_table table (datepart1 date)
declare #count int
declare #count1 int = 1
set #count=(select day(#date1) )
print #count
while #count1<=#count
begin
insert into #lv_table
select dateadd(day,#count1,#date2)
set #count1=#count1+1
print #count1
end
select * from #lv_table

Passing in Week Day name to get nearest date in SQL

I'm working on a query that deals with a frequency value (i.e. Mondays, Tuesdays, etc. - Think assignments).
So in my query I currently have a result of
jobId:1, personId:100, frequencyVal: 'Mondays'
jobId:2, personId:101, frequencyVal: 'Saturdays'
What I need is the next the 4 future(or current) dates for the frequencyVal.
So if today is 1/3/2015
I would need my result set to be
jobId:1, personId:100, frequencyVal: 'Mondays', futureDates: '1/5,1/12,1/19,1/26'
jobId:2, personId:102, frequencyVal: 'Saturdays', futureDates: '1/3,1/10,1/17,1/24'
I was looking at the following post:
How to find the Nearest (day of the week) for a given date
But that sets it for a specific date. And I'm looking at this being a web application and I want the dates for the current date. So if I try to run this query next Tuesday the future dates for jobId:1 would remove the 1/5 and add the 2/2.
Is there a way to pass in a weekday value to get the next nearest date?
I prefer a calendar table for this kind of query. Actually, I prefer a calendar table over date functions for most queries. Here's a minimal one. The one I use in production has more columns and more rows. (100 years of data is only 37k rows.)
create table calendar (
cal_date date not null primary key,
day_of_week varchar(15)
);
insert into calendar (cal_date) values
('2015-01-01'), ('2015-01-02'), ('2015-01-03'), ('2015-01-04'),
('2015-01-05'), ('2015-01-06'), ('2015-01-07'), ('2015-01-08'),
('2015-01-09'), ('2015-01-10'), ('2015-01-11'), ('2015-01-12'),
('2015-01-13'), ('2015-01-14'), ('2015-01-15'), ('2015-01-16'),
('2015-01-17'), ('2015-01-18'), ('2015-01-19'), ('2015-01-20'),
('2015-01-21'), ('2015-01-22'), ('2015-01-23'), ('2015-01-24'),
('2015-01-25'), ('2015-01-26'), ('2015-01-27'), ('2015-01-28'),
('2015-01-29'), ('2015-01-30'), ('2015-01-31'),
('2015-02-01'), ('2015-02-02'), ('2015-02-03'), ('2015-02-04'),
('2015-02-05'), ('2015-02-06'), ('2015-02-07'), ('2015-02-08'),
('2015-02-09'), ('2015-02-10'), ('2015-02-11'), ('2015-02-12'),
('2015-02-13'), ('2015-02-14'), ('2015-02-15'), ('2015-02-16'),
('2015-02-17'), ('2015-02-18'), ('2015-02-19'), ('2015-02-20'),
('2015-02-21'), ('2015-02-22'), ('2015-02-23'), ('2015-02-24'),
('2015-02-25'), ('2015-02-26'), ('2015-02-27'), ('2015-02-28')
;
update calendar
set day_of_week = datename(weekday, cal_date);
alter table calendar
alter column day_of_week varchar(15) not null;
alter table calendar
add constraint cal_date_matches_dow
check (datename(weekday, cal_date) = day_of_week);
create index day_of_week_ix on calendar (day_of_week);
Set the privileges so that
everyone can select, but
almost nobody can insert new rows, and
even fewer people can delete rows.
(Or write a constraint that can guarantee there are no gaps. I think you can do that in SQL Server.)
You can select the next four Mondays after today with a very simple SQL statement. (The current date is 2015-01-05, which is a Monday.)
select top 4 cal_date
from calendar
where cal_date > convert(date, getdate())
and day_of_week = 'Monday'
order by cal_date;
CAL_DATE
--
2015-01-12
2015-01-19
2015-01-26
2015-02-02
For me, this is a huge advantage. No procedural code. Simple SQL that is obviously right. Big win.
Your sample table
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
QUERY 1 : Select nearest 4 week of days in current month for particular week day
-- Gets first day of month
DECLARE #FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
;WITH CTE as
(
-- Will find all dates in current month
SELECT CAST(#FIRSTDAY AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(MONTH,1,#FIRSTDAY)
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal,DATES,
-- Get week difference for each weekday
DATEDIFF(WEEK,DATES,GETDATE()) WEEKDIFF,
-- Count the number of weekdays in a month
COUNT(DATES) OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES)) WEEKCOUNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
WHERE MONTH(DATES)= MONTH(GETDATE())
)
-- Converts to CSV and make sure that only nearest 4 week of days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal AND
((WEEKDIFF<3 AND WEEKDIFF>-3 AND WEEKCOUNT = 5) OR WEEKCOUNT <= 4)
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
For example, in Query2 the nearest date(here we take example as Saturday) of
2015-Jan-10 will be 01/03,01/10,01/17,01/24
2015-Jan-24 will be 01/10,01/17,01/24,01/31
QUERY 2 : Select next 4 week's dates for particular week day irrelevant of month
;WITH CTE as
(
-- Will find the next 4 week details
SELECT CAST(GETDATE() AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(DAY,28,GETDATE())
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal, DATES,
ROW_NUMBER() OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES) ORDER BY CTE.DATES) DATECNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
)
-- Converts to CSV and make sure that only 4 days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal
AND DATECNT < 5
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
The following would be the output if the GETDATE() (if its Saturday) is
2015-01-05 - 1/10, 1/17, 1/24, 1/31
2015-01-24 - 1/24, 1/31, 2/7, 2/14
There's no built-in function to do it. But you can try this, you may place it inside a Scalar-Valued Function:
DECLARE #WeekDay VARCHAR(10) = 'Monday';
DECLARE #WeekDayInt INT;
SELECT #WeekDayInt = CASE #WeekDay
WHEN 'SUNDAY' THEN 1
WHEN 'MONDAY' THEN 2
WHEN 'TUESDAY' THEN 3
WHEN 'WEDNESDAY' THEN 4
WHEN 'THURSDAY' THEN 5
WHEN 'FRIDAY' THEN 6
WHEN 'SATURDAY' THEN 7 END
SELECT CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + #WeekDayInt) % 7, GETDATE())) AS NearestDate
UPDATE:
Looks like radar was right, here's the solution:
DECLARE #WeekDay VARCHAR(10) = 'Monday';
DECLARE #WeekDayInt INT;
DECLARE #Date DATETIME = GETDATE();
SELECT #WeekDayInt = CASE #WeekDay
WHEN 'SUNDAY' THEN 1
WHEN 'MONDAY' THEN 2
WHEN 'TUESDAY' THEN 3
WHEN 'WEDNESDAY' THEN 4
WHEN 'THURSDAY' THEN 5
WHEN 'FRIDAY' THEN 6
WHEN 'SATURDAY' THEN 7 END
DECLARE #Diff INT = DATEPART(WEEKDAY, #Date) - #WeekDayInt;
SELECT CONVERT(DATE, DATEADD(DAY, CASE WHEN #Diff >= 0 THEN 7 - #Diff ELSE ABS(#Diff) END, #Date)) AS NearestDate
Try this - based on king.code's answer to get the nearest date.
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
WITH cte(n) AS
(
SELECT 0
UNION ALL
SELECT n+1 FROM cte WHERE n < 3
)
select #t.jobId, #t.personId, #t.frequencyVal, STUFF(a.d, 1, 1, '') AS FutureDates
from #t
cross apply (SELECT CASE #t.frequencyVal
WHEN 'SUNDAYS' THEN 1
WHEN 'MONDAYS' THEN 2
WHEN 'TUESDAYS' THEN 3
WHEN 'WEDNESDAYS' THEN 4
WHEN 'THURSDAYS' THEN 5
WHEN 'FRIDAYS' THEN 6
WHEN 'SATURDAYS' THEN 7
END)tranlationWeekdays(n)
cross apply (select ',' + CONVERT(varchar(10), CONVERT(date,dateadd(WEEK, cte.n,CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + tranlationWeekdays.n) % 7, GETDATE()))))) from cte FOR XML PATH('')) a(d);
drop table #t;
Try this,
DECLARE #YEAR INT=2015
DECLARE #MONTH INT=1
DECLARE #DAY INT=1
DECLARE #DATE DATE = (SELECT DateFromParts(#Year, #Month, #Day))
DECLARE #TOTAL_DAYS INT =(SELECT DatePart(DY, #DATE));
WITH CTE1
AS (SELECT T_DAY=(SELECT DateName(DW, #DATE)),
#DATE AS T_DATE,
#DAY AS T_DDAY
UNION ALL
SELECT T_DAY=(SELECT DateName(DW, DateAdd(DAY, T_DDAY + 1, #DATE))),
DateAdd(DAY, T_DDAY + 1, #DATE) AS T_DATE,
T_DDAY + 1
FROM CTE1
WHERE T_DDAY + 1 <= 364)
SELECT DISTINCT T_DAY,
Stuff((SELECT ',' + CONVERT(VARCHAR(30), T_DATE)
FROM CTE1 A
WHERE A.T_DAY=CTE1.T_DAY AND A.T_DATE > GetDate() AND A.T_DATE<(DATEADD(WEEK,4,GETDATE()))
FOR XML PATH('')), 1, 1, '') AS FUTURE
FROM CTE1
ORDER BY T_DAY
OPTION (MAXRECURSION 365)
This is a simpler way I think, and I think it fits your requirements.
Note that I have changed your frequency_val column to an integer that represents the day of the week from SQL servers perspective and added a calculated column to illustrate how you can easily derive the day name from that.
declare #t table
(
jobId int,
personId int,
--frequencyVal varchar(10)
frequency_val int,
frequency_day as datename(weekday,frequency_val -1) + 's'
);
declare #num_occurances int = 4
declare #from_date date = dateadd(dd,3,getdate()) -- this will allow you to play with the date simply by changing the increment value
insert into #t
values
(1,100,1),--'Mondays'),
(2,101,6),--'Saturdays');
(3,101,7),--'Saturdays');
(4,100,2)--'Mondays'),
--select * from #t
;with r_cte (days_ahead, occurance_date)
as (select 0, convert(date,#from_date,121)
union all
select r_cte.days_ahead +1, convert(date,dateadd(DD, r_cte.days_ahead+1, #from_date),121)
from r_cte
where r_cte.days_ahead < 7 * #num_occurances
)
select t.*, r_cte.occurance_date
from
#t t
inner join r_cte
on DATEPART(WEEKDAY, dateadd(dd,##DATEFIRST - 1 ,r_cte.occurance_date)) = t.frequency_val
Having seen the use of DATENAME in some of the answers already given, I'd like to point out that return values of DATENAME might vary depending on your current language setting, but you can save the current language setting and ensure usage of us_english so you can be confident to use English weekday names.
Now here is my slightly different approach to get the 4 next dates that fall on a certain (known) weekday, using a user defined table valued function that allows to create a number sequence table (yes this is a pretty dull function, you have to pass MaxValue greater MinValue, but that could be easily enhanced, if needed, but hey, it does the job). Using that function span a table over 28 values (next 28 days should indeed include the next 4 relevant weekdays ;)), apply DATEADD on GETDATE and reduce the result set with WHERE to only those values that have the right weekday:
CREATE FUNCTION GetIntSequence(#MinValue INT, #MaxValue INT)
RETURNS #retSequence TABLE
(
IntValue INT NOT NULL
)
BEGIN
DECLARE #i INT = (SELECT #MinValue)
WHILE #i <= #MaxValue
BEGIN
INSERT INTO #retSequence (IntValue) SELECT #i
SELECT #i = #i + 1
END
RETURN
END
GO
DECLARE #weekDay NVARCHAR(MAX) = 'Monday' --(or Tuesday, wednesday, ...)
--save current language setting
DECLARE #languageBackup NVARCHAR(MAX) = (SELECT ##LANGUAGE)
--ensure us english language setting for reliable weekday names
SET LANGUAGE us_english;
SELECT FourWeeks.SomeDay FROM
(
SELECT
DATEADD(DAY, IntValue, GETDATE()) AS SomeDay
FROM dbo.GetIntSequence(1, 28)
) AS FourWeeks
WHERE DATENAME(WEEKDAY, SomeDay) = #weekDay
--restore old language setting
SET LANGUAGE #languageBackup;
GO
DROP FUNCTION dbo.GetIntSequence

Resources