Split date range to multiple rows using SQL - sql-server

I have a table:
startdate enddate other_columns
1956-05-06 00:00:00.000 1960-04-05 00:00:00.000 myvalues
I need a query that will return the results as:
startdate enddate other_columns
1956-05-06 00:00:00.000 1956-12-31 00:00:00.000 myvalues
1957-01-01 00:00:00.000 1957-12-31 00:00:00.000 myvalues
1958-01-01 00:00:00.000 1958-12-31 00:00:00.000 myvalues
1959-01-01 00:00:00.000 1959-12-31 00:00:00.000 myvalues
1960-01-01 00:00:00.000 1960-04-05 00:00:00.000 myvalues
Basically a query that will explode the rows into yearly results. I need the start and end dates to be retained.

CREATE TABLE #InputTABLE
(
startdate DATETIME,
enddate DATETIME,
other_columns varchar(20)
)
INSERT INTO #InputTABLE VALUES('1956-05-06','1960-04-05','myvalues');
SELECT * FROM #InputTABLE
Output:
startdate enddate other_columns
1956-05-06 00:00:00.000 1960-04-05 00:00:00.000 myvalues
Query:
CREATE TABLE #OutputTABLE
(
startdate DATETIME,
enddate DATETIME,
other_columns varchar(20)
)
DECLARE #cnt int
DECLARE #startDate datetime
DECLARE #endDate datetime
DECLARE #incr int
DECLARE #tempDate datetime
SET #startDate=(Select startdate from #InputTABLE)
SET #endDate=(Select enddate from #InputTABLE)
SET #cnt=DATEDIFF(yy,#startDate,#endDate)
SET #incr=0
SET #tempDate=DATEADD(yy,#incr,Cast(#startDate As datetime))
WHILE #cnt>=0
BEGIN
IF #cnt = 0
BEGIN
INSERT INTO #OutputTABLE VALUES(#tempDate,#endDate,'myvalues');
END
ELSE
BEGIN
insert into #OutputTABLE values(#tempDate,DATEADD(yy, DATEDIFF(yy,0,#tempDate)+1, -1),'myvalues');
END
SET #tempDate=DATEADD(yy,#incr+1,DATEADD(yy,DATEDIFF(yy,0,#startDate),0))
SET #cnt=#cnt-1
SET #incr=#incr+1
END
Result : SELECT * FROM #OutputTABLE;
startdate enddate other_columns
1956-05-06 00:00:00.000 1956-12-31 00:00:00.000 myvalues
1957-01-01 00:00:00.000 1957-12-31 00:00:00.000 myvalues
1958-01-01 00:00:00.000 1958-12-31 00:00:00.000 myvalues
1959-01-01 00:00:00.000 1959-12-31 00:00:00.000 myvalues
1960-01-01 00:00:00.000 1960-04-05 00:00:00.000 myvalues

The query is quite simple
SELECT CASE WHEN yrStart<it.startdate THEN it.startdate ELSE yrStart END AS startdate,
CASE WHEN yrEnd>it.enddate THEN it.enddate ELSE yrEnd END AS enddate,
other_columns
FROM #InputTABLE it
CROSS APPLY
(SELECT datefromparts(yr, 1, 1) yrStart, datefromparts(yr, 12, 31) yrEnd
FROM dbo.yearTable
WHERE yr >= datepart(year, it.startdate) AND yr <= datepart(year, it.enddate)
)years;
all you need is a yearTable with numbers 1..9999 (aka Tally table). You should not have numbers outside of this range in this table, otherwise you would meet some nasty conversion errors.

Related

SQL Server - Split a date range by a given date

I have a table that stores an Id and an effective period indicating when it is active
PortfolioId StartDate EndDate
1 2018-01-01 00:00:00.000 2018-05-31 00:00:00.000
2 2017-01-01 00:00:00.000 2018-05-31 00:00:00.000
I have another table that stores a component related to the above Id and that too has an effective period. Table 2 can have more that on entry for any given entry in table 1.
PortfolioComponentId PortfolioId SplitDate
1 1 2018-02-28 00:00:00.000
2 1 2018-03-31 00:00:00.000
3 2 2017-03-31 00:00:00.000
4 2 2017-09-20 00:00:00.000
5 2 2018-01-15 00:00:00.000
I have a period where I am running the query for
i.e
StartDate : 30-JUN-2017
End date : 15-MAY-2018
I have looking for a result like the below, where data in table 1 is split based on the data from table 2
PortfolioId StartDate EndDate
1 2018-01-01 00:00:00.000 2018-02-28 00:00:00.000
1 2018-03-01 00:00:00.000 2018-03-31 00:00:00.000 - Starts from End date + 1 from the prev row
1 2018-04-01 00:00:00.000 2018-05-15 00:00:00.000
2 2017-06-30 00:00:00.000 2017-09-20 00:00:00.000 - Starts from Seach date [Portfolio component Id 3 ignored as it falls outside of search date range]
2 2017-09-21 00:00:00.000 2018-01-15 00:00:00.000
2 2018-01-16 00:00:00.000 2018-05-15 00:00:00.000 - Ends by seach end date
Data setup - In case it helps
DECLARE #SearchStartDate DATETIME = '30-JUN-2017'
DECLARE #SearchEndDate DATETIME = '15-MAY-2018'
DECLARE #Portfolio TABLE
(
PortfolioId INT PRIMARY KEY IDENTITY(1,1),
StartDate DATETIME,
EndDate DATETIME
)
INSERT INTO #Portfolio
SELECT '01-JAN-2018', '31-MAY-2018'
INSERT INTO #Portfolio
SELECT '01-JAN-2017', '31-MAY-2018'
DECLARE #PortfolioComponents TABLE
(
PortfolioComponentId INT PRIMARY KEY IDENTITY(1,1),
PortfolioId INT,
SplitDate DATETIME
)
INSERT INTO #PortfolioComponents
SELECT 1, '28-FEB-2018'
INSERT INTO #PortfolioComponents
SELECT 1, '31-MAR-2018'
INSERT INTO #PortfolioComponents
SELECT 2, '31-MAR-2017'
INSERT INTO #PortfolioComponents
SELECT 2, '20-SEP-2017'
INSERT INTO #PortfolioComponents
SELECT 2, '15-JAN-2018'
SELECT * from #Portfolio
SELECT * from #PortfolioComponents
I believe I have got a result close to what I want, though not
the most ideal approach
the best approach from a performance perspective
DECLARE #Temp TABLE (BenchmarkDate Datetime, ComponentType int, PortfolioId INT)
INSERT INTO #Temp
SELECT StartDate , 1, PortfolioId FROM #Portfolio
UNION
SELECT EndDate , 2, PortfolioId FROM #Portfolio
INSERT INTO #Temp
SELECT SplitDate , 2, PortfolioId FROM #PortfolioComponents
UNION
SELECT SplitDate + 1 , 1, PortfolioId FROM #PortfolioComponents
DECLARE #Results TABLE
(
Id INT IDENTITY(1,1),
StartDate DATETIME,
EndDate DATETIME,
PortfolioId INT
)
INSERT INTO #Results
SELECT rset1.BenchmarkDate [Startdate],
( SELECT MIN(rset2.BenchmarkDate)
FROM #Temp rset2
WHERE rset2.ComponentType = 2
AND rset2.BenchmarkDate > rset1.BenchmarkDate
AND rset1.PortfolioId = rset2.PortfolioId) [Enddate],
rset1.PortfolioId
FROM #Temp rset1
WHERE rset1.ComponentType = 1
ORDER BY rset1.PortfolioId, rset1.BenchmarkDate
SELECT
CASE WHEN (#SearchStartDate BETWEEN StartDate AND EndDate ) THEN #SearchStartDate ELSE StartDate END StartDate,
CASE WHEN (#SearchEndDate BETWEEN StartDate AND EndDate) THEN #SearchEndDate ELSE EndDate END EndDate,
PortfolioId ,
(
SELECT PortfolioComponentId
FROM #PortfolioComponents pc
WHERE
(pc.PortfolioID = r.PortfolioId AND
(DATEADD(d, 1, pc.SplitDate) = r.StartDate ))
) PortfolioComponentId
FROM #Results r
WHERE
(#SearchStartDate < StartDate AND #SearchStartDate < EndDate AND #SearchEndDate > EndDate)
OR
(#SearchEndDate BETWEEN StartDate AND EndDate)
OR
(#SearchStartDate BETWEEN StartDate AND EndDate )

How to calculate Saturday in between two dates in SQL Server

Week ends on Saturday and new week starts on Sunday.
I would like to determine date which will be a Saturday and split dates in SQL Server. Example below:
Start_date - 09/11/2018 - Friday
End_date - 12/11/2018 - Monday
Total number of days = 4
I would like split the days and end results as
Start_date - 09/11/2018 - Friday
Date1 - 10/11/2018 - Saturday
Total number of days = 2
Date2 - 11/11/2018 - Sunday
End_date - 12/11/2018 - Monday
Total number of days = 2
Another example ( start_date and end_date if there are more number of Saturdays e.g. )
start-date - 04/05/2017
end_date - 31/05/2017
Then results should be like below :-
Date1 Date2 no. of days.
------------------------------------
04/05/2017 06/05/2017 3
07/05/2017 13/05/2017 7
14/05/2017 20/05/2017 7
21/05/2017 27/05/2017 7
28/05/2017 31/05/2017 4
Please help.
Thanks & Regards,
VG
If I am not mistaken you are looking for something like this:
DECLARE #StartDate DATE = '2017-05-04'
DECLARE #EndDate DATE = '2017-05-31'
DECLARE #OutputTABLE AS TABLE
(
StartDate DATE NOT NULL,
EndDate DATE NOT NULL
)
DECLARE #NumberOfWeeks INT = DATEDIFF(week, #StartDate, #EndDate)
DECLARE #Counter INT = 0
DECLARE #TempDate DATE = DATEADD(week, #Counter, #StartDate)
WHILE #NumberOfWeeks >= 0
BEGIN
IF #NumberOfWeeks = 0
BEGIN
INSERT INTO #OutputTABLE VALUES (#TempDate, #EndDate);
END
ELSE
BEGIN
INSERT INTO #OutputTABLE VALUES (#TempDate, DATEADD(DAY, -1, DATEADD(week, DATEDIFF(week ,0 , #TempDate) + 1, -1)));
END
SET #TempDate = DATEADD(week, #Counter + 1, DATEADD(day, -1, DATEADD(week, DATEDIFF(week, 0, #StartDate), 0)))
SET #NumberOfWeeks = #NumberOfWeeks - 1
SET #Counter = #Counter + 1
END
SELECT StartDate,
EndDate,
DATEDIFF(day, StartDate, EndDate) + 1 AS NumberOfDays
FROM #OutputTABLE
Here is one way. I couldn't figure out a way with a recursive CTE since you can't aggregate in the recursive part of a recursive CTE.
DECLARE #MinDate DATE = '20170504',
#MaxDate DATE = '20170531'
DECLARE #StartDate datetime, #EndDate datetime
--DATE TABLE... ONE ROW FOR EVERY DAY IN RANGE
IF OBJECT_ID('tempdb..#DateTable') IS NOT NULL DROP TABLE #DateTable
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Dates = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
INTO #DateTable
FROM sys.all_objects a
CROSS JOIN sys.all_objects b
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results
CREATE TABLE #Results (Date1 DATETIME, Date2 DATETIME, NumOfDays INT)
--INSERT FIRST ROW IN CASE IT STARTS ON A DAY OTHER THAN SUNDAY
INSERT INTO #Results
SELECT
MIN(Dates) as Date1
,MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 7 THEN Dates END) as Date2
,DATEDIFF(DAY,MIN(Dates),MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 7 THEN Dates END)) + 1 as NoOfDays
FROM #DateTable
SET #StartDate = (SELECT MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 1 THEN Dates END) FROM #DateTable)
SET #EndDate = (SELECT MAX(CASE WHEN DATEPART(WEEKDAY,Dates) = 1 THEN Dates END) FROM #DateTable)
--INSERT ALL FULL WEEKS
WHILE #StartDate < #EndDate
BEGIN
INSERT INTO #Results
SELECT
MIN(Dates) as Date1
,MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 7 THEN Dates END) as Date2
,DATEDIFF(DAY,MIN(Dates),MIN(CASE WHEN DATEPART(WEEKDAY,Dates) = 7 THEN Dates END)) + 1 as NoOfDays
FROM #DateTable
WHERE Dates >= #StartDate
SET #StartDate = DATEADD(DAY,7,#StartDate)
END
--INSERT LAST ROW IF IT ISN'T A FULL WEEK
IF (SELECT MAX(Date2) FROM #Results) <> #MaxDate
BEGIN
INSERT INTO #Results
SELECT
MIN(Dates) as Date1
,MAX(Dates) as Date2
,DATEDIFF(DAY,MIN(Dates),MAX(Dates)) + 1 as NoOfDays
FROM #DateTable
WHERE Dates > (SELECT MAX(Date2) FROM #Results)
END
SELECT * FROM #Results
DROP TABLE #Results
DROP TABLE #DateTable
RETURNS
+-------------------------+-------------------------+-----------+
| Date1 | Date2 | NumOfDays |
+-------------------------+-------------------------+-----------+
| 2017-05-04 00:00:00.000 | 2017-05-06 00:00:00.000 | 3 |
| 2017-05-07 00:00:00.000 | 2017-05-13 00:00:00.000 | 7 |
| 2017-05-14 00:00:00.000 | 2017-05-20 00:00:00.000 | 7 |
| 2017-05-21 00:00:00.000 | 2017-05-27 00:00:00.000 | 7 |
| 2017-05-28 00:00:00.000 | 2017-05-31 00:00:00.000 | 4 |
+-------------------------+-------------------------+-----------+
This will compute your Date2 from your Date1
print dateadd(day,-1,dateadd(wk,datepart(wk,'4/5/2017'),'1/1/2017'))

How to find missing dates by ID

I need help modifying a script to find the missing RevenuePeriod by ID within a given time frame. I have found a similar script that finds the missing date for a given ID but I don't know how to modify the script where it will give the missing dates per ID.
create table #WorksheetHistory (WorksheetID [int] IDENTITY(1,1) ,ID varchar(6), RevenuePeriod datetime)
insert into #WorksheetHistory (ID,RevenuePeriod)
SELECT '000001','2015-06-01 00:00:00.00' Union All
SELECT '000001','2015-07-01 00:00:00.00' Union All
SELECT '000001','2015-11-01 00:00:00.00' Union All
SELECT '000001','2015-12-01 00:00:00.00' Union All
SELECT '000002','2015-06-01 00:00:00.00' Union All
SELECT '000002','2015-12-01 00:00:00.00'
DECLARE #EndDate datetime
DECLARE #StartDate datetime
SET #StartDate = '2015-06-01 00:00:00.00'
SET #EndDate = '2015-12-01 00:00:00.00'
;WITH Dates as
(
SELECT #StartDate AS dt
UNION ALL
SELECT DATEADD(month, 1, dt) as dt
FROM Dates
WHERE dt < (select dateadd(month,-1,#EndDate) enddate)
)
select Month(dt) as dtMonth, Year(dt) dtYear
from Dates d left outer join #WorksheetHistory w
on dateadd(month, datediff(month,0,d.dt),0) = dateadd(month, datediff(month,0,w.RevenuePeriod),0) Where RevenuePeriod is null
Drop Table #WorksheetHistory
The current output returns the following output. I realize in the script it doesnt have an ID returned, but even if I do, it would return null value as the script indicates return null dates. I don't know how to put the associated ID in it too.
dt
2015-08-01 00:00:00.000
2015-09-01 00:00:00.000
2015-10-01 00:00:00.000
My desired result would be return the Missing ID's with the respective missing dates.
ID dt
000001 2015-08-01 00:00:00.00
000001 2015-09-01 00:00:00.00
000001 2015-10-01 00:00:00.00
000002 2015-07-01 00:00:00.00
000002 2015-08-01 00:00:00.00
000002 2015-09-01 00:00:00.00
000002 2015-10-01 00:00:00.00
000002 2015-11-01 00:00:00.00
Use EXCEPT:
WITH Dates as
(
SELECT #StartDate AS dt
UNION ALL
SELECT DATEADD(month, 1, dt) as dt
FROM Dates
WHERE dt < DATEADD(m, -1, #EndDate)
)
-- all the possible combinations
SELECT w.ID, d.dt
FROM Dates d
CROSS JOIN (
SELECT ID
FROM #WorksheetHistory
GROUP BY ID
) w
EXCEPT
-- the combinations you actually have
SELECT w.ID, d.dt
FROM Dates d
JOIN #WorksheetHistory w
ON d.dt = w.RevenuePeriod;

How do i display the end date from StartDate in sql server?

I am using SQL SERVER 2008. I have a table called Cut-Off table. This table only shows the StartDate of cut-off for every month. So i am creating a temp table that will store StartDate, EndDate, Month and Year. But i don't know how to get the EndDate with datetime.
TABLE
----------------------------------------------------------
StartDate EndDate Month Year
----------------------------------------------------------
2014-12-15 00:00:00 NULL 12 2014
2015-01-26 00:00:00 NULL 1 2015
2015-02-26 00:00:00 NULL 2 2015
---------------------------------------------------------
EXPECTED OUTPUT
--------------------------------------------------------------------
StartDate EndDate Month Year
------------------------------------------------------------------
2014-12-15 00:00:00 2015-01-25 23:59:99 12 2014
2015-01-26 00:00:00 2015-02-25 23:59:99 1 2015
2015-02-26 00:00:00 2015-03-25 23:59:99 2 2015
------------------------------------------------------------------
How i get the EndDate??
Was unable to get 99 seconds in '23:59:99'
Try this:
CREATE TABLE #temp(StartDate datetime, EndDate datetime, Month int, Year int)
INSERT #temp values
('2014-12-15 00:00:00', NULL, 12, 2014),
('2015-01-26 00:00:00', NULL, 1, 2015),
('2015-02-26 00:00:00', NULL, 2, 2015)
UPDATE t
SET EndDate = DateAdd(m, DateDiff(m, 0, StartDate), '1900-02-25T23:59:59')
FROM #temp t
SELECT * FROM #temp
Result:
StartDate EndDate Month Year
2014-12-15 00:00:00.000 2015-01-25 23:59:59.000 12 2014
2015-01-26 00:00:00.000 2015-02-25 23:59:59.000 1 2015
2015-02-26 00:00:00.000 2015-03-25 23:59:59.000 2 2015
Edit:
If you want to calculate the EndDate from next StartDate, here is your UPDATE statement:
UPDATE t
SET EndDate = (SELECT top 1 DateAdd(s, -1, StartDate) FROM #temp
WHERE t.StartDate < StartDate
ORDER BY startdate)
FROM #temp t
Result:
StartDate EndDate Month Year
2014-12-15 00:00:00.000 2015-01-25 23:59:59.000 12 2014
2015-01-26 00:00:00.000 2015-02-25 23:59:59.000 1 2015
2015-02-26 00:00:00.000 null 2 2015
SAMPLE TABLE
CREATE TABLE #TEMP(DATES DATETIME,DATES DATETIME,[MONTH] INT,[YEAR] INT)
INSERT INTO #TEMP
SELECT '2014-12-15 00:00:00', NULL, 12, 2014
UNION ALL
SELECT '2015-01-26 00:00:00', NULL, 1, 2015
UNION ALL
SELECT '2015-02-26 00:00:00', NULL, 2, 2015
QUERY
;WITH CTE AS
(
SELECT ROW_NUMBER() OVER(ORDER BY STARTDATE)RNO, STARTDATE
FROM #TEMP
)
,CTE2 AS
(
SELECT C1.STARTDATE,
ISNULL(DATEADD(S,-1,C2.STARTDATE),DATEADD(S,-1,DATEADD(MONTH,1,C1.STARTDATE))) ENDDATE
FROM CTE C1
LEFT JOIN CTE C2 ON C1.RNO=C2.RNO-1
)
UPDATE #TEMP
SET EndDate = CTE2.ENDDATE
FROM #TEMP T1
JOIN CTE2 ON T1.STARTDATE=CTE2.STARTDATE
RESULT
You can also do it with OUTER APPLY
select
c.StartDate
DATEADD(SECOND,-1,e.StartDate) EndDate
DATEPART(MONTH,StartDate) [Month],
DATEPART(YEAR,StartDate) [Year]
from [Cut-Off] c
outer apply (
select TOP 1
e.StartDate
from [Cut-Off] e
where
e.StartDate > c.StartDate
order by
e.StartDate
) e

How to pick last inserted DateTime from each month?

I have a table PriceDate with two columns PriceId and PriceDate with 3-4 entries per month.
I want to retrieve last inserted PriceDate for each month.
This is my table
PriceDate PriceId
2012-01-07 00:00:00.000 1
2012-01-14 00:00:00.000 2
2012-01-21 00:00:00.000 3
2012-01-28 00:00:00.000 4
2012-02-04 00:00:00.000 5
2012-02-11 00:00:00.000 6
2012-02-18 00:00:00.000 7
2012-02-25 00:00:00.000 8
I need this output
PriceDate DateFormat PriceId
2012-01-28 00:00:00.000 Jan 2012 4
2012-02-25 00:00:00.000 Feb 2012 8
This seems to do the trick:
declare #t table (PriceDate datetime not null, PriceId int not null)
insert into #t(PriceDate,PriceId) values
('2012-01-07T00:00:00.000',1),
('2012-01-14T00:00:00.000',2),
('2012-01-21T00:00:00.000',3),
('2012-01-28T00:00:00.000',4),
('2012-02-04T00:00:00.000',5),
('2012-02-11T00:00:00.000',6),
('2012-02-18T00:00:00.000',7),
('2012-02-25T00:00:00.000',8)
;With Numbered as (
select *,
ROW_NUMBER() OVER (
PARTITION BY DATEADD(month,DATEDIFF(month,0,PriceDate),0)
ORDER BY PriceDate desc) as rn
from #t
)
select PriceDate,
RIGHT(CONVERT(varchar(20),PriceDate,106),8) [Dateformat],
PriceId
from Numbered where rn=1
The DATEADD/DATEDIFF trick is to basically round every date to the start of its respective month.
Result:
PriceDate Dateformat PriceId
----------------------- -------- -----------
2012-01-28 00:00:00.000 Jan 2012 4
2012-02-25 00:00:00.000 Feb 2012 8
Similar to #Damian_The_Unbeliever's but using the YEAR() and Month() functions
;WITH DateOrdered
AS
(
SELECT PriceDate, PriceId,
ROW_NUMBER() OVER (
PARTITION BY YEAR(PriceDate), MONTH(PriceDate)
ORDER BY PriceDate DESC) As Num
from PriceDate
)
SELECT PriceDate, RIGHT(CONVERT(varchar(20),PriceDate,106),8) [Dateformat], PriceId
FROM DateOrdered
WHERE Num = 1

Resources