Query to return +- 30 days from a specific date - sql

I'm trying to figure out how to write a query that will return a table of 61 record that will list a date for each record from the current date.

This is a useful function I use, taken from here:
Explode Dates Between Dates, check and adjust parameter
Just send it Date-30 and Date+30
CREATE FUNCTION [dbo].[ExplodeDates] (#startdate DATETIME, #enddate DATETIME)
RETURNS TABLE
AS
RETURN (
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)
,N5 AS (SELECT 1 AS n FROM N4 t1, N4 t2)
,N6 AS (SELECT 1 AS n FROM N5 t1, N5 t2)
,nums AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS num FROM N6)
SELECT DATEADD(day, num-1, #startdate) AS thedate
FROM nums
WHERE num <= DATEDIFF(day, #startdate, #enddate) + 1
);
GO
If you don't want the function, you can also simply use it as a query, declaring
#startdate = #myDate - 30 and
#enddate = #myDate + 30

The simplest, and probably most efficient way in SQL-Server to get a list of 61 dates is to use the system table Master.dbo.spt_values:
SELECT [Date] = DATEADD(DAY, number - 30, CAST(CURRENT_TIMESTAMP AS DATE))
FROM Master..spt_values
WHERE Type = 'P'
AND Number <= 60;
Example on SQL Fiddle
EDIT
If you are concerned about using undocumented system tables then this will do the same thing (again with no looping)
WITH T AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY Object_ID)
FROM sys.all_objects
)
SELECT [Date] = DATEADD(DAY, number - 30, CAST(CURRENT_TIMESTAMP AS DATE))
FROM T
WHERE Number <= 60;
Example on SQL Fiddle
Extensive testing has been done here on the merits of various methods of generating sequences of numbers. My preferred option would always be your own table (e.g. dbo.numbers, or in this case a calendar table).

Try this
;with DateList As
(
select GETDATE() as DateCol
union all
select datecol + 1 from datelist
where DateDiff(d, getdate(),datecol+1) < 31 and DateCol + 1 > GETDATE()
union all
select datecol - 1 from datelist
where DateDiff(d, datecol-1, getdate()) < 31 and DateCol - 1 < GETDATE()
)
select CONVERT(varchar(15), DateCol, 101) DateCol from DateList
order by 1
OPTION (MAXRECURSION 0)
If you want to join other table
declare #t table (code varchar(10));
insert into #t
values ('a'), ('b')
;with DateList As
(
select GETDATE() as DateCol
union all
select datecol + 1 from datelist
where DateDiff(d, getdate(),datecol+1) < 31 and DateCol + 1 > GETDATE()
union all
select datecol - 1 from datelist
where DateDiff(d, datecol-1, getdate()) < 31 and DateCol - 1 < GETDATE()
)
select * from DateList, #t
OPTION (MAXRECURSION 0)

In my opinion, the best way to approach this is not to use recursive ctes, temp tables, or system tables, but rather to create and reuse a date lookup table. Create the lookup table once, and then you can use it as needed.
From there, it's really easy to generate a list of dates:
select *
from datelookup
where datefull >= dateadd(day,-30,convert(varchar(10), getDate(), 120))
and datefull <= dateadd(day,30,convert(varchar(10), getDate(), 120));
SQL Fiddle Demo (includes sample code to create such a table)

This T-SQL code will generate your table:
DECLARE #dates TABLE (date_item DATE)
DECLARE #day DATE = DATEADD(DAY, -30, N'2013-05-02')
WHILE #day <= DATEADD(DAY, 30, N'2013-05-02')
BEGIN
INSERT INTO #dates (date_item) SELECT #day
SET #day = DATEADD(DAY, 1, #day)
END
The result is in #dates. Obviously you will need to set the desired value for the center date in place of N'2013-05-02'

Related

SQL Server - Breakdown date period

I want to create a query that breakdowns a date period into 10 days sub-periods
So a period of 2022-04-15 to 2022-05-01 should be broken into
2022-04-15 2022-04-24
2022-04-25 2022-05-01
The period could be one day (2022-04-15 to 2022-04-15) or even years
Any help appreciated
Thank you
A Tally would be a much more performant approach:
DECLARE #Start date = '20220415',
#End date = '20220501',
#Days int = 10;
WITH N AS (
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT 0 AS I
UNION ALL
SELECT TOP (DATEDIFF(DAY,#Start,#End)/#Days)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM N N1, N N2, N N3, N N4) --Up to 1,000 rows. Add more cross joins for more rows
SELECT DATEADD(DAY, T.I*#Days,#Start),
CASE WHEN DATEADD(DAY, ((T.I+1)*#Days)-1,#Start) > #End THEN #END ELSE DATEADD(DAY, ((T.I+1)*#Days)-1,#Start) END
FROM Tally T;
You can use a recursive cte. A rough outline is as follows:
create table #test (
id int identity primary key,
date1 date,
date2 date
);
insert into #test (date1, date2) values
('2022-04-15', '2022-05-01'),
('2022-04-15', '2022-04-15');
with rcte as (
select id, date1 as date1_, dateadd(day, 10, date1) as date2_, date2 as enddate
from #test
union all
select id, date2_, dateadd(day, 10, date2_), enddate
from rcte
where date2_ <= enddate
)
select id, date1_, dateadd(day, -1, date2_)
from rcte
order by 1, 2
DB<>Fiddle
Does this help?
declare #fromdate date=cast('2022-04-15' as date);
declare #todate date=cast('2022-05-01' as date);
WITH cte_dates(tendays)
AS (
SELECT
#fromdate
UNION ALL
SELECT
case when dateadd(d,10,tendays) > #todate then #todate else dateadd(d,10,tendays) end
FROM
cte_dates
WHERE tendays < dateadd(d,-9,#todate)
)
SELECT
tendays,case when dateadd(d,9,tendays) > #todate then #todate else dateadd(d,9,tendays) end
FROM
cte_dates;
DB<>Fiddle

Adding dates in a range

I need to add dates from 01-01-2011 to 31-12-2014 in format: dd-mm-yyyy, how can I do this? I mean something like this:
SET #Date = '01/01/2011';
WHILE #Date <'31/12/2014'
BEGIN
INSERT INTO Calendar(DataKal) VALUES (#Date);
SET #Date = #Date + 1;
END
I am using SQL Server 2014.
If what you are saying is you want to INSERT a row for each date between 2 dates then the best, and by far fastest, method to do this is with a Tally:
DECLARE #StartDate date = '20110101',
#EndDate date = '20141230'; --Seems odd you want to omit 31 December 2014
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 AS I
FROM N N1, N N2, N N3, N N4) --10,000 rows
INSERT INTO dbo.Calendar (DataKal)
SELECT DATEADD(DAY, T.I, #StartDate)
FROM Tally T;
You can use a recursive CTE to define the dates and load them:
with cte as (
select convert(date, '2011-01-01') as dte
union all
select dateadd(day, 1, dte)
from cte
where dte < '2014-12-31'
)
insert into calendar (datakal)
select dte
from cte
option (maxrecursion 0);
Note that this inserts the date using the internal format. If you want to see it in a particular format -- such as dd-mm-yyyy -- then you can add a second column. I would suggest adding the column as a computed column:
alter table calendar add dd_mm_yyyy as (convert(varchar(10), datakal, 105));
Here is a db<>fiddle.

How to get all dates between current month and the two last months

I am trying to get all dates existing between the current month and the two last months.
For example: today 10-01-2019
With an sql script, I will get all dates between 2018-10-01 and 2019-01-31.
with cte as
(
select getdate() as n
union all
select dateadd(DAY,-1,n) from cte where month(dateadd(dd,-1,n)) < month(DATEADD(month, -3, getdate())) --and month(DATEADD(month, 0, getdate()))
union all
select dateadd(DAY,-1,n) from cte where month(dateadd(dd,-1,n)) > month(DATEADD(month, 0, getdate()))
)
select * from cte
I get
error Msg 530, Level 16, State 1, Line 1
The statement terminated. The maximum recursion 100 has been exhausted before statement completion.
Recursion is not a good approach to this. Performance wise using a recursive cte to increment a counter is the same thing as a cursor. http://www.sqlservercentral.com/articles/T-SQL/74118/
A much better approach is to do this set based. For this task a tally table is ideal. Here is a great article on the topic.
I keep a tally table as a view in my system. It is lightning fast with zero reads.
create View [dbo].[cteTally] as
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), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
GO
Then for something like this problem it is super simple to use. This will produce the same results with no looping.
declare #endDate datetime = '2019-01-31'
, #tmpDate datetime = '2018-10-01'
select dateadd(day, t.N - 1, #tmpDate)
from cteTally t
where t.N - 1 <= DATEDIFF(day, #tmpDate, #endDate)
--EDIT--
If you need this to be dynamic you can use a little date math. This will get the data from the beginning of 3 months ago through the end of the current month regardless of when you run this. The date logic might be a little tough to decipher if you haven't seen this kind of thing before. Lynn Pettis has a great article on this topic. http://www.sqlservercentral.com/blogs/lynnpettis/2009/03/25/some-common-date-routines/
select dateadd(day, t.N - 1, dateadd(month, -3, dateadd(month, datediff(month, 0, getdate()), 0)))
from cteTally t
where t.N - 1 < datediff(day,dateadd(month, -3, dateadd(month, datediff(month, 0, getdate()), 0)), dateadd(month, datediff(month, 0, getdate()) + 1, 0))
This will work depending on your version of SQL Server.
with cte as
(
select cast(getdate() as date) as n
union all
select dateadd(DAY,-1,n) from cte where dateadd(DAY,-1,n) > (select eomonth(cast(dateadd(month,-4,getdate()) as date)))
)
select *
from cte
order by n desc
option (maxrecursion 200)
with cte as
(
select dateadd(month,1,dateadd(day, -1* day(getdate()) , cast(getdate() as date) ) ) n
union all
select dateadd(day,-1,n) from cte where month(n) + year(n) * 12 >= month(getdate()) + year(getdate()) * 12 -3
),
final as (select * from cte except select top 1 * from cte order by n)
select * from final order by n
OPTION (MAXRECURSION 1000)
or to use dateadd only and avoid the except
with cte as
(
select dateadd(day,-1,dateadd(month,1,dateadd(day, 1 - day(getdate()) , cast(getdate() as date)))) n
union all
select dateadd(day,-1,n) from cte where n > dateadd(month,-3,dateadd(day , 1 - day(getdate()),cast(getdate() as date)))
)
select * from cte order by n
OPTION (MAXRECURSION 1000)
You're hitting the maxrecursion limit. Increase it as an option:
with cte as
(
select getdate() as n
union all
select dateadd(DAY,-1,n) from cte where month(dateadd(dd,-1,n)) < month(DATEADD(month, -3, getdate())) --and month(DATEADD(month, 0, getdate()))
union all
select dateadd(DAY,-1,n) from cte where month(dateadd(dd,-1,n)) > month(DATEADD(month, 0, getdate()))
)
select * from cte
OPTION (MAXRECURSION 1000)
You can use a temp table for this purpose. With a loop, just add the dates you need to the temp table. Check the query below:
create table #temp (thedate date)
declare #i int = 1
declare #tmpDate datetime = dateadd(month,-2,getdate())
while #tmpDate<=getdate()
begin
insert into #temp
values (#tmpDate)
set #tmpDate = dateadd(day,1,#tmpDate)
end
select * from #temp
EDIT: Based on OP's comment, new query:
create table #temp (thedate date)
declare #i int = 1
declare #endDate datetime = '2019-01-31'
declare #tmpDate datetime = '2018-10-01'
while #tmpDate<=#endDate
begin
insert into #temp
values (#tmpDate)
set #tmpDate = dateadd(day,1,#tmpDate)
end
select * from #temp
If you're using SQL 2012+
SELECT
dateadd(dd, number, (dateadd(dd, 1, dateadd(MM, -4, eomonth(getdate()))))) as TheDate
FROM
master..spt_values m1
WHERE
type = 'P'
AND dateadd(dd, number, (dateadd(dd, 1, dateadd(MM, -4, eomonth(getdate())))) ) <= eomonth(getdate())
And for earlier versions of SQL:
SELECT
cast(dateadd(dd, number, dateadd(MM, -3, dateadd(dd, -day(getdate())+1, getdate()))) as date)
FROM
master..spt_values m1
WHERE
type = 'P'
AND dateadd(dd, number, dateadd(MM, -3, dateadd(dd, -day(getdate())+1, getdate()))) <= dateadd(MM, 1, dateadd(dd, -day(getdate()) , getdate()))

Generate Dates recursively in SQL Server

I have some dates I want to calculate which is currently done over several subqueries. Each subsequent subquery uses the result (a date) of the previous query in its calculation. E.g.
DECLARE #Date DATE = '20170101'
SELECT #foo1 = (SELECT TOP 1 dbo.DateFunction(DateField)
FROM [DateTable]
WHERE DateField <= #Date
ORDER BY DateField DESC)
SELECT #foo2 = (SELECT TOP 1 dbo.DateFunction(DateField)
FROM [DateTable]
WHERE DateField <= #foo1
ORDER BY DateField DESC)
....
SELECT #fooN = (SELECT TOP 1 dbo.DateFunction(DateField)
FROM [DateTable]
WHERE DateField <= #fooNMinus1
ORDER BY DateField DESC)
Is it possible (perhaps using CTE) to make a recursive query to achieve this for a specified number of times?
Weeks are almost always 7 days, so you can get the first one and then just add seven days. If so:
WITH dates as (
SELECT MAX(dbo.DateFunction(DateField)) as dte, 1 as counter
FROM [DateTable]
WHERE DateField <= #Date
UNION ALL
SELECT DATEADD(DAY, 7, dte), counter + 1
FROM dates
WHERE counter < #n
)
SELECT dte
FROM dates;
You can use small tally table as below
Declare #d1 date = '2017-01-01'
Declare #d2 date = '2017-12-31'
select top (datediff(day, #d1, #d2)+1) dt = DateAdd(day, Row_Number() over (order by (Select NULL))-1, #d1)
from master..spt_values s1, master..spt_values s2
Or custom tally tables
;with num as
( select * from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) v(n) )
, n1 as (select n1.* from num n1, num n2, num n3, num n4) --numbers generation
select top (datediff(day, #d1, #d2)+1) dt = DateAdd(day, Row_Number() over (order by (Select NULL))-1, #d1)
from n1
Yes, you can use a recursive query. Since top and aggregates are not allowed in the recursive part, you can use the row_number() function instead.
Declare #date date = cast(getdate() as date), #n int = 10
declare #DateTable table (DateField date)
insert into #DateTable values ('2017-05-01'),('2017-05-02'),('2017-05-03'),('2017-05-04'),('2017-05-05'),('2017-05-06'),('2017-05-07'),('2017-05-08'),('2017-05-09'),('2017-05-10'),
('2017-05-11'),('2017-05-12'),('2017-05-13'),('2017-05-14'),('2017-05-15'),('2017-05-16'),('2017-05-17'),('2017-05-18'),('2017-05-19'),('2017-05-20')
;with date_rte as (
select top 1 dbo.DateFunction(DateField) datefield, 0 recursions, cast(1 as bigint) rn
from #dateTable
where datefield <= #date
order by datefield desc
union all
select dbo.DateFunction(DateField), recursions+1, ROW_NUMBER() over (order by d.datefield desc)
from #datetable d
join date_rte r on d.DateField <= r.datefield
where recursions < #n and rn = 1
)
select datefield
from date_rte
where rn=1 and recursions = #n

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