views:

38

answers:

2

I have a query I am trying to make on a table that has multiple entries for people with a start time and an end time e.g.:

name,startime,endtime
('richard','2010-04-21 08:01:15','2010-04-21 08:06:15'),
('bill','2010-04-21 08:07:45','2010-04-21 08:11:15')

What I need to do is create a report showing the seconds of each entry within each 5 minute interval e.g.

name,time,seconds
------------------
richard,8:00,225
richard,8:05,75
bill,8:05,135
bill,8:10,75

So the query must create these intervals and then count the seconds for each record to show the total seconds in that interval. Any help would be appreciated.

A: 

Following script should get you started.

A few notes of warning though

  • it does not work in its current form if starttime and endtime are not the same day
  • there are no checks in place wether starttime is less than endtime

SQL Script

DECLARE @People TABLE (
  Name VARCHAR(32)
  , StartTime DATETIME
  , EndTime DATETIME
)

DECLARE @MinHour INTEGER
DECLARE @MaxHour INTEGER
DECLARE @Times TABLE (
  Hour INTEGER
  , Minute INTEGER
)

INSERT INTO @People
SELECT           'richard', '2010-04-21 08:01:15', '2010-04-21 08:06:15'
UNION ALL SELECT 'bill'   , '2010-04-21 08:07:45', '2010-04-21 08:11:15'


SELECT  @MinHour = MIN(DATEPART(hh, StartTime))
        , @MaxHour = MAX(DATEPART(hh, EndTime))
FROM    @People

WHILE @MinHour < @MaxHour + 1
BEGIN
  INSERT INTO @Times 
  SELECT @MinHour, 0
  UNION ALL SELECT @MinHour, 5
  UNION ALL SELECT @MinHour, 10
  UNION ALL SELECT @MinHour, 15
  UNION ALL SELECT @MinHour, 20
  UNION ALL SELECT @MinHour, 25
  UNION ALL SELECT @MinHour, 30
  UNION ALL SELECT @MinHour, 35
  UNION ALL SELECT @MinHour, 40
  UNION ALL SELECT @MinHour, 45
  UNION ALL SELECT @MinHour, 50
  UNION ALL SELECT @MinHour, 55
  SET @MinHour = @MinHour + 1
END

SELECT  p.Name
        , t.Hour
        , t.Minute
        , CASE WHEN DATEPART(mi, p.EndTime) - DATEPART(mi, p.EndTime) % 5  = t.Minute 
          THEN 60 * (DATEPART(mi, p.EndTime) % 5) + DATEPART(ss, p.EndTime)
          ELSE 300 - 60 * (DATEPART(mi, p.StartTime) % 5) - DATEPART(ss, p.StartTime)
          END
FROM    @People p
        INNER JOIN @Times t ON DATEPART(hh, p.StartTime) = t.Hour
                               AND DATEPART(hh, p.EndTime) = t.Hour
                               AND DATEPART(mi, p.StartTime) - DATEPART(mi, p.StartTime) % 5 <= t.Minute
                               AND DATEPART(mi, p.EndTime) - DATEPART(mi, p.EndTime) % 5 >= t.Minute
ORDER BY 
        p.Name
Lieven
I tried this solution but got some errors on some entries.Bill 2010-06-23 08:12:59.000 2010-06-23 08:14:12.000Bill 2010-06-23 08:14:12.000 2010-06-23 08:25:12.000Returns:Bill 8 10 252Bill 8 10 48Bill 8 15 48Bill 8 20 48Bill 8 25 12Which is incorrect. Any ideas and what may be causing this?
Richard
A: 

Here's an iterative solution that will work over any time period where the start time is before the end time. You didn't specify whether you wanted to keep the date component for reporting time intervals in different days at 8am on a different line or whether you wanted to group them. It should be quite easy to discard the date component by converting the time_bucket column in my solution to a varchar then substring, group by that varchar then sum the seconds in that group.

If you're doing analysis by time buckets then you should probably have a time bucket dimension table, similar to what Lieven proposes in his solution. That solves your cardinality issues. Without that you'd have to do something like this:

create table #results
( name varchar(20) not null, time_bucket datetime not null, seconds int not null )

declare @name varchar(20), @startTime datetime, @endTime datetime, @timeBucket datetime, @secondsInBucket int
declare dataCur cursor for select * from source_data
open dataCur

fetch next from dataCur into @name, @startTime, @endTime
while @@fetch_status = 0
begin
    set @timeBucket = convert(datetime, convert(varchar(14), @startTime, 120) + convert(varchar(2), (datepart(mi, @startTime) / 5) * 5), 120)

    while @timeBucket < @endTime
    begin
        set @secondsInBucket = case
            when @timeBucket < @startTime then datediff(ss, @startTime, dateadd(mi, 5, @timeBucket))
            when @endTime < dateadd(mi, 5, @timeBucket) then datediff(ss, @timeBucket, @endTime)
            else 300
        end

        insert into #results values (@name, @timeBucket, @secondsInBucket)
        set @timeBucket = dateadd(mi, 5, @timeBucket)
    end

    fetch next from dataCur into @name, @startTime, @endTime
end

close dataCur
deallocate dataCur

select * from #results
Sir Wobin