SQL date range breaking into months - sql-server

I have a sql table that has the following
ID StartDate EndDate
10 2015-12-01 2016-05-31
15 2016-01-05 2016-07-04
20 2016-02-10 2016-08-09
I need to break down the months like so...
ID StartDate EndDate
10 2015-12-01 2015-12-31
10 2016-01-01 2016-01-31
10 2016-02-01 2016-02-29
10 2016-03-01 2016-03-31
10 2016-04-01 2016-04-30
10 2016-05-01 2016-05-31
15 2016-01-05 2016-02-04
15 2016-02-05 2016-03-04
15 2016-03-05 2016-04-04
15 2016-04-05 2016-05-04
15 2016-05-05 2016-06-04
15 2016-06-05 2016-07-04
etc
I'm new to SQL so an example would be very helpful

Calendar
recommended if you have persistent Calendar/DateRanges table
declare #datebegin date = '20140101'
;with cteCalendar as
(
select
c.period_start,
dateadd(dd, -1, dateadd(mm, 1, c.period_start)) as period_end
from
(
select top 100
dateadd(mm, row_number() over(order by sc.object_id)-1, #datebegin) as period_start
from sys.columns sc
order by period_start
) c
),
cteData as
(
select cast(10 as int) as id, cast('20151201' as date) as StartDate, cast('20160531' as date) as EndDate
union all
select 15, '20160105', '20160704'
union all
select 25, '20160210', '20160809'
),
cteDataEx as
(
select d.id, d.StartDate, d.EndDate, datepart(dd, d.StartDate)-1 as DateOffset
from cteData d
)
select
d.id,
dateadd(dd, d.DateOffset, c.period_start) as StartDate,
dateadd(dd, d.DateOffset, c.period_end) as EndDate
from cteDataEx d
inner join cteCalendar c on c.period_start <= d.EndDate and c.period_end >= d.StartDate
where dateadd(dd, d.DateOffset, c.period_end) <= d.EndDate
order by id, StartDate
Actually I did not notice at the beginning that periods may start and end not at 1st day of month, so had to append some calculations after completion of the whole script. Later I realized that <= >= date filter produces unnecessary last row which overflows original date range high bound. So had to append final filter and after that modification don't like this approach totally )) May be some enhancements can be applied but I'm not interested in. Lots of ways to accomplish this task. Additional information about nature and purpose of periods given may alter relevance and applicability of different approaches
Recursion
no extra data required but recursion can be slow if date ranges can be wide enough.
;with cteData as
(
select cast(10 as int) as id, cast('20151201' as date) as StartDate, cast('20160531' as date) as EndDate
union all
select 15, '20160105', '20160704'
union all
select 25, '20160210', '20160809'
),
ctePeriods as
(
select
d.id,
d.StartDate,
dateadd(dd, -1, dateadd(mm, 1, d.StartDate)) as EndDate,
d.EndDate as _EndDate
from cteData d
union all
select
p.id,
dateadd(mm, 1, p.StartDate),
dateadd(dd, -1, dateadd(mm, 2, p.StartDate)),
p._EndDate
from ctePeriods p
where p.EndDate < p._EndDate
)
select p.id, p.StartDate, p.EndDate
from ctePeriods p
order by id, startdate

this code generate the rage of months, inclute leap year, but I don't undestand your need so explain better
create table #dia_meses
(mes int,
messtr varchar(2),
dia_final varchar(2))
insert into #dia_meses values(1,'01','31')
insert into #dia_meses values(2,'02','29')
insert into #dia_meses values(3,'03','31')
insert into #dia_meses values(4,'04','30')
insert into #dia_meses values(5,'05','31')
insert into #dia_meses values(6,'06','30')
insert into #dia_meses values(7,'07','31')
insert into #dia_meses values(8,'08','31')
insert into #dia_meses values(9,'09','30')
insert into #dia_meses values(10,'10','31')
insert into #dia_meses values(11,'11','30')
insert into #dia_meses values(12,'12','31')
declare #year varchar(4)
declare #contador int
set #year =convert(varchar,DATEPART(YEAR,GETDATE()))
set #contador =convert(varchar,DATEPART(month,GETDATE()))
declare #dataIni datetime
declare #datafim datetime
set #dataIni=(select #year+'-'+messtr+'-01' from #dia_meses where mes=#contador)
--pulo do gato ano bissexto
if(#contador=2)
begin
if(select ISDATE(#year+'-'+messtr+'-'+dia_final) from #dia_meses where mes=#contador)=0
begin
set #datafim=(select #year+'-'+messtr+'-28' from #dia_meses where mes=#contador)
end
else--ano bissexto
begin
set #datafim=(select #year+'-'+messtr+'-'+dia_final from #dia_meses where mes=#contador)
end
end
else
begin
set #datafim=(select #year+'-'+messtr+'-'+dia_final from #dia_meses where mes=#contador)
end
print #dataIni
print #dataFim

This will work on SQL Server 2012 and up; the EOMONTH function does not exist on earlier versions.
DECLARE #table TABLE (ID INT, StartDate DATE, EndDate DATE)
DECLARE #outtable TABLE (ID INT, StartDate DATE, EndDate DATE)
DECLARE #ID INT
DECLARE #StartDate DATE
DECLARE #Date1 DATE
DECLARE #Date2 DATE
DECLARE #EndDate DATE
INSERT INTO #table VALUES
(10,'2015-12-01','2016-05-31')
,(15,'2016-01-05','2016-07-04')
,(20,'2016-02-10','2016-08-09')
DECLARE tablecursor CURSOR FOR
SELECT * FROM #table
OPEN tablecursor
FETCH NEXT FROM tablecursor INTO #ID, #StartDate, #EndDate
WHILE ##FETCH_STATUS = 0
BEGIN
SET #Date1 = #StartDate
SET #Date2 = EOMONTH(#Date1)
WHILE #Date1 < #EndDate
BEGIN
PRINT CONVERT(VARCHAR,#ID) + ' ' + CONVERT(VARCHAR,#Date1) + ' ' + CONVERT(VARCHAR,#Date2)
INSERT INTO #outtable
SELECT #ID, #Date1, #Date2
SET #Date1 = DATEADD(DAY,1,#Date2)
SET #Date2 = EOMONTH(#Date1)
IF #Date2 > #EndDate
BEGIN
SET #Date2 = #EndDate
END
END
FETCH NEXT FROM tablecursor INTO #ID, #StartDate, #EndDate
END
SELECT * FROM #outtable
CLOSE tablecursor
DEALLOCATE tablecursor

Related

SQL - Split Row into multiple based on month

I have data in a row as below in SQL (2012) table
ID Value StartDate EndDate
123 5000 14/04/2017 15/12/2017
I would like to Split the row into each individual month as below:
ID Value StartDate EndDate
123 5000 14/04/2017 30/04/2017
123 5000 01/05/2017 31/05/2017
123 5000 01/06/2017 30/06/2017
123 5000 01/07/2017 31/07/2017
123 5000 01/08/2017 30/08/2017
123 5000 01/09/2017 31/09/2017
123 5000 01/10/2017 31/10/2017
123 5000 01/11/2017 30/11/2017
123 5000 01/12/2017 15/12/2017
Appreciate help in this matter.
You can do it like this:
WITH tally
AS (SELECT TOP (1000)
ROW_NUMBER() OVER (ORDER BY t1.object_id) AS N
FROM master.sys.all_columns t1
CROSS JOIN master.sys.all_columns t2),
rowset
AS (SELECT ID,
Value,
DATEADD(m, N - 1, StartDate) AS StartDate,
Enddate,
N
FROM tally, MyDates
WHERE DATEFROMPARTS(YEAR(DATEADD(m, N - 1, StartDate)),
MONTH(DATEADD(m, N - 1, StartDate)), 1) <= EndDate)
SELECT Id,
Value,
CASE
WHEN N = 1 THEN
StartDate
ELSE
DATEFROMPARTS(YEAR(StartDate), MONTH(StartDate), 1)
END AS StartDate,
CASE
WHEN DATEADD(d, -DAY(DATEADD(m, 1, StartDate)), DATEADD(m, 1, StartDate)) <= EndDate THEN
DATEADD(d, -DAY(DATEADD(m, 1, StartDate)), DATEADD(m, 1, StartDate))
ELSE
Enddate
END AS EndDate
FROM rowset
ORDER BY ID, StartDate;
Use below query to get the result
DECLARE #Temp TABLE
(
ID INT
,Value NUMERIC(10,2)
,StartDate DATE
,EndDate DATE
)
DECLARE #NewTable TABLE
(
id INT IDENTITY(1,1)
,item INT
,Value NUMERIC(10,2)
,StartDate DATE
,EndDate DATE
)
INSERT INTO #Temp
SELECT 123,5000 ,'2017-04-14','2018-12-15'
DECLARE #SDate date
DECLARE #EDate date
declare #LastDay INT =0
SELECT top 1 #SDate=StartDate,#EDate=EndDate FROM #Temp
declare #Year int = 0
declare #Month int = 0
declare #Index INT =1
DECLARE #Lenght int =DATEDIFF(MONTH,#SDate,#EDate) +1
WHILE #Index<=#Lenght
BEGIN
IF #Index=1 BEGIN
-- first record
SELECT #Year= YEAR(#SDate)
SELECT #Month = MONTH(#SDate)
-- get last day of the date
SELECT #LastDay = day(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#SDate)+1,0)))
SELECT #EDate= CONVERT(VARCHAR(10),#Year)+'-'+CONVERT(VARCHAR(10),#Month)+'-
'+CONVERT(VARCHAR(10),#LastDay)
INSERT INTO #NewTable
SELECT 123,5000,#SDate, #EDate
END ELSE IF #Index = #Lenght BEGIN
SELECT #Month = #Month+1
IF #Month>12 BEGIN
SELECT #Month =1
SELECT #Year =#Year +1
END ELSE BEGIN
SELECT #Year= YEAR(#EDate)
END
SELECT #SDate =CONVERT(VARCHAR(10),#Year)+'-'+CONVERT(VARCHAR(10),#Month)+ '-01'
INSERT INTO #NewTable
SELECT 123,5000 ,#SDate,#EDate
END ELSE BEGIN
SELECT #Month = #Month+1
if #Month>12 BEGIN
SELECT #Month =1
SELECT #Year =#Year +1
END
SELECT #SDate =CONVERT(VARCHAR(10),#Year)+'-'+CONVERT(VARCHAR(10),#Month)+ '-01'
SELECT #LastDay = day(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#SDate)+1,0)))
SELECT #EDate= CONVERT(VARCHAR(10),#Year)+'-'+CONVERT(VARCHAR(10),#Month)+'-'+CONVERT(VARCHAR(10),#LastDay)
INSERT INTO #NewTable
SELECT 123,5000 ,#SDate,#EDate
END
SELECT #Index=#Index+1
end
SELECT * from #NewTable
--select day(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,getdate())+1,0)))

Retrieve date from year, week number, day name in SQL Server 2012

Thanks for all for reading my questions, I have fallen a big problem to retrieve date from SQL Server 2012 by providing year, week number and day name.
Suppose I have
Year = 2016
Week number = 1
Day Name ='FRI'
First day of week='SUN'
Expected result:
01-01-2016
How can I do that?
EDIT: I have found similar solution from here but I have no month name.
My suggestion is based on the solution to the question in the link you provided.
Basically, I've created a calendar that holds the dates since January 1st of the year till #x weeks after that, and then queried that calendar:
-- provided data:
DECLARE #Year int = 2016,
#WeekNumber int = 1,
#DayName char(3) = 'Fri';
-- Calculate start date and end date
DECLARE #StartDate date,
#EndDate date;
SELECT #StartDate = CAST('01-01-'+ CAST(#Year as char(4)) as date),
#EndDate = DATEADD(WEEK, #WeekNumber, #StartDate)
-- Create the calendar
;WITH CTE AS
(
SELECT #StartDate as TheDate
UNION ALL
SELECT DATEADD(DAY, 1, TheDate)
FROM CTE
WHERE DATEADD(DAY, 1, TheDate) <= #EndDate
)
-- Finally, query the calendar:
SELECT TheDate
FROM CTE
WHERE DATEPART(WEEK, TheDate) = #WeekNumber
AND YEAR(TheDate) = #Year
AND DATENAME(WEEKDAY, TheDate) LIKE #DayName + '%'
OPTION(MAXRECURSION 0)
results:
TheDate
----------
2016-01-01
Note: This solution will return no rows if the day you specify is mon, since the first week of 2016 starts on Friday.
TRY THIS
DECLARE #Year varchar(4)
DECLARE #WeekDayday varchar(10)
DECLARE #WeekNumber int
SET #Year ='2016'
SET #WeekDayday ='fri'
SET #WeekNumber =1
--used to solve
DECLARE #StartDate datetime
,#EndDate datetime
,#FirstWeek int
SET #StartDate='01-01-'+' '+#Year
SET #EndDate=#StartDate+38
SET #FirstWeek=DATENAME(week,#StartDate)-1
;with AllDates AS
(
SELECT #StartDate AS DateOf, DATENAME(week,#StartDate)-#FirstWeek AS WeekOf, DATENAME(weekday,#StartDate) AS WeekDayOf
UNION ALL
SELECT DateOf+1, DATENAME(week,DateOf+1)-#FirstWeek AS WeekOf, DATENAME(weekday,DateOf+1) AS WeekDayOf
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT
DateOf
FROM AllDates
WHERE WeekOf=#WeekNumber AND WeekDayOf LIKE #WeekDayday+'%'
ORDER BY DateOf
How about improve that answer by evaluate month name by week number:
--given info
DECLARE #Year varchar(4);
DECLARE #MonthName varchar(10);
DECLARE #WeekDayday varchar(10);
DECLARE #WeekNumber int;
SET #Year = '2016';
SET #WeekDayday = 'Tue';
set #WeekNumber = 46;
-- get month number by week
declare #w int = 0;
declare #m int = 1;
while (#w <= #WeekNumber and #m < 13) begin
set #w = datepart(wk, datefromparts(#year, #m, 1));
if (#w <= #WeekNumber and #m < 13) begin
set #m = #m + 1;
end;
end;
set #m = #m -1;
-- get month name
set #MonthName = left(DateName(month ,DateAdd(month ,#m ,0 ) - 1), 3);
--used to solve
DECLARE #StartDate datetime
,#EndDate datetime
,#FirstWeek int
SET #StartDate='01 '+#MonthName+' '+#Year
SET #EndDate=#StartDate+38
SET #FirstWeek=DATENAME(week,#StartDate)-1
;with AllDates AS
(
SELECT #StartDate AS DateOf, DATENAME(week,#StartDate)-#FirstWeek AS WeekOf, DATENAME(weekday,#StartDate) AS WeekDayOf
UNION ALL
SELECT DateOf+1, DATENAME(week,DateOf+1)-#FirstWeek AS WeekOf, DATENAME(weekday,DateOf+1) AS WeekDayOf
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT
DateOf ,WeekOf ,WeekDayOf
FROM AllDates
WHERE datepart(wk, DateOf) = #WeekNumber AND WeekDayOf LIKE #WeekDayday+'%'
ORDER BY DateOf

How can I list all dates between two date parameters in SQL

How can I list all dates between two date parameters in SQL Server, without creating a stored procedure, calendar table or recursive function?
There's always the recursive CTE option:
DECLARE #STARTDATE DATETIME
DECLARE #ENDDATE DATETIME
SET #STARTDATE = '2015-01-01'
SET #ENDDATE = '2015-12-31'
;WITH DATE_RANGE (DATES) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, #STARTDATE), 0)
UNION ALL SELECT DATEADD(DAY, 1, DATES)
FROM DATE_RANGE
WHERE DATEADD(DAY, 1, DATES) <= #ENDDATE)
SELECT DATES
FROM DATE_RANGE
OPTION (MAXRECURSION 0)
Be sure to use the MAXRECURSION option, or your results will be limited to 100 as default.
This uses Row_Number on the spt_values table in Master database to create a list of years, months and dates within the date range.
This is then built into a datetime field, and filtered to only return dates within the date parameters entered.
Very quick to execute and returns 500 years worth of dates (182987 days) in less than 1 second.
Declare #DateFrom datetime = '2000-01-01'
declare #DateTo datetime = '2500-12-31'
Select
*
From
(Select
CAST(CAST(years.Year AS varchar) + '-' + CAST(Months.Month AS varchar) + '-' + CAST(Days.Day AS varchar) AS DATETIME) as Date
From
(select row_number() over(order by number) as Year from master.dbo.spt_values) as Years
join (select row_number() over(order by number) as Month from master.dbo.spt_values) as Months on 1 = 1
join (select row_number() over(order by number) as Day from master.dbo.spt_values) as Days on 1 = 1
Where
Years.Year between datepart(year,#DateFrom) and datepart(year,#DateTo)
and Months.Month between 1 and 12
and
Days.Day between 1 and datepart(day,dateadd(day,-1,dateadd(month,1,(CAST(CAST(Years.Year AS varchar)+'-' + CAST(Months.Month AS varchar) + '-01' AS DATETIME)))))
) as Dates
Where Dates.Date between #DateFrom and #DateTo
order by 1
Following will be a solution for YOU
DECLARE #DATE1 DATE
DECLARE #DATE2 DATE
SET #DATE1 ='20020101'
SET #DATE2 = '20020311'
SELECT #DATE1 as t
into #FromDate
DECLARE cur CURSOR FOR
SELECT t FROM #FromDate
OPEN cur
FETCH NEXT FROM cur INTO #DATE1
WHILE(##FETCH_STATUS=0)
BEGIN
IF(#DATE1<=#DATE2)
INSERT INTO #FromDate
VALUES(DATEADD(DAY,1,#DATE1))
FETCH NEXT FROM cur INTO #DATE1
END
CLOSE cur
DEALLOCATE cur;
SELECT t FROM #FromDate;
DROP TABLE #FromDate;
Simple result.
DECLARE #DATE1 DATE
DECLARE #DATE2 DATE
SET #DATE1 ='20020101'
SET #DATE2 = '20020311'
WHILE(#DATE1<=#DATE2)
Begin
PRINT #DATE1
set #DATE1 = DATEADD(dd,1,#DATE1)
END

Find Mondays between 2 dates

I need to display dates of all Mondays in the given date range.
For example, if my start date is 01/05/2015 and end date is 31/05/2015, I need to show
04/05/2015
11/05/2015
18/05/2015
25/05/2015
How is it possible?
This procedure is independent from regions and languages.
Please note the first line with SET DATEFIRST 1.
SET DATEFIRST 1; -- First day of the week is set to monday
DECLARE #DateFrom DateTime ='20150601', #DateTo DateTime = '20150630' ;
WITH CTE(dt)
AS
(
SELECT #DateFrom
UNION ALL
SELECT DATEADD(d, 1, dt) FROM CTE
WHERE dt < #DateTo
)
SELECT dt FROM CTE where datepart ("dw", dt) = 1;
Using a CTE it is possible this way..
DECLARE #DateFrom DateTime ='2015-05-01',
#DateTo DateTime = '2015-05-31'
;WITH CTE(dt)
AS
(
SELECT #DateFrom
UNION ALL
SELECT DATEADD(d, 1, dt) FROM CTE
WHERE dt < #DateTo
)
SELECT 'Monday', dt FROM CTE
WHERE DATENAME(dw, dt) In ('Monday')
Refer: Select dates of a day between two dates.
SELECT [Day],[Dt] FROM dbo.fnGetDatesforAday('7/1/2008','8/31/2008','Sunday')
CREATE FUNCTION fnGetDatesforAday
(
-- Add the parameters for the function here
#DtFrom DATETIME,
#DtTo DATETIME,
#DayName VARCHAR(12)
)
RETURNS #DateList TABLE ([Day] varchar(20),Dt datetime)
AS
BEGIN
IF NOT (#DayName = 'Monday' OR #DayName = 'Sunday' OR #DayName = 'Tuesday' OR #DayName = 'Wednesday' OR #DayName = 'Thursday' OR #DayName = 'Friday' OR #DayName = 'Saturday')
BEGIN
--Error Insert the error message and return
INSERT INTO #DateList
SELECT 'Invalid Day',NULL AS DAT
RETURN
END
DECLARE #TotDays INT
DECLARE #CNT INT
SET #TotDays = DATEDIFF(DD,#DTFROM,#DTTO)-- [NO OF DAYS between two dates]
SET #CNT = 0
WHILE #TotDays >= #CNT -- repeat for all days
BEGIN
-- Pick each single day and check for the day needed
IF DATENAME(DW, (#DTTO - #CNT)) = #DAYNAME
BEGIN
INSERT INTO #DateList
SELECT #DAYNAME,(#DTTO - #CNT) AS DAT
END
SET #CNT = #CNT + 1
END
RETURN
END
SET DATEFIRST 7; -- Set's sunday as first day of week, won't work otherwise
DECLARE #StartDate DATE = '06/01/2015'
DECLARE #EndDate DATETIME = '06/30/2015'
DECLARE #TableOfDates TABLE(DateValue DATETIME)
DECLARE #CurrentDate DATETIME
SET #CurrentDate = #startDate
WHILE #CurrentDate <= #endDate
BEGIN
INSERT INTO #TableOfDates(DateValue) VALUES (#CurrentDate)
SET #CurrentDate = DATEADD(DAY, 1, #CurrentDate)
END
SELECT * FROM #TableOfDates WHERE DATEPART(weekday,Datevalue) = 2

How to add current year's all dates to SQL server?

Is it possible to add all the dates between 1st Jan-31-Dec in MS Sql server using query?
If someone has done it before please guide me to right track.
Thanks
DECLARE #dt Date
SET #dt = '2015-01-01'
WHILE #dt < '2016-01-01'
BEGIN
SELECT #dt
--INSERT .....
SET #dt = DATEADD(DAY, 1, #dt)
END
Of course it depends on your table structure
using loop we can achieve this
Declare #date table(d datetime)
Declare #d datetime, #d1 datetime, #d2 datetime
Declare #inc INT
set #d1='20150101'
set #d2='20151231'
Set #inc = DATEDIFF(D, #d1, #d2)
Set #d = #d1
While #d<=#d2
Begin
Insert into #date values (#d)
set #d=#d+1
End
Select d as DateCol from #date
This should do the trick:
DECLARE #year int = 2015
;WITH N(N)AS
(SELECT 1 FROM(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))M(N)),
tally(N)AS(SELECT ROW_NUMBER()OVER(ORDER BY N.N)-1 FROM N,N a,N b,N c,N d,N e,N f)
SELECT top (datediff(d, cast(#year as char(4)), cast(#year + 1 as char(4))))
CAST(DATEADD(d, N, cast(#year as char(4))) as date)
FROM tally
Result:
2015-01-01
2015-01-02
..
..
2015-12-31

Resources