Find number of days that intersect a given date range in a table of date ranges - sql

I want to find the total number of days in a date range that overlap a table of date ranges.
For example, I have 7 days between 2 dates in the table below. I want to find the days between between them that also fall this date range: 2019-08-01 to 2019-08-30.
It should return 1 day.
This is the data source query:
SELECT LeaveId, UserId, StartDate, EndDate, Days
FROM TblLeaveRequest
WHERE UserId = 218
LeaveID UserID StartDate EndDate Days
----------- ----------- ----------------------- ----------------------- -----------
22484 218 2019-07-26 00:00:00.000 2019-08-01 00:00:00.000 7

I believe this might help you:
--create the table
SELECT
22484 LeaveID,
218 UserID,
CONVERT(DATETIME,'7/26/2019') StartDate,
CONVERT(DATETIME,'8/1/2019') EndDate,
7 Days
INTO #TblLeaveRequest
--Range Paramters
DECLARE #StartRange AS DATETIME = '8/1/2019'
DECLARE #EndRange AS DATETIME = '8/30/2019'
--Find sum of days between StartDate and EndDate that intersect the range paramters
--for UserId=218
SELECT
SUM(
DATEDIFF(
DAY
,CASE WHEN #StartRange < StartDate THEN StartDate ELSE #StartRange END
,DATEADD(DAY, 1, CASE WHEN #EndRange > EndDate THEN EndDate ELSE #EndRange END)
)
) TotalDays
from #TblLeaveRequest
where UserId=218
This assumes that no start dates are greater than end dates. It also assumes that the range parameters always intersect some portion of the range in the table.
If the parameter ranges might not intersect then you'll have to eliminate those cases by excluding negative days:
SELECT SUM( CASE WHEN Days < 0 THEN 0 ELSE Days END ) TotalDays
FROM
(
SELECT
DATEDIFF(
DAY
,CASE WHEN #StartRange < StartDate THEN StartDate ELSE #StartRange END
,DATEADD(DAY, 1, CASE WHEN #EndRange > EndDate THEN EndDate ELSE #EndRange END)
) Days
from #TblLeaveRequest
where UserId=218
) TotalDays

if I understand your problem correctly, you have two ranges of dates and you are looking for the number of days in the intersection.
Considering a Gant chart:
Start_1 .................... End_1
Start_2 .......................End_2
If you can create a table structure like
LeaveID UserID StartDate_1 EndDate_1 StartDate_2 EndDate_2
----------- ----------- ---------- --------- ---------- ---------
22484 218 2019-07-26 2019-08-01 2019-08-01 2019-08-30
You can determine the number of days by
select
leaveID
, UserID
,case
when StartDate_2 <= EndDate_1 then datediff(day,StartDate_2,EndDate_1) + 1
else 0
end as delta_days_intersection
from table
I hope that helps

You can use a numbers (Tally) table to count the number of days:
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE LeaveRequest
(
LeaveId Int,
UserId Int,
StartDate Date,
EndDate Date,
Days Int
)
Insert Into LeaveRequest
VALUES
(22484, 218, '2019-07-26','2019-08-01', 7)
Query 1:
DECLARE #StartDate Date = '2019-08-01'
DECLARE #EndDate Date = '2019-08-30'
;WITH Tally
AS
(
SELECT ROW_NUMBER() OVER (ORdER By Numbers.Num) AS Num
FROM
(
Values(1),(2),(3),(4),(5),(6),(7),(8),(9)
)Numbers(Num)
Cross APPLY
(
Values(1),(2),(3),(4),(5),(6),(7),(8),(9)
)Numbers2(Num2)
)
SELECT COUNT(DATEADD(d, Num -1, StartDate)) As NumberOfDays
FROM LeaveRequest
CROSS APPLY Tally
WHERE DATEADD(d, Num -1, StartDate) <= EndDate AND
DATEADD(d, Num -1, StartDate) >= #StartDate AND
DATEADD(d, Num -1, StartDate) <= #EndDate
Results:
| NumberOfDays |
|--------------|
| 1 |

CREATE TABLE #LeaveRequest
(
LeaveId Int,
UserId Int,
StartDate Date,
EndDate Date,
Days Int
)
Insert Into #LeaveRequest
VALUES
(22484, 218, '2019-07-26','2019-08-01', 7)
Declare #FromDate datetime='2019-08-01'
declare #ToDate datetime='2019-08-30'
;With CTE as
(
select top (DATEDIFF(day,#FromDate,#ToDate)+1)
DATEADD(day, ROW_NUMBER()over(order by (select null))-1,#FromDate) DT
from sys.objects
)
select * from #LeaveRequest LR
cross apply(select count(*)IntersectingDays
from CTE c where dt between lr.StartDate and lr.EndDate)ca
--or
--select lr.*,c.*
from CTE c
--cross apply
(select lr.* from #LeaveRequest LR
where c.DT between lr.StartDate and lr.EndDate)lr
drop table #LeaveRequest
Better create one Calendar table which will always help you in other queries also.
create table CalendarDate(Dates DateTime primary key)
insert into CalendarDate WITH (TABLOCK) (Dates)
select top (1000000)
DATEADD(day, ROW_NUMBER()over(order by (select null))-1,'1970-01-01') DT
from sys.objects
Then inside CTE write this,
select top (DATEDIFF(day,#FromDate,#ToDate)+1) Dates from CalendarDate
where Dates between #FromDate and #ToDate

DECLARE #FromDate datetime = '2019-08-01'
DECLARE #ToDate datetime = '2019-08-30'
SELECT
IIF(#FromDate <= EndDate AND #ToDate >= StartDate,
DATEDIFF(day,
IIF(StartDate > #FromDate, StartDate, #FromDate),
IIF(EndDate < #ToDate, EndDate, #ToDate)
),
0) AS overlapping_days
FROM TblLeaveRequest;

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)

How to get the summation of days in specific month of year in range

If i have Vacation table with the following structure :
emp_num start_date end_date
234 8-2-2015 8-5-2015
234 6-28-2015 7-1-2015
234 8-29-2015 9-2-2015
115 6-7-2015 6-7-2015
115 8-7-2015 8-10-2015
considering date format is: m/dd/yyyy
How could i get the summation of vacations for every employee during specific month .
Say i want to get the vacations in 8Aug-2015
I want the result like this
emp_num sum
234 7
115 4
7 = all days between 8-2-2015 and 8-5-2015 plus all days between 8-29-2015 AND 8-31-2015 the end of the month
i hope this will help you
declare #temp table
(emp_num int, startdate date, enddate date)
insert into #temp values (234,'8-2-2015','8-5-2015')
insert into #temp values (234,'6-28-2015','7-1-2015')
insert into #temp values (234,'8-29-2015','9-2-2015')
insert into #temp values (115,'6-7-2015','6-7-2015')
insert into #temp values (115,'8-7-2015','8-10-2015')
-- i am passing 8 as month number in your case is August
select emp_num,
SUM(
DATEDIFF (DAY , startdate,
case when MONTH(enddate) = 8
then enddate
else DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,startdate)+1,0))--end date of month
end
)+1) AS Vacation from #temp
where (month(startdate) = 8 OR month(enddate) = 8) AND (Year(enddate)=2015 AND Year(enddate)=2015)
group by emp_num
UPDATE after valid comment: This will fail with these dates: 2015-07-01, 2015-09-30 –#t-clausen.dk
i was assumed OP wants for month only which he will pass
declare #temp table
(emp_num int, startdate date, enddate date)
insert into #temp values (234,'8-2-2015','8-5-2015')
insert into #temp values (234,'6-28-2015','7-1-2015')
insert into #temp values (234,'8-29-2015','9-2-2015')
insert into #temp values (115,'6-7-2015','6-7-2015')
insert into #temp values (115,'8-7-2015','8-10-2015')
insert into #temp values (116,'07-01-2015','9-30-2015')
select emp_num,
SUM(
DATEDIFF (DAY , startdate,
case when MONTH(enddate) = 8
then enddate
else DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,startdate)+1,0))
end
)+1) AS Vacation from #temp
where (Year(enddate)=2015 AND Year(enddate)=2015)
AND 8 between MONTH(startdate) AND MONTH(enddate)
group by emp_num
This will work for sqlserver 2012+
DECLARE #t table
(emp_num int, start_date date, end_date date)
INSERT #t values
( 234, '8-2-2015' , '8-5-2015'),
( 234, '6-28-2015', '7-1-2015'),
( 234, '8-29-2015', '9-2-2015'),
( 115, '6-7-2015' , '6-7-2015'),
( 115, '8-7-2015' , '8-10-2015')
DECLARE #date date = '2015-08-01'
SELECT
emp_num,
SUM(DATEDIFF(day,
CASE WHEN #date > start_date THEN #date ELSE start_date END,
CASE WHEN EOMONTH(#date) < end_date
THEN EOMONTH(#date)
ELSE end_date END)+1) [sum]
FROM #t
WHERE
start_date <= EOMONTH(#date)
and end_date >= #date
GROUP BY emp_num
Using a Tally Table:
SQL Fiddle
DECLARE #month INT,
#year INT
SELECT #month = 8, #year = 2015
--SELECT
-- DATEADD(MONTH, #month - 1, DATEADD(YEAR, #year - 1900, 0)) AS start_day,
-- DATEADD(MONTH, #month, DATEADD(YEAR, #year - 1900, 0)) AS end_d
;WITH CteVacation AS(
SELECT
emp_num,
start_date = CONVERT(DATE, start_date, 101),
end_date = CONVERT(DATE, end_date, 101)
FROM vacation
)
,E1(N) AS(
SELECT * FROM(VALUES
(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)
)t(N)
),
E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
Tally(N) AS(
SELECT TOP(SELECT MAX(DATEDIFF(DAY, start_date, end_date)) FROM vacation)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
FROM E4
)
SELECT
v.emp_num,
COUNT(*)
FROM CteVacation v
CROSS JOIN Tally t
WHERE
DATEADD(DAY, t.N - 1, v.start_date) <= v.end_date
AND DATEADD(DAY, t.N - 1, v.start_date) >= DATEADD(MONTH, #month - 1, DATEADD(YEAR, #year - 1900, 0))
AND DATEADD(DAY, t.N - 1, v.start_date) < DATEADD(MONTH, #month, DATEADD(YEAR, #year - 1900, 0))
GROUP BY v.emp_num
First, you want to use the correct data type to ease your calculation. In my solution, I used a CTE to format your data type. Then build a tally table from 1 up to the max duration of the all the vacations. Using that tally table, do a CROSS JOIN on the vacation table to generate all vacation dates from its start_date up to end_date.
After that, add a WHERE clause to filter dates that falls on the passed month-year parameter.
Here, #month and #year is declared as INT. What you want is to get all dates from the first day of the month-year up to its last day. The formula for first day of the month is:
DATEADD(MONTH, #month - 1, DATEADD(YEAR, #year - 1900, 0))
And for the last day of the month, add one month to the above and just use <:
DATEADD(MONTH, #month, DATEADD(YEAR, #year - 1900, 0))
Some common date routines.
More explanation on tally table.
Select(emp_name,start_date,end_date) AS sum_day from table_Name Group by emp_num,start_date,end_date
Try this
with cte(
Select emp_num,DATEDIFF(day,start_date,end_date) AS sum_day from table_Name
Group by emp_num,start_date,end_date
)
Select emp_num,sum(sum_day) as sum_day from cte group by emp_num

Get all dates between two dates in SQL Server

How to get all the dates between two dates?
I have a variable #MAXDATE which is storing the maximum date from the table. Now I want to get the all dates between #Maxdate and GETDATE() and want to store these dates in a cursor.
So far I have done as follows:
;with GetDates As
(
select DATEADD(day,1,#maxDate) as TheDate
UNION ALL
select DATEADD(day,1, TheDate) from GetDates
where TheDate < GETDATE()
)
This is working perfectly but when I am trying to store these values in a cursor
SET #DateCurSor = CURSOR FOR
SELECT TheDate
FROM GetDates
Compilation Error
Incorrect syntax near the keyword 'SET'.
How to solve this?
My first suggestion would be use your calendar table, if you don't have one, then create one. They are very useful. Your query is then as simple as:
DECLARE #MinDate DATE = '20140101',
#MaxDate DATE = '20140106';
SELECT Date
FROM dbo.Calendar
WHERE Date >= #MinDate
AND Date < #MaxDate;
If you don't want to, or can't create a calendar table you can still do this on the fly without a recursive CTE:
DECLARE #MinDate DATE = '20140101',
#MaxDate DATE = '20140106';
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b;
For further reading on this see:
Generate a set or sequence without loops – part 1
Generate a set or sequence without loops – part 2
Generate a set or sequence without loops – part 3
With regard to then using this sequence of dates in a cursor, I would really recommend you find another way. There is usually a set based alternative that will perform much better.
So with your data:
date | it_cd | qty
24-04-14 | i-1 | 10
26-04-14 | i-1 | 20
To get the quantity on 28-04-2014 (which I gather is your requirement), you don't actually need any of the above, you can simply use:
SELECT TOP 1 date, it_cd, qty
FROM T
WHERE it_cd = 'i-1'
AND Date <= '20140428'
ORDER BY Date DESC;
If you don't want it for a particular item:
SELECT date, it_cd, qty
FROM ( SELECT date,
it_cd,
qty,
RowNumber = ROW_NUMBER() OVER(PARTITION BY ic_id
ORDER BY date DESC)
FROM T
WHERE Date <= '20140428'
) T
WHERE RowNumber = 1;
You can use this script to find dates between two dates. Reference taken from this Article:
DECLARE #StartDateTime DATETIME
DECLARE #EndDateTime DATETIME
SET #StartDateTime = '2015-01-01'
SET #EndDateTime = '2015-01-12';
WITH DateRange(DateData) AS
(
SELECT #StartDateTime as Date
UNION ALL
SELECT DATEADD(d,1,DateData)
FROM DateRange
WHERE DateData < #EndDateTime
)
SELECT DateData
FROM DateRange
OPTION (MAXRECURSION 0)
GO
Just saying...here is a more simple approach to this:
declare #sdate date = '2017-06-25'
, #edate date = '2017-07-24';
with dates_CTE (date) as (
select #sdate
Union ALL
select DATEADD(day, 1, date)
from dates_CTE
where date < #edate
)
select *
from dates_CTE;
Easily create a Table Value Function that will return a table with all dates.
Input dates as string
You can customize the date in the the format you like '01/01/2017' or '01-01-2017' in string formats (103,126 ...)
Try this
CREATE FUNCTION [dbo].[DateRange_To_Table] ( #minDate_Str NVARCHAR(30), #maxDate_Str NVARCHAR(30))
RETURNS #Result TABLE(DateString NVARCHAR(30) NOT NULL, DateNameString NVARCHAR(30) NOT NULL)
AS
begin
DECLARE #minDate DATETIME, #maxDate DATETIME
SET #minDate = CONVERT(Datetime, #minDate_Str,103)
SET #maxDate = CONVERT(Datetime, #maxDate_Str,103)
INSERT INTO #Result(DateString, DateNameString )
SELECT CONVERT(NVARCHAR(10),#minDate,103), CONVERT(NVARCHAR(30),DATENAME(dw,#minDate))
WHILE #maxDate > #minDate
BEGIN
SET #minDate = (SELECT DATEADD(dd,1,#minDate))
INSERT INTO #Result(DateString, DateNameString )
SELECT CONVERT(NVARCHAR(10),#minDate,103), CONVERT(NVARCHAR(30),DATENAME(dw,#minDate))
END
return
end
To execute the function do this:
SELECT * FROM dbo.DateRange_To_Table ('01/01/2017','31/01/2017')
The output will be
01/01/2017 Sunday
02/01/2017 Monday
03/01/2017 Tuesday
04/01/2017 Wednesday
05/01/2017 Thursday
06/01/2017 Friday
07/01/2017 Saturday
08/01/2017 Sunday
09/01/2017 Monday
10/01/2017 Tuesday
11/01/2017 Wednesday
12/01/2017 Thursday
13/01/2017 Friday
14/01/2017 Saturday
15/01/2017 Sunday
16/01/2017 Monday
17/01/2017 Tuesday
18/01/2017 Wednesday
19/01/2017 Thursday
20/01/2017 Friday
21/01/2017 Saturday
22/01/2017 Sunday
23/01/2017 Monday
24/01/2017 Tuesday
25/01/2017 Wednesday
26/01/2017 Thursday
27/01/2017 Friday
28/01/2017 Saturday
29/01/2017 Sunday
30/01/2017 Monday
31/01/2017 Tuesday
This can be considered as bit tricky way as in my situation, I can't use a CTE table, so decided to join with sys.all_objects and then created row numbers and added that to start date till it reached the end date.
See the code below where I generated all dates in Jul 2018. Replace hard coded dates with your own variables (tested in SQL Server 2016):
select top (datediff(dd, '2018-06-30', '2018-07-31')) ROW_NUMBER()
over(order by a.name) as SiNo,
Dateadd(dd, ROW_NUMBER() over(order by a.name) , '2018-06-30') as Dt from sys.all_objects a
You can try this:
SET LANGUAGE SPANISH
DECLARE #startDate DATE = GETDATE() -- Your start date
DECLARE #endDate DATE = DATEADD(MONTH, 16, GETDATE()) -- Your end date
DECLARE #years INT = YEAR(#endDate) - YEAR(#startDate)
CREATE TABLE #TMP_YEARS (
[year] INT
)
-- Get all posible years between the start and end date
WHILE #years >= 0
BEGIN
INSERT INTO #TMP_YEARS
([year])
SELECT YEAR(#startDate) + #years
SET #years = #years - 1
END
;WITH [days]([day]) AS -- Posible days at a month
(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL -- days lower than 10
SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15 UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 UNION ALL -- days lower than 20
SELECT 20 UNION ALL SELECT 21 UNION ALL SELECT 22 UNION ALL SELECT 23 UNION ALL SELECT 24 UNION ALL SELECT 25 UNION ALL SELECT 26 UNION ALL SELECT 27 UNION ALL SELECT 28 UNION ALL SELECT 29 UNION ALL -- days lower than 30
SELECT 30 UNION ALL SELECT 31 -- days higher 30
),
[months]([month]) AS -- All months at a year
(
SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
)
SELECT CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, d.[day]))) + CONVERT(VARCHAR, d.[day]) as [date]
FROM #TMP_YEARS a
CROSS JOIN [months] n -- Join all years with all months
INNER JOIN [days] d on DAY(EOMONTH(CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + CONVERT(VARCHAR, DAY(EOMONTH(CAST(CONVERT(VARCHAR, a.[year]) + '-' + CONVERT(varchar, n.[month]) + '-15' AS DATE)))))) >= d.[day] AND -- The number of the day can't be higher than the last day of the current month and the current year
CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, d.[day]))) + CONVERT(VARCHAR, d.[day]) <= ISNULL(#endDate, GETDATE()) AND -- The current date can't be higher than the end date
CONVERT(VARCHAR, a.[year]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, n.[month]))) + CONVERT(VARCHAR, n.[month]) + '-' + REPLICATE('0', 2 - LEN(CONVERT(VARCHAR, d.[day]))) + CONVERT(VARCHAR, d.[day]) >= ISNULL(#startDate, GETDATE()) -- The current date should be higher than the start date
ORDER BY a.[year] ASC, n.[month] ASC, d.[day] ASC
The output will be something like this, you can format the date as you like:
2019-01-24
2019-01-25
2019-01-26
2019-01-27
2019-01-28
2019-01-29
2019-01-30
2019-01-31
2019-02-01
2019-02-02
2019-02-03
2019-02-04
2019-02-05
2019-02-06
2019-02-07
2019-02-08
2019-02-09
...
create procedure [dbo].[p_display_dates](#startdate datetime,#enddate datetime)
as
begin
declare #mxdate datetime
declare #indate datetime
create table #daterange (dater datetime)
insert into #daterange values (#startdate)
set #mxdate = (select MAX(dater) from #daterange)
while #mxdate < #enddate
begin
set #indate = dateadd(day,1,#mxdate)
insert into #daterange values (#indate)
set #mxdate = (select MAX(dater) from #daterange)
end
select * from #daterange
end
I listed dates of 2 Weeks later. You can use variable #period OR function datediff(dd, #date_start, #date_end)
declare #period INT, #date_start datetime, #date_end datetime, #i int;
set #period = 14
set #date_start = convert(date,DATEADD(D, -#period, curent_timestamp))
set #date_end = convert(date,current_timestamp)
set #i = 1
create table #datesList(dts datetime)
insert into #datesList values (#date_start)
while #i <= #period
Begin
insert into #datesList values (dateadd(d,#i,#date_start))
set #i = #i + 1
end
select cast(dts as DATE) from #datesList
Drop Table #datesList
This is the method that I would use.
DECLARE
#DateFrom DATETIME = GETDATE(),
#DateTo DATETIME = DATEADD(HOUR, -1, GETDATE() + 2); -- Add 2 days and minus one hour
-- Dates spaced a day apart
WITH MyDates (MyDate)
AS (
SELECT #DateFrom
UNION ALL
SELECT DATEADD(DAY, 1, MyDate)
FROM MyDates
WHERE MyDate < #DateTo
)
SELECT
MyDates.MyDate
, CONVERT(DATE, MyDates.MyDate) AS [MyDate in DATE format]
FROM
MyDates;
Here is a similar example, but this time the dates are spaced one hour apart to further aid understanding of how the query works:
-- Alternative example with dates spaced an hour apart
WITH MyDates (MyDate)
AS (SELECT #DateFrom
UNION ALL
SELECT DATEADD(HOUR, 1, MyDate)
FROM MyDates
WHERE MyDate < #DateTo
)
SELECT
MyDates.MyDate
FROM
MyDates;
As you can see, the query is fast, accurate and versatile.
You can use SQL Server recursive CTE
DECLARE
#MinDate DATE = '2020-01-01',
#MaxDate DATE = '2020-02-01';
WITH Dates(day) AS
(
SELECT CAST(#MinDate as Date) as day
UNION ALL
SELECT CAST(DATEADD(day, 1, day) as Date) as day
FROM Dates
WHERE CAST(DATEADD(day, 1, day) as Date) < #MaxDate
)
SELECT* FROM dates;
declare #start_dt as date = '1/1/2021'; -- Date from which the calendar table will be created.
declare #end_dt as date = '1/1/2022'; -- Calendar table will be created up to this date (not including).
declare #dates as table (
date_id date primary key,
date_year smallint,
date_month tinyint,
date_day tinyint,
weekday_id tinyint,
weekday_nm varchar(10),
month_nm varchar(10),
day_of_year smallint,
quarter_id tinyint,
first_day_of_month date,
last_day_of_month date,
start_dts datetime,
end_dts datetime
)
while #start_dt < #end_dt
begin
insert into #dates(
date_id, date_year, date_month, date_day,
weekday_id, weekday_nm, month_nm, day_of_year, quarter_id,
first_day_of_month, last_day_of_month,
start_dts, end_dts
)
values(
#start_dt, year(#start_dt), month(#start_dt), day(#start_dt),
datepart(weekday, #start_dt), datename(weekday, #start_dt), datename(month, #start_dt), datepart(dayofyear, #start_dt), datepart(quarter, #start_dt),
dateadd(day,-(day(#start_dt)-1),#start_dt), dateadd(day,-(day(dateadd(month,1,#start_dt))),dateadd(month,1,#start_dt)),
cast(#start_dt as datetime), dateadd(second,-1,cast(dateadd(day, 1, #start_dt) as datetime))
)
set #start_dt = dateadd(day, 1, #start_dt)
end
-- sample of the data
select
top 50 *
--into master.dbo.DimDate
from #dates d
order by date_id
DECLARE #FirstDate DATE = '2018-01-01'
DECLARE #LastDate Date = '2018-12-31'
DECLARE #tbl TABLE(ID INT IDENTITY(1,1) PRIMARY KEY,CurrDate date)
INSERT #tbl VALUES( #FirstDate)
WHILE #FirstDate < #LastDate
BEGIN
SET #FirstDate = DATEADD( day,1, #FirstDate)
INSERT #tbl VALUES( #FirstDate)
END
INSERT #tbl VALUES( #LastDate)
SELECT * FROM #tbl

Get start and end dates of month in between two dates in SQL?

I have the following function which is supposed to return the start and end dates of the months in between two months, the problem how ever is that since this month is 28 days the function is calculating all the upcoming months on a 28 day basis thus returning the following wrong values.
StartDate EndDate
-----------------------
2013-02-01 2013-02-28
2013-03-01 2013-03-28
2013-04-01 2013-04-28
declare #sDate datetime,
#eDate datetime;
select #sDate = '2013-02-25',
#eDate = '2013-04-25';
;with months as
(
select DATEADD(mm,DATEDIFF(mm,0,#sDate),0) StartDate,
DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#sDate)+1,0)) EndDate
union all
select dateadd(mm, 1, StartDate),
dateadd(mm, 1, EndDate)
from months
where dateadd(mm, 1, StartDate)<= #eDate
)
select * from months
how can I modify this to return the right dates?
Try this;
declare #sDate datetime,
#eDate datetime
select #sDate = '2013-02-25',
#eDate = '2013-04-25'
;with cte as (
select convert(date,left(convert(varchar,#sdate,112),6) + '01') startDate,
month(#sdate) n
union all
select dateadd(month,n,convert(date,convert(varchar,year(#sdate)) + '0101')) startDate,
(n+1) n
from cte
where n < month(#sdate) + datediff(month,#sdate,#edate)
)
select startdate, dateadd(day,-1,dateadd(month,1,startdate)) enddate
from cte
FIDDLE DEMO
| STARTDATE | ENDDATE |
---------------------------
| 2013-02-01 | 2013-02-28 |
| 2013-03-01 | 2013-03-31 |
| 2013-04-01 | 2013-04-30 |
If you can get the first day of the month, use dateadd twice to get the last day.
First, add 1 month, then subtract 1 day.
Try This : It's a table function. Return a table of month start and end Date with partial Date Also.
CREATE FUNCTION [dbo].[Fun_GetFirstAndLastDateOfeachMonth] (
#StartDate DATE,
#EndDate DATE
)
RETURNS #Items TABLE (
StartDate DATE ,EndDate DATE,MonthNumber INT,YearNumber INT
)
AS
BEGIN
;WITH cte AS (
SELECT CONVERT(DATE,LEFT(CONVERT(VARCHAR,#StartDate,112),6) + '01') startDate,
MONTH(#StartDate) n
UNION ALL
SELECT DATEADD(MONTH,N,CONVERT(DATE,CONVERT(VARCHAR,YEAR(#StartDate)) + '0101')) startDate,
(n+1) n
FROM cte
WHERE n < MONTH(#StartDate) + DATEDIFF(MONTH,#StartDate,#EndDate)
)
INSERT INTO #Items
SELECT CASE WHEN MONTH(startdate) = MONTH(#StartDate) THEN #StartDate ELSE startdate END AS StartDate,
CASE WHEN MONTH(startdate) = MONTH(#EndDate) THEN #EndDate ELSE DATEADD(DAY,-1,DATEADD(MONTH,1,startdate)) END AS enddate,
MONTH(startDate) AS MonthNumner,YEAR(startDate) AS YearNumber
FROM cte
RETURN
END -- End Function
Execute This After Creating The Function
SELECT * From [EDDSDBO].[Fun_GetFirstAndLastDateOfeachMonth] ('2019-02-25','2019-05-20')
You can use the code below
create FUNCTION [dbo].fn_getfirstandenddate (
#StartDate DATE,
#EndDate DATE
)
RETURNS #Items TABLE (
StartDate DATE ,EndDate DATE
)AS
BEGIN
insert into #ItemsSELECT case when #startdate > DATEADD(DAY,1,EOMONTH((DATEADD(MONTH, x.number, #StartDate)),-1)) then #startdate else DATEADD(DAY,1,EOMONTH((DATEADD(MONTH, x.number, #StartDate)),-1)) end startDay,case when #enddate < eomonth(DATEADD(MONTH, x.number, #StartDate)) then #enddate else eomonth(DATEADD(MONTH, x.number, #StartDate)) end endDate FROM master.dbo.spt_values x WHERE x.type = 'P'AND x.number <= DATEDIFF(MONTH, #StartDate, #EndDate);
RETURN
END
End of Month:
#pDate = EOMONTH(GETDATE())
Starting Date:
#pDate = DATEADD(DAY,-1 * (DAY(GETDATE())-1),GETDATE())

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

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