Adding dates in a range - sql

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.

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

Create View in SQL Server to create multiple rows for each date,with calculated date and identifier column

I need to create a VIEW/Select statement that will take a start date, and create 3 different rows for each date. One row calculates 30 days, from the start date, another 60 days, and another 90 days. Also each row needs to have an identifier that states whether the date is 30 days, 60 days or 90 days from the start date. So say that the start date is 09/01/2020. Then the View will return this for each start date:
Row Header : Start Date, AdditionalDate, AdditionalDays
Row 1 : 01/01/2020, 02/01/2020, 30
Row 2 : 01/02/2020, 03/01/2020, 60
Row 3 : 01/01/2020, 04/01/2020, 90
Sorry, forgot to mention, but start date is from a table. Like (Select startDate from Appointment)
I am using Microsoft SQL Server and a new SQL user. Really appreciate any help and advice.
Thank you!
I am unsure why what do you expect from a view for that - views don't take parameters.
Here is, however, a query that, from a given date parameter, generates three rows, at 30, 60 and 90 days later:
declare #start_date date = '2020-01-01';
select
#start_date,
dateadd(day, additional_days, #start_date) additional_date,
additional_days
from (values (30), (60), (90)) x(additional_days)
I am unsure whether you really mean 30 days or a month. If you want months, then:
declare #start_date date = '2020-01-01';
select
#start_date,
dateadd(month, additional_months, #start_date) additional_date,
additional_months
from (values (1), (2), (3)) x(additional_months)
On the other hand, if you are starting from an existing table, then that's a cross join:
select
t.*,
dateadd(day, x.additional_days, t.start_date) additional_date,
x.additional_days
from mytable t
cross join (values (30), (60), (90)) x(additional_days
You cannot use a view for this purpose, but you can use an inline table-valued function:
create function dates (
#date date,
#period int,
#num int
)
returns table
as return
with dates as (
select #date as start_date,
dateadd(day, #period, #date) as additional_date,
#period as additional_days, 1 as n
union all
select start_date,
dateadd(day, #period, additional_date),
additional_days + #period, n + 1
from dates
where n < #num
)
select start_date, additional_date, additional_days
from dates;
Here is a db<>fiddle.
You can utilize a recursive cte:
with cte as
( Select 1 as Header
,Start
,dateadd(day, 30, Start) as AdditionalDate
,30 as AdditionalDays
from Appointment
union all
select Header+1
,Start
,dateadd(day, 30, AdditionalDate)
,AdditionalDays + 30
from cte
where Header <= 2
)
Select * from cte
Or for adding months instead of days:
with cte as
( Select 1 as Header
,Start
,dateadd(month, 1, Start) as AdditionalDate
,datediff(day, Start, dateadd(month, 1, Start)) as AdditionalDays
from Appointment
union all
select Header+1
,Start
,dateadd(month, 1, AdditionalDate)
,datediff(day, Start, dateadd(month, 1, AdditionalDate))
from cte
where Header <= 2
)
Select * from cte
See fiddle

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

Simplest way to fill working day table

I have a table working_days with one column date of type date
I need to fill it with working days in USA.
Can you suggest how can I so this?
Manually it is too long.
You can use a recursive CTE to accomplish this. This only excludes the weekends. Using DATEFIRST you can figure out what day is a weekend. This query should work no matter what day of the week is set to DATEFIRST.
;WITH DatesCTE
AS (
SELECT CAST('2016-01-01' AS DATE) AS [workingDays]
UNION ALL
SELECT DATEADD(DAY, 1, workingdays)
FROM DatesCTE
WHERE DATEADD(DAY, 1, workingdays) < '2017-01-01'
)
SELECT *
FROM DatesCTE
WHERE ((DATEPART(dw, workingDays) + ##DATEFIRST) % 7) NOT IN (0, 1)
OPTION (MAXRECURSION 366)
At first fill your table with all dates for year (for example 2016):
DECLARE #date_start date = '2016-01-01',
#date_end date = '2016-12-31';
WITH cte as (
SELECT #date_start as [d], 0 as Level
UNION ALL
SELECT DATEADD(day,1,[d]), [level] + 1 as [level]
from cte
WHERE [level] < DATEDIFF(day,#date_start,#date_end)
),
holidays as ( --table with holidays
SELECT * FROM (VALUES
('2016-01-01'),
('2016-01-18'),
('2016-02-15'),
('2016-05-30'),
('2016-07-04'),
('2016-09-05'),
('2016-10-10'),
('2016-11-11'),
('2016-11-24'),
('2016-12-26')) as t(d)
)
SELECT c.d
FROM cte c
LEFT JOIN holidays h on c.d=h.d
WHERE DATEPART(WEEKDAY,d) NOT IN (1,7) --will show only monday-friday
AND AND h.d is NULL
OPTION (MAXRECURSION 1000); --if you need more than 3 years get MAXRECURSION up
A simple loop will do:
declare #d date = '20160101';
while #d <= '20161231'
begin
if datepart(weekday, #d) not in (1, 7) and <#d not a holiday>
insert into working_days ("date") values (#d);
set #d = dateadd(day, 1, #d);
end

Query to return +- 30 days from a specific date

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'