October 22, 2008 at 3:06 am
Hi all,
I have a problem and I don't know if it can be solved within SQL. I am thinking about a TALLY table but I can't get a good start.
By executing a query on some tables I will get a result like this:
StartDateEndDateUnits
2008-03-07 00:00:00.0002008-04-16 00:00:00.0003
2008-04-17 00:00:00.0002008-04-30 00:00:00.0005
2008-05-01 00:00:00.0002008-06-29 00:00:00.0001
2008-06-30 00:00:00.0002008-07-14 00:00:00.0004
2008-07-15 00:00:00.0002008-09-02 00:00:00.0001
2008-09-03 00:00:00.0002008-09-30 00:00:00.0008
2008-10-01 00:00:00.0002008-10-22 00:00:00.0003
As you can see, some rows contain a startdate and an enddate that are in different months. I want to split those rows into seperate rows with the startdate and the enddate in the same month. Like the period "2008-07-15" till "2008-09-02" must be split into three seperate rows ("2008-07-15" till "2008-07-31", "2008-08-01" till "2008-08-31" and "2008-09-01" till "2008-09-02")
Is this possible??
Below is some sample code. The last SELECT shows the final results I need.
if exists (select * from tempdb.sys.objects where type = 'U' and name like '#DatePeriod%')
drop table #DatePeriod
Create table #DatePeriod
(StartDate DateTime,
EndDate DateTime,
Units int)
-- create the sample data
INSERT INTO #DatePeriod
SELECT '20080307','20080416',3 UNION ALL
SELECT '20080417','20080430',5 UNION ALL
SELECT '20080501','20080629',1 UNION ALL
SELECT '20080630','20080714',4 UNION ALL
SELECT '20080715','20080902',1 UNION ALL
SELECT '20080903','20080930',8 UNION ALL
SELECT '20081001','20081022',3
SELECT * FROM #DatePeriod
DROP TABLE #DatePeriod
-- show the final result I need
SELECT '20080307' as 'begin','20080331' as 'end',3 as 'Units' UNION ALL
SELECT '20080401','20080416',3 UNION ALL
SELECT '20080417','20080430',5 UNION ALL
SELECT '20080501','20080531',1 UNION ALL
SELECT '20080601','20080629',1 UNION ALL
SELECT '20080630','20080630',4 UNION ALL
SELECT '20080701','20080714',4 UNION ALL
SELECT '20080715','20080731',1 UNION ALL
SELECT '20080801','20080831',1 UNION ALL
SELECT '20080901','20080902',1 UNION ALL
SELECT '20080903','20080930',8 UNION ALL
SELECT '20081001','20081022',3
October 22, 2008 at 3:54 am
You'll need a calendar table with month start and end dates
if object_id('tempdb..#Calendar') is not null
drop table #Calendar
Create table #Calendar(dtStart DateTime,dtEnd as dateadd(day,-1,dateadd(month,1,dtStart)))
insert into #Calendar(dtStart)
select '20080301' union all
select '20080401' union all
select '20080501' union all
select '20080601' union all
select '20080701' union all
select '20080801' union all
select '20080901' union all
select '20081001'
SELECT CASE WHEN p.StartDate>c.dtStart THEN p.StartDate ELSE c.dtStart END AS 'begin',
CASE WHEN p.EndDate<c.dtEnd THEN p.EndDate ELSE c.dtEnd END AS 'end',
p.Units
FROM #DatePeriod p
INNER JOIN #Calendar c ON c.dtStart BETWEEN p.StartDate AND p.EndDate OR c.dtEnd BETWEEN p.StartDate AND p.EndDate
ORDER BY p.StartDate
____________________________________________________
Deja View - The strange feeling that somewhere, sometime you've optimised this query before
How to get the best help on a forum
http://www.sqlservercentral.com/articles/Best+Practices/61537October 22, 2008 at 4:09 am
Thanks Mark, it works like a charm.
I knew a TALLY table could bring me the solution 😀
October 23, 2008 at 3:18 am
hi
I know that solution provided works. But my question is;
Is this not a very Hard coded solution for the particular example that was given.
Surely the more generic solution is not much more difficult and should be the prefered solution.? 🙂
October 23, 2008 at 3:37 am
It seems I can not post code to the site, I wanted to post a more generic solution
October 23, 2008 at 4:21 am
AnzioBake (10/23/2008)
It seems I can not post code to the site, I wanted to post a more generic solution
Hi Anzio,
Allthough the provided solution fits my current needs, I'm interested in your general approach.
You can copy/paste the code into a reply. Use the -designator to seperate your code from your text (see the IFCode Shortcuts on the lefts side beside your 'new reply' window).
October 23, 2008 at 4:55 am
I tried that, all of that. I will attempt to put the solution in words.
1. Calculate the dateDiff in months between the Start and End Dates
S: E: Diff:
2008-06-15 2008-08-10 2
2. Join to a Table (tally, derived, CTE ) of numbers that covers your range including 0
Start End Diff Num
2008-06-15 2008-08-10 2 0
2008-06-15 2008-08-10 2 1
2008-06-15 2008-08-10 2 2
3. Calculate the Start of months (and therefore the End )
Start End Diff Num NewStart NewEnd
2008-06-15 2008-08-10 2 0 2008-06-01 2008-06-30
2008-06-15 2008-08-10 2 1 2008-07-01 2008-07-31
2008-06-15 2008-08-10 2 2 2008-08-01 2008-08-31
NewStart = DateAdd(Month, DateDiff( month, 0, Start) +Num ,0 )
Then do the Case When NewStart > Start Etc.
October 23, 2008 at 6:05 am
Hi Anzio,
I have work out your solution. Maybe others can benefit from this code.
As you allready mentioned, this solution is more generic. The TALLY table now only contains the number of months (between startdate and enddate) instead of a fixed startdate and enddate of possible periods. A bit more coding is involved to get the dates, but that is done only once. 😉
if exists (select * from tempdb..sysobjects where type = 'U' and name like '#Tally%')
drop table #Tally
Create table #Tally
(Numbers tinyint)
-- fill the tally-table with numeric range
INSERT INTO #Tally
SELECT 0 UNION ALL
SELECT 1 UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4
if exists (select * from tempdb..sysobjects where type = 'U' and name like '#DatePeriod%')
drop table #DatePeriod
Create table #DatePeriod
(StartDate DateTime,
EndDate DateTime,
Units int)
-- create the sample data
INSERT INTO #DatePeriod
SELECT '20080307', '20080416', 3 UNION ALL
SELECT '20080417', '20080430', 5 UNION ALL
SELECT '20080501', '20080629', 1 UNION ALL
SELECT '20080630', '20080714', 4 UNION ALL
SELECT '20080715', '20080902', 1 UNION ALL
SELECT '20080903', '20080930', 8 UNION ALL
SELECT '20081001', '20081022', 3
-- select and calculate the periods
SELECT
StartDate AS OriginalStartDate
, EndDate AS OriginalEndDate
, CASE WHEN (DateAdd(Month, DateDiff(month, 0, StartDate) + Numbers ,0)) <= StartDate
THEN StartDate
ELSE DateAdd(Month, DateDiff(month, 0, StartDate) + Numbers ,0)
END AS PeriodStart
, CASE WHEN (DateAdd(Day, -1, DateAdd(Month, DateDiff(month, 0, StartDate) + Numbers + 1 ,0))) > EndDate
THEN EndDate
ELSE DateAdd(Day, -1, DateAdd(Month, DateDiff(month, 0, StartDate) + Numbers + 1 ,0))
END AS PeriodEnd
, Units
FROM
#DatePeriod, #Tally
WHERE
datediff(m, StartDate, EndDate) >= Numbers
ORDER BY StartDate, Numbers
DROP TABLE #DatePeriod
DROP TABLE #Tally
October 23, 2008 at 7:54 am
On the money.
October 23, 2008 at 11:41 am
Glad you have a working solution, but I feel the need to point out that with this new "split" table, the meaning of the value in the UNITS field has changed, so attempts to use aggregates against that field may well return incorrect results, because the UNITS value applies to an entire period, for which you now have no indicator in the new table that would mark a record as one that began (or the one that ended) the period to which the UNITS value applies. FYI...
Steve
(aka smunson)
:):):)
Steve (aka sgmunson) 🙂 🙂 🙂
Rent Servers for Income (picks and shovels strategy)
October 23, 2008 at 11:35 pm
smunson (10/23/2008)
...the meaning of the value in the UNITS field has changed...
Hi Steve,
Thanks for pointing that out. But I created this sample to only simulate my data. The UNITS value equals in real life multiple columns containing (mostly) text data. In my business case each row needs to have the value of the original period.
But for anyone using this code: Adjust it to your business needs!! :w00t:
January 1, 2009 at 7:10 pm
HanShi (10/23/2008)
I have work out your solution. Maybe others can benefit from this code.As you allready mentioned, this solution is more generic. The TALLY table now only contains the number of months (between startdate and enddate) instead of a fixed startdate and enddate of possible periods. A bit more coding is involved to get the dates, but that is done only once. 😉
I missed this post, before. Well done, HanShi!
--Jeff Moden
Change is inevitable... Change for the better is not.
February 19, 2009 at 10:23 am
Great post- it looks like I will be able to utilize/modify parts of this code to work with my business needs ... the report I'm creating is tough!
See if you can follow these requirements:
I have a date range, dateA through dateB. (e.g., feb1 - feb28 ... these would be my input parameters for my report)
I have a series of events that fall anywhere inside and/or outside of that date range, partially within that date range, or fully covering that date range.
If my event falls within this date range, but the start of the event precedes my date range, then I need to split that date range at dateA. (So following my example, if my event started was jan20-feb10, then i need that split into two dates, jan20-feb1, and feb1-feb10).
If my event falls within this date range, but the end of the event exceeds my date range, then i need to split that date range at dateB. (so if my event was feb20-mar10, then i need feb20-feb28 and feb28-mar10).
On the same note, if my event precedes AND exceeds my date range, then my single event will need to be split three ways.
That's the problem I am tackling right now, and I should be able to work through it, but man that's tough! If anybody wants to add some input, please do!
February 19, 2009 at 11:44 am
rncruz,
Does your Events table specify the time as well as the date of the start and end of your events, or is the time portion zero, e.g. '2009-02-03 00:00:00'?
Likewise for your input date range (@DateA, @DateB)
Do these parameters allow times to be specified?
Is this date range inclusive, i.e. if an event starts on @DateB and ends at some date after @DateB, should the event be split into 2 parts?
--Andrew
February 19, 2009 at 11:54 am
The events table will specify the time, the dateRange values will not.
So for example, I will have, 2009-02-01 00:00:00 thru 2009-02-28 00:00:00
While an event can start at like, "2009-02-01 8:30:00" and end at "2009-03-05 00:00:00"
So this event will be partially inclusive to this date range. This specific event would need to be split into
2009-02-01 thru 2009-02-28 and
2009-02-28 thru 2009-03-05
I've actually just drawn out all my possible situations on paper ... i just need to code it up now ... but it is not proving to be easy ...
Viewing 15 posts - 1 through 15 (of 18 total)
You must be logged in to reply to this topic. Login to reply