'How to create a sql statement or anonymous plsql block to increase count when only having a start time and an end time

I have an ask for a count of number of guests in a venue broken down to the minute. The data set I have available to me is the venue, the date/time the guest entered the venue, and the date/time the guest exited the venue. The business is asking for a breakdown by minute of the count of guests in the venue.

For example, guest A enters the venue at 12:00 and exits at 13:00. Guest B enters the venue at 12:30 and exits at 13:30. The expected output would show a count of 1 from 12:00 to 12:29, a count of two from 12:30 to 13:00, and back to a count of one from 13:00 to 13:30.

I’m struggling with the ask due to restrictions placed upon me. I am not authorized to make any structure changes; therefore, no DDL, which means I am restricted to SQL or anonymous PLSQL blocks. More information: however, I am unsure if it is necessary. The database version is 12.2c and it is running on AIX.

I do have a workaround where I extract the dataset as a csv and import it into a C# console application, which I wrote, but I would prefer if the ask can be conducted within the Oracle ecosystem. I appreciate any help or insight you can share about my problem.



Solution 1:[1]

You can solve this problem with a combination of several tricks: connect by level <= 91 to create the 91 minutes for the time frame, a left join to include all minutes even if there isn't an event at that minute, a case and sum to count and sum arrivals and departures, and finally an analytic function to generate the running total of guests by adding arrivals and subtracting departures.

--The number of guests present per minute.
select
    the_minute,
    sum(arrive_counter + depart_counter) over (order by the_minute) guest_count
from
(
    --Join time and visits and count arrivals and departures.
    select
        the_minute,
        sum(case when the_minute = arrive_date then 1 else 0 end) arrive_counter,
        sum(case when the_minute = depart_date then -1 else 0 end) depart_counter
    from
    (
        --Every minute for a time period. (Change to 1441 for an entire day.)
        select timestamp '2022-01-24 12:00:00' + (level - 1) * interval '1' minute the_minute
        from dual
        connect by level <= 91
    ) minutes
    left join visit
        on minutes.the_minute = arrive_date
        or minutes.the_minute = depart_date
    group by the_minute
    order by the_minute
)
order by the_minute;

Results:

THE_MINUTE                         GUEST_COUNT
24-JAN-22 12.00.00.000000000 PM    1
24-JAN-22 12.01.00.000000000 PM    1
...
24-JAN-22 12.28.00.000000000 PM    1
24-JAN-22 12.29.00.000000000 PM    1
24-JAN-22 12.30.00.000000000 PM    2
24-JAN-22 12.31.00.000000000 PM    2
...
24-JAN-22 12.58.00.000000000 PM    2
24-JAN-22 12.59.00.000000000 PM    2
24-JAN-22 01.00.00.000000000 PM    1
24-JAN-22 01.01.00.000000000 PM    1
...
24-JAN-22 01.28.00.000000000 PM    1
24-JAN-22 01.29.00.000000000 PM    1
24-JAN-22 01.30.00.000000000 PM    0

Solution 2:[2]

You can use:

SELECT timestamp AS time_from,
       LEAD(timestamp) OVER(ORDER BY timestamp) AS time_to,
       SUM(SUM(change_in_guests)) OVER (ORDER BY timestamp) AS guests
FROM   guests
UNPIVOT(
  timestamp FOR change_in_guests IN (
    entry AS +1,
    exit  AS -1
  )
)
GROUP BY timestamp;

Which, for the sample data:

CREATE TABLE guests (id, entry, exit) AS
SELECT 'A', DATE '2022-01-25' + INTERVAL '12:00' HOUR TO MINUTE, DATE '2022-01-25' + INTERVAL '13:00' HOUR TO MINUTE FROM DUAL UNION ALL
SELECT 'B', DATE '2022-01-25' + INTERVAL '12:30' HOUR TO MINUTE, DATE '2022-01-25' + INTERVAL '13:30' HOUR TO MINUTE FROM DUAL;

Outputs:

TIME_FROM TIME_TO GUESTS
2022-01-25 12:00:00 2022-01-25 12:30:00 1
2022-01-25 12:30:00 2022-01-25 13:00:00 2
2022-01-25 13:00:00 2022-01-25 13:30:00 1
2022-01-25 13:30:00 null 0

If you want it minute-by-minute then:

WITH minutes (minute, time_to, guests) AS (
  SELECT timestamp,
         LEAD(timestamp) OVER(ORDER BY timestamp),
         SUM(SUM(change_in_guests)) OVER (ORDER BY timestamp)
  FROM   guests
  UNPIVOT(
    timestamp FOR change_in_guests IN (
      entry AS +1,
      exit  AS -1
    )
  )
  GROUP BY timestamp
UNION ALL
  SELECT minute + INTERVAL '1' MINUTE,
         time_to,
         guests
  FROM   minutes
  WHERE  minute + INTERVAL '1' MINUTE < time_to
)
SEARCH DEPTH FIRST BY minute SET order_rn
SELECT minute,
       guests
FROM   minutes;

Which outputs:

MINUTE GUESTS
2022-01-25 12:00:00 1
2022-01-25 12:01:00 1
2022-01-25 12:02:00 1
... ...
2022-01-25 12:28:00 1
2022-01-25 12:29:00 1
2022-01-25 12:30:00 2
2022-01-25 12:31:00 2
... ...
2022-01-25 12:58:00 2
2022-01-25 12:59:00 2
2022-01-25 13:00:00 1
2022-01-25 13:01:00 1
... ...
2022-01-25 13:28:00 1
2022-01-25 13:29:00 1
2022-01-25 13:30:00 0

db<>fiddle here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jon Heller
Solution 2