Home > Back-end >  T- SQL Split time in half hour intervals
T- SQL Split time in half hour intervals

Time:10-14

I have a table calls that shows every call for every employee and looks like this:

date employee call_pick_up_time
2021-10-08 12345 2021-10-08 08:13:26
2021-10-08 123456 2021-10-08 08:16:42

Now I want to show the call count for each employee for every 30 minutes interval:

interval employee call_count
08:00 12345 4
08:00 123456 7
08:30 12345 5
08:30 123456 3

The considered period is 08:00 - 08:30 / 08:30 -09:00 and so on.

Is there an easy way to get the desired result?

Thanks in advance.

CodePudding user response:

The way I like to round datetime values to the nearest n-minute interval is to take advantage of SQL Server's integer math behavior. If you take the difference in minutes between midnight and the time in question, then divide by n and then multiply by n, it gets rid of any remainder. So to round right now down to the previous 30-minute interval:

DECLARE @now   datetime = GETDATE();
DECLARE @today datetime = CONVERT(date, @now);

SELECT DATEADD
(
  MINUTE, 
  DATEDIFF(MINUTE, @today, @now)/30*30,
  @today
);

We can apply this to your query by taking your source table and using CROSS APPLY as Charlie suggested and apply that same calculation to your source values (you have to do a little more conversion inline because you don't have nice, static variables to use):

DECLARE @WindowSizeInMinutes smallint = 30;

SELECT x.interval, c.employee, call_count = COUNT(*)
FROM dbo.Calls AS c
CROSS APPLY 
(
  VALUES 
  (
    DATEADD
    (
      MINUTE, 
      DATEDIFF
      (
        MINUTE, 
        CONVERT(datetime, CONVERT(date, call_pick_up_time)), 
        call_pick_up_time
      ) / @WindowSizeInMinutes * @WindowSizeInMinutes,
      CONVERT(datetime, CONVERT(date, call_pick_up_time))
    )
  )
) AS x(interval)
-- WHERE c.something something
GROUP BY c.employee, x.interval;

If there is an index on call_pick_up_time you were hoping to use, that's out the window.

Another approach that could make use of an index is to pre-determine all the possible 30-minute windows in the range you're after, and then inner join to those:

DECLARE @WindowSizeInMinutes smallint = 30,
        @min_date datetime = '20211001', 
        @max_date datetime = '20211014';
    
;WITH n(n) AS
(
  SELECT 0 UNION ALL 
  SELECT n   1
    FROM n WHERE n <= 24*60/@WindowSizeInMinutes
),
days(d) AS
(
  SELECT @min_date UNION ALL
  SELECT DATEADD(DAY, 1, d)
    FROM days WHERE d < @max_date
),
intervals AS 
(
  SELECT interval_start = DATEADD(MINUTE, n*@WindowSizeInMinutes, d),
         interval_end   = DATEADD(MINUTE, (n 1)*@WindowSizeInMinutes, d)
    FROM n CROSS JOIN days
)
SELECT interval = i.interval_start, 
   c.employee, 
   call_count = COUNT(c.employee)
FROM intervals AS i
INNER JOIN dbo.Calls AS c
   ON c.call_pick_up_time >= i.interval_start
  AND c.call_pick_up_time <  i.interval_end
GROUP BY c.employee, i.interval_start;

While more complicated, one nice thing about this approach is if you want to show slots for windows where no employees had calls, you could just change the join to an inner join, and if you wanted a slot for each employee, you could just add a CTE with the list of employees and cross join to that.

  • Related