October 30, 2013 at 4:24 am
Hi,
Wondering if anyone has an elegant solution to finding out how much time has elapsed for a specific hour.
declare @t as table (ptr int identity, START time, ENDD time)
insert into @t (START, ENDD)
SELECT '09:30', '10:30'
union all
SELECT '10:00', '11:00'
union all
SELECT '10:30', '11:30'
Taking the example table above, I am looking to output the amount of time elapsed for each hour block. So:
09 - 0.5
10 - 2
11 - 0.5
I can think of complicated and ugly ways using cursors and many lines of code, but I thought someone might have a more user friendly solution!
Many thanks for your time.
Matt
October 30, 2013 at 8:06 am
m.dunster (10/30/2013)
Hi,Wondering if anyone has an elegant solution to finding out how much time has elapsed for a specific hour.
declare @t as table (ptr int identity, START time, ENDD time)
insert into @t (START, ENDD)
SELECT '09:30', '10:30'
union all
SELECT '10:00', '11:00'
union all
SELECT '10:30', '11:30'
Taking the example table above, I am looking to output the amount of time elapsed for each hour block. So:
09 - 0.5
10 - 2
11 - 0.5
I can think of complicated and ugly ways using cursors and many lines of code, but I thought someone might have a more user friendly solution!
Many thanks for your time.
Matt
Thank you for providing ddl and sample data. I am confused by your expected output. Can you try to explain what you want? The business rules for the output is lost on me.
_______________________________________________________________
Need help? Help us help you.
Read the article at http://www.sqlservercentral.com/articles/Best+Practices/61537/ for best practices on asking questions.
Need to split a string? Try Jeff Modens splitter http://www.sqlservercentral.com/articles/Tally+Table/72993/.
Cross Tabs and Pivots, Part 1 – Converting Rows to Columns - http://www.sqlservercentral.com/articles/T-SQL/63681/
Cross Tabs and Pivots, Part 2 - Dynamic Cross Tabs - http://www.sqlservercentral.com/articles/Crosstab/65048/
Understanding and Using APPLY (Part 1) - http://www.sqlservercentral.com/articles/APPLY/69953/
Understanding and Using APPLY (Part 2) - http://www.sqlservercentral.com/articles/APPLY/69954/
October 30, 2013 at 8:14 am
Hi,
To explain further. Let us assume that table @t represents light bulbs being switched on and off. So the first light bulb is switched on at 9.30am and off at 10.30am, the second on at 10.00am and off at 11.00am, the third on at 10.30 am and off at 11.30am.
I need to know how long, within hour blocks, the total time expended for the sum of all light bulbs being on.
So in our example:
Between 9am to 10am, light bulb 1 was on for 0.5 hours, light bulb 2&3 were both off, so a total of half an hour (0.5)
Between 10am to 11am, light bulb 1 was on for 0.5 hours, light bulb 2 for an hour, and light bulb 3 on for 0.5 hours, so a total of 2 hours (2.0)
Between 11.00am to 12.00pm, light bulb 1 was off, light bulb 2 was off, hour, and light bulb 3 on for 0.5 hours, so a total of half an hour (0.5)
October 30, 2013 at 8:22 am
Not quite a complete solution, it doesn't handle spanning midnight
WITH Hours(hrStart,hrEnd) AS (
SELECT CAST(hrStart AS TIME),CAST(hrEnd AS TIME)
FROM (
VALUES ('00:00','01:00'),('01:00','02:00'),('02:00','03:00'),('03:00','04:00'),
('04:00','05:00'),('05:00','06:00'),('06:00','07:00'),('07:00','08:00'),
('08:00','09:00'),('09:00','10:00'),('10:00','11:00'),('11:00','12:00'),
('12:00','13:00'),('13:00','14:00'),('14:00','15:00'),('15:00','16:00'),
('16:00','17:00'),('17:00','18:00'),('18:00','19:00'),('19:00','20:00'),
('20:00','21:00'),('21:00','22:00'),('22:00','23:00'),('23:00','00:00')) h(hrStart,hrEnd))
SELECT h.hrStart AS [Hour],
SUM(DATEDIFF(minute,CASE WHEN t.START < h.hrStart THEN h.hrStart ELSE t.START END,
CASE WHEN t.ENDD > h.hrEnd THEN h.hrEnd ELSE t.ENDD END)) AS minutes
FROM Hours h
INNER JOIN @t t ON t.ENDD > h.hrStart AND t.START < h.hrEnd
GROUP BY h.hrStart
ORDER BY h.hrStart;
____________________________________________________
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 30, 2013 at 10:55 am
Thanks Mark. I'll give it a go. The midnight issue shouldn't be a problem.
October 30, 2013 at 6:41 pm
Another take (also not handling crossing midnight), with some sample data added.
declare @t as table (ptr int identity, START time, ENDD time)
insert into @t (START, ENDD)
SELECT '09:30', '10:30' union all
SELECT '10:00', '11:00' union all
SELECT '10:30', '11:30' union all
SELECT '09:30', '11:30' union all
SELECT '11:00', '11:30';
WITH Tally (n) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES (0),(0),(0),(0),(0),(0)) a(a)
CROSS JOIN (VALUES (0),(0),(0),(0)) b(b)
)
SELECT Hour=RIGHT(100+d.n-1,2) + ':00'
,LightBulbsShining=SUM(
CASE WHEN StartHr=c.n AND EndHr=c.n THEN DATEDIFF(minute, START, ENDD)/60.
WHEN StartHr=c.n AND EndHr<>c.n THEN DATEDIFF(minute, START, DATEADD(hour, 1, hr))/60.
ELSE DATEDIFF(minute, hr, ENDD)/60. END)
FROM @t a
CROSS APPLY
(
SELECT StartHr=DATEPART(hour, START), EndHr=DATEPART(hour, ENDD)
) b
CROSS APPLY
(
SELECT n, hr=RIGHT(100+n,2) + ':00'
FROM Tally
WHERE n BETWEEN StartHr AND EndHr
) c
RIGHT JOIN Tally d ON d.n-1 = c.n
GROUP BY RIGHT(100+d.n-1,2) + ':00';
My thought question: Have you ever been told that your query runs too fast?
My advice:
INDEXing a poor-performing query is like putting sugar on cat food. Yeah, it probably tastes better but are you sure you want to eat it?
The path of least resistance can be a slippery slope. Take care that fixing your fixes of fixes doesn't snowball and end up costing you more than fixing the root cause would have in the first place.
Need to UNPIVOT? Why not CROSS APPLY VALUES instead?[/url]
Since random numbers are too important to be left to chance, let's generate some![/url]
Learn to understand recursive CTEs by example.[/url]
[url url=http://www.sqlservercentral.com/articles/St
October 31, 2013 at 7:04 pm
Hi Mark,
Your solution is good, but it has a limitation in that if a period start and end more than one hour apart, it doesn't get calculated.
Dwain: Yours, I can't really understand (my limitation), but there is something wrong, if you alter the first end time to 13:30, then it counts all the extra hours in the 10:00 slot.
Thanks for your efforts and help. I'll have to keep going until I beat it.
Matt
November 1, 2013 at 7:04 pm
Here is my shot at this using Mark-101232 derived table for times.
--use any database which has this numbers table.
declare @t as table (ptr int identity, START time, ENDD time)
insert into @t (START, ENDD)
SELECT '09:30', '10:00'
union all
SELECT '10:00', '11:00'
union all
SELECT '10:30', '11:30'
union all
SELECT '13:30', '15:30'
union all
SELECT '18:30', '19:00'
union all
SELECT '10:00', '10:30'
union all
SELECT '03:00', '08:30'
union all
SELECT '19:00', '19:45'
--uses number table
--http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/you-require-a-numbers-table.aspx
/**************************************
Most of this code is because of expanding the
times in the start and end. If times span more than
1 hour this will generate a new record for this.
*******************************************/
IF OBJECT_ID('tempdb..#LightHour','u') IS NOT NULL
DROP TABLE #LightHour
;WITH ExpandHours
AS
(
SELECT t.ptr
,e.RowNum
,StartTime = CAST(e.StartTime AS TIME)
,EndTime = CAST(e.EndTime AS TIME)
,OriginalStartTime = t.START
,OriginalEndTime = t.ENDD
FROM @t t
CROSS APPLY (
SELECT RowNum = ROW_NUMBER() OVER (PARTITION BY t.ptr ORDER BY prev.Number)
,DATEADD(hh, prev.Number, dateadd(hour, datediff(hour, 0, t.START), 0)) AS StartTime
,DATEADD(hh, curr.Number, dateadd(hour, datediff(hour, 0, t.START), 0)) AS EndTime
FROM dbo.Numbers curr
JOIN dbo.Numbers prev
ON curr.Number = prev.Number + 1
WHERE curr.Number <= DATEDIFF(hh, t.START, t.ENDD)
) e
),
Final
AS
(
SELECT ptr
,StartTime = CASE WHEN StartTime < OriginalStartTime
THEN OriginalStartTime
ELSE StartTime
END
,EndTime = CASE WHEN OriginalEndTime < EndTime
THEN OriginalEndTime
ELSE EndTime
END
FROM ExpandHours
UNION
SELECT ptr
,StartTime = (SELECT MAX(c.EndTime) FROM ExpandHours c WHERE i.ptr = c.ptr)
,EndTime = OriginalEndTime
FROM ExpandHours i
)
SELECT ptr
,Start = StartTime
,Endd = EndTime
INTO #LightHour
FROM Final
WHERE StartTime <> EndTime
UNION
--if under 1 hour then include here
SELECT *
FROM @t
WHERE DATEDIFF(mi, START, ENDD) < 60
IF OBJECT_ID('tempdb..#Final','u') IS NOT NULL
DROP TABLE #Final
SELECT LighHour =hrStart
,TotalTimeOn_MI = SUM(CASE WHEN t.Start >= hrStart AND t.Start < hrEnd
THEN DATEDIFF(mi, t.Start, t.Endd)
ELSE 0
END)
--,ptr
INTO #Final
FROM #LightHour t
CROSS APPLY
(
SELECT hrStart = CAST(hrStart AS TIME)
,hrEnd = CAST(hrEnd AS TIME)
FROM (
VALUES ('00:00','01:00'),('01:00','02:00'),('02:00','03:00'),('03:00','04:00'),
('04:00','05:00'),('05:00','06:00'),('06:00','07:00'),('07:00','08:00'),
('08:00','09:00'),('09:00','10:00'),('10:00','11:00'),('11:00','12:00'),
('12:00','13:00'),('13:00','14:00'),('14:00','15:00'),('15:00','16:00'),
('16:00','17:00'),('17:00','18:00'),('18:00','19:00'),('19:00','20:00'),
('20:00','21:00'),('21:00','22:00'),('22:00','23:00'),('23:00','24:00')) h(hrStart,hrEnd)
) h
GROUP BY h.hrStart
-- ,ptr
SELECT *
FROM #Final
WHERE TotalTimeOn_MI > 0
November 1, 2013 at 7:37 pm
Borrowing from Dwain's sample data and Tally, here is my submission:
--== Dwain's setup ==--
declare @t as table (ptr int identity, START time, ENDD time)
insert into @t (START, ENDD)
SELECT '09:30', '10:30' union all
SELECT '10:00', '11:00' union all
SELECT '10:30', '11:30' union all
SELECT '09:30', '11:30' union all
SELECT '11:00', '11:30';
WITH Tally (n) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM (VALUES (0),(0),(0),(0),(0),(0)) a(a)
CROSS JOIN (VALUES (0),(0),(0),(0)) b(b)
)
--== And MM's solution ==--
select Tally.n as [Hour],SUM(calc.UpToENDD - calc.UpToSTART) as [TotalMinutes]
from Tally
left outer join @t as source
on Tally.n between datepart(hour,convert(datetime,source.START)) and datepart(hour,convert(datetime,source.ENDD))
outer apply (
select case when source.START is null then 0
when dateadd(hour,Tally.n,0)<convert(datetime,source.START) then datediff(minute,dateadd(hour,Tally.n,0),convert(datetime,source.START))
else 0
end as UpToSTART,
case when source.ENDD is null then 0
when Tally.n=datepart(hour,convert(datetime,source.ENDD)) then datediff(minute,dateadd(hour,Tally.n,0),convert(datetime,source.ENDD))
else 60
end as UpToENDD
) calc
group by Tally.n
order by Tally.n;
MM
select geometry::STGeomFromWKB(0x0106000000020000000103000000010000000B0000001000000000000840000000000000003DD8CCCCCCCCCC0840000000000000003DD8CCCCCCCCCC08408014AE47E17AFC3F040000000000104000CDCCCCCCCCEC3F9C999999999913408014AE47E17AFC3F9C99999999991340000000000000003D0000000000001440000000000000003D000000000000144000000000000000400400000000001040000000000000F03F100000000000084000000000000000401000000000000840000000000000003D0103000000010000000B000000000000000000143D000000000000003D009E99999999B93F000000000000003D009E99999999B93F8014AE47E17AFC3F400000000000F03F00CDCCCCCCCCEC3FA06666666666FE3F8014AE47E17AFC3FA06666666666FE3F000000000000003D1800000000000040000000000000003D18000000000000400000000000000040400000000000F03F000000000000F03F000000000000143D0000000000000040000000000000143D000000000000003D, 0);
November 1, 2013 at 7:54 pm
Monster Maghoul thank you for sharing! This is awesome now I just need to understand what is going on here. This guy will perform well under load too!
November 2, 2013 at 5:42 am
brad.mason5 (11/1/2013)
Monster Maghoul thank you for sharing! This is awesome now I just need to understand what is going on here. This guy will perform well under load too!
You are most welcome.
It's really quite simple in terms of "how it works".
The link from Tally (the hours in a day) to your data is where the "hour of the day (n)" falls between the start and end time in your data.
This gives us every data row for each hour, then it is simply a CASE of working out how much of that hour each start/end pair has consumed and SUMming them up.
MM
select geometry::STGeomFromWKB(0x0106000000020000000103000000010000000B0000001000000000000840000000000000003DD8CCCCCCCCCC0840000000000000003DD8CCCCCCCCCC08408014AE47E17AFC3F040000000000104000CDCCCCCCCCEC3F9C999999999913408014AE47E17AFC3F9C99999999991340000000000000003D0000000000001440000000000000003D000000000000144000000000000000400400000000001040000000000000F03F100000000000084000000000000000401000000000000840000000000000003D0103000000010000000B000000000000000000143D000000000000003D009E99999999B93F000000000000003D009E99999999B93F8014AE47E17AFC3F400000000000F03F00CDCCCCCCCCEC3FA06666666666FE3F8014AE47E17AFC3FA06666666666FE3F000000000000003D1800000000000040000000000000003D18000000000000400000000000000040400000000000F03F000000000000F03F000000000000143D0000000000000040000000000000143D000000000000003D, 0);
November 6, 2013 at 9:22 am
Hi Guys,
Thanks all for your input. I suppose the MM solution is as close as we are going to get to "eloquent", adn certainly beats mine !!
Thanks again.
Matt
Viewing 12 posts - 1 through 11 (of 11 total)
You must be logged in to reply to this topic. Login to reply