Easiest way to populate a temp table with dates between and including 2 date parameters - sql

What is the easiest way to populate a temp table with dates including and between 2 date parameters. I only need the 1st day of the month dates.
So for example if #StartDate = '2011-01-01' and #EndDate = '2011-08-01'
Then I want this returned in the table
2011-01-01
2011-02-01
2011-03-01
2011-04-01
2011-05-01
2011-06-01
2011-07-01
2011-08-01

This works even if the #StartDate is not the first of the month. I'm assuming that if it's not the start of the month, you want to begin with the first of the next month. Otherwise remove the +1.:
;WITH cte AS (
SELECT CASE WHEN DATEPART(Day,#StartDate) = 1 THEN #StartDate
ELSE DATEADD(Month,DATEDIFF(Month,0,#StartDate)+1,0) END AS myDate
UNION ALL
SELECT DATEADD(Month,1,myDate)
FROM cte
WHERE DATEADD(Month,1,myDate) <= #EndDate
)
SELECT myDate
FROM cte
OPTION (MAXRECURSION 0)

declare #StartDate date = '2014-01-01';
declare #EndDate date = '2014-05-05';
;WITH cte AS (
SELECT #StartDate AS myDate
UNION ALL
SELECT DATEADD(day,1,myDate) as myDate
FROM cte
WHERE DATEADD(day,1,myDate) <= #EndDate
)
SELECT myDate
FROM cte
OPTION (MAXRECURSION 0)

declare #StartDate datetime
declare #EndDate datetime
select #StartDate = '2011-01-01' , #EndDate = '2011-08-01'
select #StartDate= #StartDate-(DATEPART(DD,#StartDate)-1)
declare #temp table
(
TheDate datetime
)
while (#StartDate<=#EndDate)
begin
insert into #temp
values (#StartDate )
select #StartDate=DATEADD(MM,1,#StartDate)
end
select * from #temp
Works even if the #StartDate is not the first day of the month by going back to the initial day of the month of StartDate

this is tested in SQL 2008 R2
Declare #StartDate datetime = '2015-03-01'
Declare #EndDate datetime = '2015-03-31'
declare #temp Table
(
DayDate datetime
);
WHILE #StartDate <= #EndDate
begin
INSERT INTO #temp (DayDate) VALUES (#StartDate);
SET #StartDate = Dateadd(Day,1, #StartDate);
end ;
select * from #temp
Result:
DayDate
-----------------------
2015-03-01 00:00:00.000
2015-03-02 00:00:00.000
2015-03-03 00:00:00.000
2015-03-04 00:00:00.000
...

Interestingly, it is faster to create from enumerated data as per this article.
DECLARE #StartDate DATE = '10001201';
DECLARE #EndDate DATE = '20000101';
DECLARE #dim TABLE ([date] DATE)
INSERT #dim([date])
SELECT d
FROM
(
SELECT
d = DATEADD(DAY, rn - 1, #StartDate)
FROM
(
SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM
sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
ORDER BY
s1.[object_id]
) AS x
) AS y;
On my machine, it's around 60% faster with large date ranges. The recursion method can populate 2000 years worth of data in around 3 seconds though, and looks a lot nicer, so I don't really recommend this method just for incrementing days.

Correction for null dates:
IF OBJECT_ID('tempdb..#dim') IS NOT NULL
DROP TABLE #dim
CREATE TABLE #dim ([date] DATE)
if not #Begin_Date is null and not #End_Date is null
begin
INSERT #dim([date])
SELECT d
FROM(
SELECT
d = DATEADD(DAY, rn - 1, #Begin_Date)
FROM
(
SELECT TOP (DATEDIFF(DAY, #Begin_Date, #End_Date))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM
sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
ORDER BY
s1.[object_id]
) AS x
) AS y;
end

CREATE TABLE #t (d DATE)
INSERT INTO #t SELECT GETDATE()
GO
INSERT #t SELECT DATEADD(DAY, -1, MIN(d)) FROM #t
GO 10

Related

SQL - Insert all dates within range from table into another

I'm looking to populate a table with dates, based upon values contained within another.
Source : tblA
dtFrom dtTo
2019-01-01 2019-01-03
2019-02-01 2019-02-02
2019-03-01 2019-03-01
Destination : tblB
sDate
2019-01-01
2019-01-02
2019-01-03
2019-02-01
2019-02-02
2019-03-01
SQL Server 2014. As always, thanks in advance :-)
You can use a recursive CTE:
with dates as (
select dtfrom as dt, dtto
from tblA
union all
select dateadd(day, 1, dt), dtto
from dates
where dt < dtto
)
insert tblB (sDate)
select distinct dt
from dates;
The select distinct is only necessary to handle overlapping periods. If you know there are no overlaps, then don't use it.
You can use union to combine values from both columns into one rowset:
insert tblB
(sDate)
select distinct dt
from (
select dtFrom as dt
from tblA
union all
select dtTo
from tblA
) s
Use the always handy Calendar Table, which is a table that holds 1 row for each day, for all days between specific years. You can add additional columns like IsBusinessDay or WorkingStartHour / WorkingEndHour to make your date queries much easier.
-- Create Calendar Table
DECLARE #StartDate DATE = '2000-01-01'
DECLARE #EndDate DATE = '2050-01-01'
SET DATEFIRST 1 -- 1: Monday, 7: Sunday
CREATE TABLE CalendarTable (
Date DATE PRIMARY KEY,
IsWorkingDay BIT
-- Other columns you might need
)
;WITH RecursiveCTE AS
(
SELECT
Date = #StartDate
UNION ALL
SELECT
Date = DATEADD(DAY, 1, R.Date)
FROM
RecursiveCTE AS R
WHERE
DATEADD(DAY, 1, R.Date) <= #EndDate
)
INSERT INTO CalendarTable (
Date,
IsWorkingDay)
SELECT
Date = R.Date,
IsWorkingDay = CASE WHEN DATEPART(WEEKDAY, R.Date) BETWEEN 1 AND 5 THEN 1 ELSE 0 END
FROM
RecursiveCTE AS R
OPTION
(MAXRECURSION 0)
Now with your calendar table, just join with a BETWEEN and INSERT to your destination table. You can use DISTINCT to make sure dates don't repeat:
INSERT INTO tblB (
sDate)
SELECT DISTINCT
sDate = C.Date
FROM
tlbA AS A
INNER JOIN CalendarTable AS C ON C.Date BETWEEN A.dtFrom AND A.dtTo
Let's say for example that you only want to insert records that are working days (monday to friday). You just need to filter the calendar table and done. You can add whichever logic you want on your table and just filter it when using, without repeating complex datetime logics.
INSERT INTO tblB (
sDate)
SELECT DISTINCT
sDate = C.Date
FROM
tlbA AS A
INNER JOIN CalendarTable AS C ON C.Date BETWEEN A.dtFrom AND A.dtTo
WHERE
C.IsWorkingDay = 1
With a Calendar you can inner join on the ranges to produce an Insert statement.
DECLARE #StartDate DATETIME = (SELECT MIN(dtFrom) FROM tblA)
DECLARE #EndDate DATETIME = (SELECT MAX(dtTo) FROM tblB)
;WITH Calendar as
(
SELECT CalendarDate = #StartDate, CalendarYear = DATEPART(YEAR, #StartDate), CalendarMonth = DATEPART(MONTH, #StartDate)
UNION ALL
SELECT CalendarDate = DATEADD(MONTH, 1, CalendarDate), CalendarYear = DATEPART(YEAR, CalendarDate), CalendarMonth = DATEPART(MONTH, CalendarDate)
FROM Calendar WHERE DATEADD (MONTH, 1, CalendarDate) <= #EndDate
)
INSERT INTO tblB
SELECT DISTINCT
C.CalendarDate
FROM
Calendar C
INNER JOIN tblA A ON C.CalendarDate BETWEEN A.dtFrom AND A.dtTo
You can achieve this result by using the below queries.
Steps 1 - Create a Custom Function which will take date range as a parameter and will return date series.
CREATE FUNCTION [dbo].[GenerateDateRange]
(#StartDate AS DATE,
#EndDate AS DATE,
#Interval AS INT
)
RETURNS #Dates TABLE(DateValue DATE)
AS
BEGIN
DECLARE #CUR_DATE DATE
SET #CUR_DATE = #StartDate
WHILE #CUR_DATE <= #EndDate BEGIN
INSERT INTO #Dates VALUES(#CUR_DATE)
SET #CUR_DATE = DATEADD(DAY, #Interval, #CUR_DATE)
END
RETURN;
END;
Step 2 - Join this custom function with your table tblA and insert the record in tblb as needed
insert tblb
select b.* from tblA a cross apply dbo.GenerateDateRange(a.dtFrom, a.dtTo, 1) b

How to get all rows from calender table using a resultset as input

I need to find all dates between 2 dates from a resultset.
My database has a calendar table that holds one row for each date from a few years ago until far enough into the future.
Now I have this query
select convert(date, r.LaadDatum),
convert(date, r.LosDatum)
from tblPlanning p
inner join tblRit r on p.RitID = r.RitID
where r.ChauffeurID = 201
and (convert(date, r.LaadDatum) >= '20180812' and convert(date, r.LaadDatum) <= '20180921')
and datediff(day, r.LaadDatum, r.LosDatum) > 1
and it returns this result set
COLUMN1 COLUMN2
------- -------
2018-08-14 2018-08-16
2018-08-20 2018-08-22
2018-09-01 2018-09-03
2018-09-08 2018-09-10
2018-09-14 2018-09-17
Using this resultset as input, I need the following result :
2018-08-15
2018-08-21
2018-09-02
2018-09-09
2018-09-15
2018-09-16
In other words, all rows from the calendar table that are between the dates from the query above. The calendar table is called tblCalendar.
How can this be done ?
Probably very simple but for some reason i just dont see it
You can try to use cte recursive with DATEADD function.
;WITH CTE AS(
SELECT DATEADD (DAY,1,COLUMN1) COLUMN1,COLUMN2
FROM T
UNION ALL
SELECT DATEADD (DAY,1,COLUMN1) ,COLUMN2
FROM CTE
WHERE DATEADD (DAY,1,COLUMN1)< COLUMN2
)
SELECT COLUMN1
FROM CTE
ORDER BY COLUMN1
sqlfiddle
Have a look at this.
DECLARE #myTable AS TABLE (Column1 DATE, Column2 DATE);
INSERT INTO #myTable (Column1, Column2)
VALUES ('2018-08-14', '2018-08-16')
, ('2018-08-20', '2018-08-22')
, ('2018-09-01', '2018-09-03')
, ('2018-09-08', '2018-09-10')
, ('2018-09-14', '2018-09-17');
WITH cte AS
(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM master..spt_values)
SELECT DATEADD(DAY,rn,Column1) DayToShow
FROM #myTable
CROSS APPLY cte
WHERE DATEADD(DAY,rn,Column1) < column2
You should find it quicker than the iterative cte method, however it is limited to date ranges of about 2,500 days. You can crossaply inside the cte to give more rows if you want.
i would use cursor here. it's like for each.
please try this code. the output is #TempTable which contains all between dates
Declare #TempTable table (BetweenDate datetime)
Declare #FromDate datetime,
#ToDate datetime,
#date datetime
Declare DatesCursor Cursor For
Select FromDate, ToDate From (
select convert(date, r.LaadDatum) FromDate,
convert(date, r.LosDatum) ToDate
from tblPlanning p
inner join tblRit r on p.RitID = r.RitID
where r.ChauffeurID = 201
and (convert(date, r.LaadDatum) >= '20180812'
and convert(date, r.LaadDatum) <= '20180921')
and datediff(day, r.LaadDatum, r.LosDatum) > 1) t
Open DatesCursor
Fetch Next From DatesCursor Into #FromDate, #ToDate
While ##Fetch_Status = 0
Begin
If (#FromDate is not null and #ToDate is not null )
Begin
SET #date = DATEADD(day, 1, #FromDate)
WHILE (#date < #ToDate)
BEGIN
insert #TempTable Values(#date)
SET #date = DATEADD(day, 1, #date)
END
END
Fetch Next From DatesCursor Into #FromDate, #ToDate
End
Close DatesCursor
select * from #TempTable

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)

Converting 1 record with a start and end date into multiple records for each day

I'd like to know an efficient way of taking event records with a start date and end date and basically replicating that record for each day between the start and end date
So a record with a Start Date as 2014-01-01 and End Date as 2014-01-03 would become 3 records, one for each day
I have a date table if that helps. I'm using SQL Server 2012
Thanks
As you already have date table, You can JOIN your table with date table to get all dates to have same record as your start and end date
SELECT A.data,
DT.startDate,
DT.endDate
FROM
DateTable DT
JOIN A
ON A.StartDate >= DT.startDate
And A.EndDate <= DT.endDate
use this query
declare #startDate datetime = getdate()
declare #endDate datetime = dateadd(day,10,getdate())
;with days as
(
select
#startDate as StartDate,
#endDate as EndDate,
#startDate as CurrentDate,
0 as i
union all
select
d.StartDate,
d.EndDate,
dateadd(day,d.i + 1,#startDate) as CurrentDate,
d.i + 1 as i
from days d
where dateadd(day,d.i + 1,#startDate) < d.EndDate
)
select
*
from days d
Try this
DECLARE #dt1 Datetime='2014-01-01'
DECLARE #dt2 Datetime='2014-01-03'
;WITH ctedaterange
AS (SELECT [Dates]=#dt1
UNION ALL
SELECT [dates] + 1
FROM ctedaterange
WHERE [dates] + 1<= #dt2)
SELECT [dates]
FROM ctedaterange
OPTION (maxrecursion 0)
SELECT COl1,COL2, split.a.value('.', 'VARCHAR(100)') data
FROM (SELECT COl1,COL2, Cast ('<M>'
+ Replace(LEFT(Replicate(NAME+',', datediff(dd,startdate,endate), Len(Replicate(NAME+',', datediff(dd,startdate,endate)))-1), ',''</M><M>')
+ '</M>' AS XML) AS Data
FROM tablename) AS A
CROSS apply data.nodes ('/M') AS Split(a)
Query
DECLARE #StartDate DATE = '2014-01-01',
#EndDate DATE = '2014-01-03';
SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.number) - 1, #StartDate) AS Date_Range
FROM master..spt_values a
CROSS APPLY master..spt_values b;
Result
Date_Range
2014-01-01
2014-01-02
2014-01-03

How can I select dates in SQL server to show?

Is there a way for me to show dates using a select statement in sql dates from to To?
Like if I select the date Jan. 15 2013 as from and Jan. 20, 2013 as To the query will show the following:
DATE
2013/01/15 12:00
2013/01/16 12:00
2013/01/16 12:00
2013/01/17 12:00
2013/01/18 12:00
2013/01/19 12:00
2013/01/20 12:00
Is this possible?
A better approach would be to write something as:
DECLARE #from DATE, #to DATE;
SELECT #from = '2013-01-15' , #to = '2013-01-20';
SELECT DATEADD(DAY, n-1, #from)
FROM
(
SELECT TOP (DATEDIFF(DAY, #from, #to)+1) ROW_NUMBER()
OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
) AS x(n);
Check Demo here.
Use BETWEEN...AND:
SELECT DateCol
FROM TableName
WHERE DateCol BETWEEN #FromDate AND #ToDate
ORDER BY DateCol
Next solution. I think more intuitive
declare #table table (d datetime)
declare #start datetime = '2014-01-01'
declare #stop datetime = '2014-01-21'
while #start <= #stop
begin
insert into #table (d)
values (#start)
set #start = DATEADD(day, 1, #start)
end
select * from #table