Get all dates between 2 dates in SQL Server stored procedure - sql

I 'm using SQL Server 2014 and I have a table Attendance. This table has 2 columns AttdDate and Status. I want to create a stored procedure that returns list of dates between 2 dates, AttdDate and status. And if the AttdDate is in this list (dates list) status should be true else status should be false.
Any advice? Thanks

CREATE PROCEDURE sp_Attendance #Start DATETIME, #End DATETIME
AS
BEGIN
DECLARE #NumDays INT;
-- This will give you the number of days between your start date and end date.
SELECT #NumDays = DATEDIFF(DAY, #Start, #End) + 1;
WITH CTE AS (
SELECT TOP (#Numdays)
/*
ROW_NUMBER() OVER (ORDER BY a.object_id) will give you an integer from 1 to #NumDays becuase of TOP (#NumDays)
ROW_NUMBER() OVER (ORDER BY a.object_id) - 1 will give you an integer from 0 to #NumDays
DATEADD(DAY, ROW_NUMBER(), #Start) -- This will add the integer from the row number statement to your start date.
i.e.
#Start + 0
#Start + 1
#Start + 2
etc
etc
#Start + #NumDays
*/
DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY a.object_id) - 1, #Start) AS AttdDate
FROM
sys.all_columns a
CROSS JOIN
sys.all_columns b)
SELECT
c.AttdDate,
CASE WHEN a.AttdDate IS NOT NULL THEN 1 ELSE 0 END AS Status
FROM
CTE c
LEFT JOIN
Attendance a
ON c.AttdDate = a.AttdDate;
END;

You can use a recursive query for this:
WITH q(d) AS
(
SELECT #mindate
UNION ALL
SELECT DATEADD(day, 1 d)
FROM q
WHERE d < #maxdate
)
SELECT *
FROM q
OPTION (MAXRECURSION 0)
, however, its performance would be quite poor for longer ranges.
I usually just create a table with all possible dates up to year 2100 in my SQL Server databases:
SELECT *
FROM dates
WHERE d BETWEEN #mindate AND #maxdate

Related

Based on day fetch all dates - sql

I have start date, end date and name of days. How can fetch all dates between those two dates of that specific days in sql?
example data:
start_date:4/11/2018
end_date: 5/11/2018
days: monday, thursday
expected output: all dates between start and end date which comes on monday and thursday and store them in table
updated
my present code(not working)
; WITH CTE(dt)
AS
(
SELECT #P_FROM_DATE
UNION ALL
SELECT DATEADD(dw, 1, dt) FROM CTE
WHERE dt < #P_TO_DATE
)
INSERT INTO Table_name
(
ID
,DATE_TIME
,STATUS
,CREATED_DATE
,CREATED_BY
)
SELECT #P_ID
,(SELECT dt FROM CTE WHERE DATENAME(dw, dt) In ('tuesday','friday',null))
,'NOT SENT'
,CAST(GETDATE() AS DATE)
,#USER_ID
Another approach for generating dates between ranges can be like following query. This will be faster compared to CTE or WHILE loop.
DECLARE #StartDate DATETIME = '2018-04-11'
DECLARE #EndDate DATETIME = '2018-05-15'
SELECT #StartDate + RN AS DATE FROM
(
SELECT (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))-1 RN
FROM master..[spt_values] T1
) T
WHERE RN <= DATEDIFF(DAY,#StartDate,#EndDate)
AND DATENAME(dw,#StartDate + RN) IN('Monday','Thursday')
Note:
If the row count present in master..[spt_values] is not sufficient for the provided range, you can make a cross join with the same to get a bigger range like following.
SELECT (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)))-1 RN
FROM master..[spt_values] T1
CROSS JOIN master..[spt_values] T2
By this you will be able to generate date between a range with gap of 6436369 days.
You can use a recursive common table expression (CTE) to generate a list of days. With datepart(dw, ...) you can filter for specific days of the week.
An example that creates a list of Mondays and Thursdays between March 1st and today:
create table ListOfDates (dt date);
with cte as
(
select cast('2018-03-01' as date) as dt -- First day of interval
union all
select dateadd(day, 1, dt)
from cte
where dt < getdate() -- Last day of interval
)
insert into ListOfDates
(dt)
select dt
from cte
where datepart(dw, dt) in (2, 5) -- 2=Monday and 5=Thursday
option (maxrecursion 0)
See it working at SQL Fiddle.
This will work for you:
DECLARE #table TABLE(
ID INT IDENTITY(1,1),
Date DATETIME,
Day VARCHAR(50)
)
DECLARE #Days TABLE(
ID INT IDENTITY(1,1),
Day VARCHAR(50)
)
INSERT INTO #Days VALUES ('Monday')
INSERT INTO #Days VALUES ('Thursday')
DECLARE #StartDate DATETIME='2018-01-01';
DECLARE #EndDate DATETIME=GETDATE();
DECLARE #Day VARCHAR(50)='Friday';
DECLARE #TempDate DATETIME=#StartDate;
WHILE CAST(#TempDate AS DATE)<=CAST(#EndDate AS DATE)
BEGIN
IF EXISTS (SELECT 1 FROM #Days WHERE DAY IN (DATENAME(dw,#TempDate)))
BEGIN
INSERT INTO #table
VALUES (
#TempDate, -- Date - datetime
DATENAME(dw,#TempDate) -- Day - varchar(50)
)
END
SET #TempDate=DATEADD(DAY,1,#TempDate)
END
SELECT * FROM #table
INSERT INTO TargetTab(dateCOL)
SELECT dateCOL
FROM tab
WHERE dateCOL >= startdate AND dateCOL <= enddate
AND (DATENAME(dw,dateCOL) ='Thursday' OR DATENAME(dw,dateCOL) = 'Monday')
Try this query to get your result.
Use a recursive CTE to generate your dates, then filter by week day.
SET DATEFIRST 1 -- 1: Monday, 7 Sunday
DECLARE #StartDate DATE = '2018-04-11'
DECLARE #EndDate DATE = '2018-05-15'
DECLARE #WeekDays TABLE (WeekDayNumber INT)
INSERT INTO #WeekDays (
WeekDayNumber)
VALUES
(1), -- Monday
(4) -- Thursday
;WITH GeneratingDates AS
(
SELECT
GeneratedDate = #StartDate,
WeekDay = DATEPART(WEEKDAY, #StartDate)
UNION ALL
SELECT
GeneratedDate = DATEADD(DAY, 1, G.GeneratedDate),
WeekDay = DATEPART(WEEKDAY, DATEADD(DAY, 1, G.GeneratedDate))
FROM
GeneratingDates AS G -- Notice that we are referencing a CTE that we are also declaring
WHERE
G.GeneratedDate < #EndDate
)
SELECT
G.GeneratedDate
FROM
GeneratingDates AS G
INNER JOIN #WeekDays AS W ON G.WeekDay = W.WeekDayNumber
OPTION
(MAXRECURSION 30000)
Try this:
declare #start date = '04-11-2018'
declare #end date = '05-11-2018'
declare #P_ID int = 1
declare #USER_ID int = 11
;with cte as(
select #start [date]
union all
select dateadd(DAY, 1, [date]) from cte
where [date] < #end
)
--if MY_TABLE doesn't exist
select #P_ID,
[date],
'NOT SENT',
cast(getdate() as date),
#USER_ID
into MY_TABLE
from cte
--here you can specify days: 1 - Sunday, 2 - Monday, etc.
where DATEPART(dw,[date]) in (2, 5)
option (maxrecursion 0)
--if MY_TABLE does exist
--insert into MY_TABLE
--select #P_ID,
-- [date],
-- 'NOT SENT',
-- cast(getdate() as date),
-- #USER_ID
--from cte
--where DATEPART(dw,[date]) in (2, 5)
--option (maxrecursion 0)

Is that possible to write the below query using CTE recursion?

create table #temp (date date)
declare #X date
set #X = '2016-7-01'
declare #Y date
set #Y = cast (getdate() as date)
while(#X<=#Y)
begin
if (datename(WEEKDAY,#X) = 'Sunday')
insert into #temp values (#X)
set #X = cast(((cast(#X as datetime))+1)as date)
continue
end
select * from #temp
drop table #temp
Is that possible to write the above query using CTE recursion?
You can use a CTE to create a numbers table. You can then use the numbers table to get your dates like so:
Declare #Startdate Datetime = '2016-07-01'
Declare #EndDate Datetime = '2016-08-29'
;with
N0 as (SELECT 1 as n UNION ALL SELECT 1)
,N1 as (SELECT 1 as n FROM N0 t1, N0 t2)
,N2 as (SELECT 1 as n FROM N1 t1, N1 t2)
,N3 as (SELECT 1 as n FROM N2 t1, N2 t2)
,N4 as (SELECT 1 as n FROM N3 t1, N3 t2)
,nums as (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) as num FROM N4)
SELECT DATEADD(day,num-1,#startdate) as thedate
FROM nums
WHERE num <= DATEDIFF(day,#startdate,#enddate) + 1
and datename(WEEKDAY, DATEADD(day,num-1,#startdate)) = 'Sunday'
Each table (N0 to nums) effectively multiplies the number of rows in the previous 'table', so you end up with 65,536 rows of numbers in nums (you can do less or more by adding or removing table NX as required). Then, use the numbers table to add days to your start date(SELECT DATEADD(day,num-1,#startdate) as thedate) , where the dates returned are in your date range, and the weekday is Sunday.
Also, because the numbers in nums start at 1, we use nums-1 in our select, so as to avoid skipping over the first date in our series, effectively giving us DATEADD(day, 0, #startdate) in our first row.
You can try something like explained here: http://blog.sqlauthority.com/2009/12/29/sql-server-get-date-of-all-weekdays-or-weekends-of-the-year/
DECLARE #StartDate DATETIME
DECALRE #EndDate DATETIME
SET #StartDate = '2016-07-01'
SET #EndDate = GETDATE()
;WITH cte AS (
SELECT
1 AS DayID,
#StartDate AS FromDate,
DATENAME(dw, #StartDate) AS Dayname
UNION ALL
SELECT
cte.DayID + 1 AS DayID,
DATEADD(d, 1, cte.FromDate),
DATENAME(dw, DATEADD(d, 1, cte.FromDate)) AS Dayname
FROM cte
WHERE DATEADD(d, 1, cte.FromDate) < #EndDate
)
SELECT FromDate AS Date, Dayname
FROM cte
WHERE Dayname IN ('Sunday')
I'm thinking there's be a more efficient way to do this by using a numbers table and a factor of 7 (depending on how many dates you need to get, possible pre-calc the first sunday from your start date, then join all numbers that are factors of 7 from a numbers table), but the above works well enough also.
;with cte
as
(
select getdate() as datee
union all
select dateadd(day,1,datee)
from cte
where datediff(day,getdate(),datee)<100
)
select * from cte
where datename(WEEKDAY,datee) = 'Sunday'
You will hit max recursion limit as well ,to avoid..that use something like below..
option ( MaxRecursion 0 )
I would solve this using numbers table if i am not constrained by the need to use recursive cte ,which is also way faster than Recursive cte..
select
dateadd(day,n,getdate()) as datee
from numbers
where n<100 and datename(weekday,dateadd(day,n,getdate()))='sunday'
To learn why you need numbers table,check this link..https://dba.stackexchange.com/questions/11506/why-are-numbers-tables-invaluable

T-SQL to select every nth date from column of dates

I have a column of dates. They are all workdays. I would like to generate a list of dates that are 'n' days apart. For example, starting with the most recent date, I want to find the date n days before it, 2n days before it, 3n days before it, etc. I could use a while loop but I wanted to know if I could use SQL set operations instead. Can it be done?
Find the difference between the most_recent_date and dates in your table, then use the modulo function, where n is the interval.
SELECT date
FROM my_table
WHERE mod(most_recent_date - date, n) = 0
This is the perfect case for a CTE:
DECLARE #LastDate datetime;
DECLARE #N int;
DECLARE #NCoefficientMax;
SELECT #N = 1, #NCoefficientMax = 10;
SELECT #LastDate = MyDate
FROM MyTable
ORDER BY MyDate DESC
WITH mycte
AS
(
SELECT DATEADD(dd, #N, #LastDate) AS NextDate, #N AS NCoefficient
UNION ALL
SELECT DATEADD(dd, #N, NextDate), #N + NCoefficient AS NCoefficient
FROM mycte WHERE NCoefficient < #NCoefficientMax
)
SELECT NextDate FROM mycte
Where #NCoefficientMax is the max coefficient for N.
You can use the dateadd funcion
and make select with join to self table.
What that you need to do it -
Insert the result to temporary table,
with additional column then contain the row_number then order like the result
simple example:
declare #t1 table (d datetime, row int)
insert #t1
select d, row_number()over(order by d)
from T1
order by d
select T1A.*, datediff(day,T1A.d,T1B.d) as dif
from #t1 as T1A
left join #t1 as T1B on T1A.row = T1B.row-1
DECLARE #mostRecent datetime2
SELECT #mostRecent = MAX(dateColumn)
FROM table
SELECT columns
FROM table
WHERE (DATEDIFF(day, dateColumn, #mostRecent) % n) = 0

SQL first of every month

Supposing that I wanted to write table valued function in SQL that returns a table with the first day of every month between the argument dates, what is the simplest way to do this?
For example fnFirstOfMonths('10/31/10', '2/17/11') would return a one-column table with 11/1/10, 12/1/10, 1/1/11, and 2/1/11 as the elements.
My first instinct is just to use a while loop and repeatedly insert first days of months until I get to before the start date. It seems like there should be a more elegant way to do this though.
Thanks for any help you can provide.
Something like this would work without being inside a function:
DECLARE #LowerDate DATE
SET #LowerDate = GETDATE()
DECLARE #UpperLimit DATE
SET #UpperLimit = '20111231'
;WITH Firsts AS
(
SELECT
DATEADD(DAY, -1 * DAY(#LowerDate) + 1, #LowerDate) AS 'FirstOfMonth'
UNION ALL
SELECT
DATEADD(MONTH, 1, f.FirstOfMonth) AS 'FirstOfMonth'
FROM
Firsts f
WHERE
DATEADD(MONTH, 1, f.FirstOfMonth) <= #UpperLimit
)
SELECT *
FROM Firsts
It uses a thing called CTE (Common Table Expression) - available in SQL Server 2005 and up and other database systems.
In this case, I start the recursive CTE by determining the first of the month for the #LowerDate date specified, and then I iterate adding one month to the previous first of month, until the upper limit is reached.
Or if you want to package it up in a stored function, you can do so, too:
CREATE FUNCTION dbo.GetFirstOfMonth(#LowerLimit DATE, #UpperLimit DATE)
RETURNS TABLE
AS
RETURN
WITH Firsts AS
(
SELECT
DATEADD(DAY, -1 * DAY(#LowerLimit) + 1, #LowerLimit) AS 'FirstOfMonth'
UNION ALL
SELECT
DATEADD(MONTH, 1, f.FirstOfMonth) AS 'FirstOfMonth'
FROM
Firsts f
WHERE
DATEADD(MONTH, 1, f.FirstOfMonth) <= #UpperLimit
)
SELECT * FROM Firsts
and then call it like this:
SELECT * FROM dbo.GetFirstOfMonth('20100522', '20100831')
to get an output like this:
FirstOfMonth
2010-05-01
2010-06-01
2010-07-01
2010-08-01
PS: by using the DATE datatype - which is present in SQL Server 2008 and newer - I fixed the two "bugs" that Richard commented about. If you're on SQL Server 2005, you'll have to use DATETIME instead - and deal with the fact you're getting a time portion, too.
create function dbo.fnFirstOfMonths(#d1 datetime, #d2 datetime)
returns table as return
select dateadd(m,datediff(m,0,#d1)+v.number,0) as FirstDay
from master..spt_values v
where v.type='P' and v.number between 0 and datediff(m, #d1, #d2)
and dateadd(m,datediff(m,0,#d1)+v.number,0) between #d1 and #d2
GO
Notes
master..spt_values is a source for general purpose sequence numbers in SQL Server
dateadd(m, datediff(m is a technique for working out the first day of month for any date
+v.number is used to increase it by one month each time
0 and datediff(m, #d1, #d2) this condition gives us all the numbers we need to generate a first-of-month date for each month between #d1 and #d2, inclusive of both months
and dateadd(m,datediff(m,0,#d1)+v.number,0) between #d1 and #d2 the final filter to verify that the first-of-month date generated is between #d1 and #d2
Performance comparison against marc_s's code
Summary
8220 ms (CTE)
4173 ms (master..spt_values)
Test
declare #t table (dt datetime)
declare #d datetime
declare #i int
set nocount on
set #d = GETDATE()
set #i = 0
while #i < 10000
begin
insert #t select * from dbo.getfirstofmonth('20090102', '20100506')
delete #t
set #i = #i + 1
end
print datediff(ms, #d, getdate())
set #d = GETDATE()
set #i = 0
while #i < 10000
begin
insert #t select * from dbo.fnfirstofmonths('20090102', '20100506')
delete #t
set #i = #i + 1
end
print datediff(ms, #d, getdate())
Performante
It will loop just between the months involved (4 times in the example):
set dateformat mdy;
declare #date1 smalldatetime,#date2 smalldatetime,#i int
set #date1= '10-31-2010'
set #date2= '02-17-2011'
set #i=1
while(#i<=DATEDIFF(mm,#date1,#date2))
begin
select convert(smalldatetime,CONVERT(varchar(6),DATEADD(mm,#i,#date1),112)+'01',112)
set #i=#i+1
end
I realize this isn't a function, but I'm going to throw this into the mix anyway.
select cal_date from calendar
where day_of_month = 1
and cal_date between '2011-01-01' and '2012-01-01'
This calendar table runs on a PostgreSQL server at work. I'll port it to SQL Server tonight, and run some speed comparisons. (Why? Because this stuff is fun, that's why.)
Just in case anybody is still reading this ...
I cannot imaging that any of the aforementioned functions is faster than this:
declare #DatFirst date = '20101031', #DatLast date = '21110217';
declare #DatFirstOfFirstMonth date = dateadd(day,1-day(#DatFirst),#DatFirst);
select DatFirstOfMonth = dateadd(month,n,#DatFirstOfFirstMonth)
from (
select top (datediff(month,#DatFirstOfFirstMonth,#DatLast)+1)
n=row_number() over (order by (select 1))-1
from (values (1),(1),(1),(1),(1),(1),(1),(1)) a (n)
cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) b (n)
cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) c (n)
cross join (values (1),(1),(1),(1),(1),(1),(1),(1)) d (n)
) x

How I can select / sort dates by period intervals?

For ex:
If we have in table records like:
25/06/2009
28/12/2009
19/02/2010
16/04/2011
20/05/2012
I want to split/select this dates according to 6 month intervals starting from current date.
result should be like:
0-6 month from now: first record
7-12 month from now: second record
...
It will be much apreciated if you make this simple as I made it very stupid and complicated like:
declare variable like t1=curdate()+6
t2=curdate()+12
...
then selected records to fit between curdate() and t1, then t1 and t2 etc.
Thanks,
r.
CORRECTION: Had it backwards, Need to use Modulus, not integer division - sorry...
If MonthCount is a calculated value which counts the number of months since a specific Dec 31, and mod is modulus division (output the remainder after dividing)
Select [Column list here]
From Table
Group By Case When MonthCount Mod 12 < 6
Then 0 Else 1 End
In SQL Server, for example, you could use the DateDiff Function
Select [Column list here]
From Table
Group By Case When DateDiff(month, myDateColumn, curdate) % 12 < 6
Then 0 Else 1 End
( in SQL Server the percent sign is the modulus operator )
This will group all the record into buckets which each contain six months of data
SELECT (DATEDIFF(MONTH, thedate, GETDATE()) / 6) AS semester,
SUM(receipt)
FROM thetable
GROUP BY semester
ORDER BY semester
the key idea is grouping and ordering by the expression that gives you the "semester".
This question really baffled me, cos I couldn't actually come up with a simple solution for it. Damn.
Best I could manage was an absolute bastardization of the following where you create a Temp Table, insert the "Periods" into it, join back to your original table, and group off that.
Assume your content table has the following
ID int
Date DateTime
Counter int
And you're trying to sum all the counter's in six month periods
DECLARE #min_date datetime
select #min_date = min(date) from test
DECLARE #max_date datetime
select #max_date = max(date) from test
DECLARE #today_a datetime
DECLARE #today_b datetime
set #today_a = getdate()
set #today_b = getdate()
CREATE TABLE #temp (startdate DateTime, enddate DateTime)
WHILE #today_a > #min_date
BEGIN
INSERT INTO #temp (startDate, endDate) VALUES (dateadd(month, -6, #today_a), #today_a)
SET #today_a = dateadd(month, -6, #today_a)
END
WHILE #today_b < #max_date
BEGIN
INSERT INTO #temp (startDate, endDate) VALUES (#today_b, dateadd(month, 6, #today_b))
SET #today_b = dateadd(month, 6, #today_b)
END
SELECT * FROM #temp
SELECT
sum(counter),
'Between ' + Convert(nvarchar(10), startdate, 121) + ' => ' + Convert(nvarchar(10), enddate, 121) as Period
FROM test t
JOIN #Temp ht
ON t.Date between ht.startDate AND ht.EndDate
GROUP BY
'Between ' + Convert(nvarchar(10), startdate, 121) + ' => ' + Convert(nvarchar(10), enddate, 121)
DROP TABLE #temp
I really hope someone can come up with a better solution my brain has obviously melted.
Not quite what you're attempting to accomplish, but you could use the DATEDIFF function to distinguish the ranging of each record:
SELECT t.MonthGroup, SUM(t.Counter) AS TotalCount
FROM (
SELECT Counter, (DATEDIFF(m, GETDATE(), Date) / 6) AS MonthGroup
FROM Table
) t
GROUP BY t.MonthGroup
This would create a sub query with an expression that expresses the date ranging group you want. It would then group the sub-query by this date ranging group and you can then do whatever you want with the results.
Edit: I modified the example based on your example.
If you're using SQL Server:
SELECT *,
(
FLOOR
(
(
DATEDIFF(month, GETDATE(), date_column)
- CASE WHEN DAY(GETDATE()) > DAY(date_column) THEN 1 ELSE 0 END
) / 6.0
) * 6
) AS SixMonthlyInterval
FROM your_table
If you're using MySQL:
SELECT *,
(
FLOOR
(
(
((YEAR(date_column) - YEAR(CURDATE())) * 12)
+ MONTH(date_column) - MONTH(CURDATE())
- CASE WHEN DAY(CURDATE()) > DAY(date_column) THEN 1 ELSE 0 END
) / 6.0
) * 6
) AS SixMonthlyInterval
FROM your_table