How can I check for average concurrent events in a SQL table based on the date, time and duration of the events?

后端 未结 6 1040
滥情空心
滥情空心 2021-02-05 22:48

I have a set of call detail records, and from those records, I\'m supposed to determine the average concurrent active calls per system, per hour (at a precision of one minute).

6条回答
  •  谎友^
    谎友^ (楼主)
    2021-02-05 23:26

    As MarkusQ stated, your definition of "concurrent" allows you to short cut the maths.

    • Call (A) starts at "12:00:59" and ends at "12:01:01"
    • Call (B) starts at "12:01:59" and ends at "12:02:01"
      => 1 call in the "12:00" interval
      => 2 calls in the "12:01" interval
      => 1 call in the "12:02" interval

    The average concurrent calls is then (1+2+1)/intervalCount

    The (1+2+1) can be calculated differently, and more quickly/easily:

    • Call (A) covers 2 different minute intervals (12:00 and 12:01)
    • Call (B) covers 2 different minute intervals (12:01 and 12:02)
      => total covered minutes = 4

    The important fact here (and why I bothered reply after MarkusQ posted) is that the duration of a call itself isn't enough to calculate how many minute intervals are covered. In my example, both calls only last 2 seconds...

    You need the following info:
    - the "start time", rounded down to the minute
    - the "end time", rounded down to the minute
    => covered intervals = number of minutes difference + 1

    To round the "time" field down to the minute I'd use this...

    DATEADD(minute, DATEDIFF(minute, 0, time), 0)
    

    So the number of covered minutes by a single call would be...

    DATEDIFF(
       minute,
       DATEADD(minute, DATEDIFF(minute, 0, time), 0),
       DATEADD(second, dur, time)
    ) + 1
    
    No need to round the "end time" down.
    Using DATEDIFF(minute) gives rounding down anyway.
    

    SUM that value for the range you're looking at, then divde by the number of minutes in that range, and you have your answer.

    If you're only looking for calls that are truely concurrent you can't use such tricks, but it's still possible (I've had to do something similar). But for your definition of concurrent, this should do it...

    DECLARE
       @date DATETIME, @start DATETIME, @end DATETIME
    SELECT
       @date = '2009 Jan 01', @start = '12:00', @end = '13:00'
    
    SELECT
       system,
       SUM(
           DATEDIFF(
              minute,
              CASE WHEN
                 CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME) < @start
              THEN
                 @start
              ELSE
                 CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME)
              END,
              CASE WHEN
                 DATEADD(second, dur, CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME)) > @end
              THEN
                 @end
              ELSE
                 DATEADD(second, dur, CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME))
              END
           ) + 1
       )
       /
       CAST(DATEDIFF(minute, @start, @end) AS FLOAT)
    FROM
       records
    WHERE
       date = @date
       AND CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME) >= @start
       AND DATEADD(second, dur, CAST(LEFT(time,2) + ':' + RIGHT(time,2) AS DATETIME)) < @end
    GROUP BY
       system
    


    This will deliberately not include the interval 13:00->13:01
    Only the 60 "1 minute long intervals" 12:00->12:01 through to 12:59->13:00


    EDIT:

    I just noticed that your times and dates are stored as strings, you'd need to convert those to DATETIMEs for my code to work.

    EDIT2:

    Bug corrected. If a call started at "11:59:01" and ended at "12:00:01", the "11:59" interval should not be counted. CASE statements added to compensate.

    Various Layout Edits

提交回复
热议问题