Oracle - 将持续时间与不同的 15 分钟间隔相关联

Oracle - Associating Durations to different 15 Minute Intervals

我有一个数据库可以在员工登录到不同状态时提供信息,但问题是它将整个持续时间与 15 分钟间隔相关联,无论持续时间是否长于 15 分钟间隔,这会导致创建时出现差异计算这些数据。

样本数据集:

**ID    | Date      | Start Time            | End Time              | Secs | State    | SubState**

10001 | 18/MAR/20 | 09:06:41.000000000 AM | 09:09:31.000000000 AM | 170  | Ready    | Ready

10001 | 18/MAR/20 | 09:09:31.000000000 AM | 09:44:41.000000000 AM | 2110 | NotReady | Busy

10001 | 18/MAR/20 | 09:44:41.000000000 AM | 09:51:31.000000000 AM | 410  | NotReady | ACW

10001 | 18/MAR/20 | 09:51:31.000000000 AM | 09:54:25.000000000 AM | 174  | NotReady | Busy

10001 | 18/MAR/20 | 09:54:25.000000000 AM | 09:55:00.000000000 AM | 35   | NotReady | ACW

想要的结果:

**ID    | Date      | 15 Int  | Secs  | State    | SubState**

10001 | 18/MAR/20 | 09:00 AM | 170   | Ready    | Ready

10001 | 18/MAR/20 | 09:00 AM | 329   | NotReady | Busy

10001 | 18/MAR/20 | 09:15 AM | 900   | NotReady | Busy

10001 | 18/MAR/20 | 09:30 AM | 881   | NotReady | Busy

10001 | 18/MAR/20 | 09:30 AM | 19    | NotReady | ACW

10001 | 18/MAR/20 | 09:45 AM | 391   | NotReady | ACW

10001 | 18/MAR/20 | 09:45 AM | 174   | NotReady | Busy

10001 | 18/MAR/20 | 09:45 AM | 35    | NotReady | Busy

在 Oracle SQL 中实现此目标的最简单方法是什么?

这类似于我以前见过的问题。我所做的示例查询基于每 15 分钟的时间段并分析与其重叠的所有时间间隔。尝试以相反的方式进行操作会让人感到困惑。

根据评论,我不确定您的真实数据是否可能每 15 分钟重复 state/substate 组合,如果是这样,下面的查询可能需要修改才能正确聚合。

我注意到在你的 "Desired Result" 的最后一行,你有一个 "Busy" 的 SubState,而在数据集中,这个 35 秒的间隔表示 "ACW"。我认为这只是一个错误,我的结果是后一个值。

查询大纲:

  • 首先,我们创建 mytable2,它从 "mytable" 获取样本数据并将开始和结束时间转换为 Oracle 日期。

  • 然后,我们创建q1,其中有一行m1列在数据之前的小时,m2列在数据之后的小时。

  • 然后,我们创建 q2,它具有我们感兴趣的所有 15 分钟间隔的起点。只要您需要一些序列号,使用 connect by...rownum 是一种常见的模式。

最后,实际查询是一个四向联合,其中每个分支连接 q2 和 mytable2。

  • 分支 #1 是间隔完全在 15 分钟内的地方。
  • 分支 #2 是 15 分钟的片段完全在间隔内的地方。
  • 分支#3 是间隔在 15 分钟片段之前开始并在 15 分钟片段内结束的地方。
  • 分支 #4 是间隔从 15 分钟片段开始到结束的地方。
with mytable2 as (
    select ID, date_ as "Date",
    cast(to_timestamp(date_ || ' ' ||  START_TIME, 'dd/mon/yy HH:MI:SS.FF AM') as date) "Start Time",
    cast(to_timestamp(date_ || ' ' || END_TIME, 'dd/mon/yy HH:MI:SS.FF AM')as date) "End Time",
    secs,
    state,
    substate
    from mytable
), q1 as (
select trunc(min("Start Time"), 'hh') as m1, trunc(max("End Time")+1/24, 'hh') as m2
from mytable2
),
q2 as (
select m1 + (rownum-1)/24/4 as b1, m1 + rownum/24/4 as b2
from q1, dual
connect by rownum <= (m2-m1)*24*4
)
select "ID", "Date", "15 Int", "Secs", "State", "SubState" from (
select ID, "Date", "Start Time", to_char(b1, 'HH:MI AM') as "15 Int",  secs as "Secs", state as "State", substate as "SubState" from mytable2 m
join
 q2 on "Start Time" >= b1 and "End Time" <= b2
 union
 select ID, "Date",b1 as "Start Time",  to_char(b1, 'HH:MI AM') as "15 Int", 900 , state, substate from mytable2 m
join
 q2 on "Start Time" < b1 and "End Time" > b2
 union
 select ID, "Date",b1 as "Start Time",  to_char(b1, 'HH:MI AM') as "15 Int",  round(("End Time" - b1)*24*3600,0) , state, substate from mytable2 m
join
 q2 on "Start Time" < b1 and ("End Time" between b1 and  b2)
 union
 select ID, "Date","Start Time", to_char(b1, 'HH:MI AM') as "15 Int",  round((b2-"Start Time")*24*3600,0), state, substate from mytable2 m
join
 q2 on ("Start Time" between b1 and b2 )and "End Time" > b2
) q 
order by q.ID, q."Start Time"
+-------+-----------+----------+------+----------+----------+
|  ID   |   Date    |  15 Int  | Secs |  State   | SubState |
+-------+-----------+----------+------+----------+----------+
| 10001 | 18/MAR/20 | 09:00 AM |  170 | Ready    | Ready    |
| 10001 | 18/MAR/20 | 09:00 AM |  329 | NotReady | Busy     |
| 10001 | 18/MAR/20 | 09:15 AM |  900 | NotReady | Busy     |
| 10001 | 18/MAR/20 | 09:30 AM |  881 | NotReady | Busy     |
| 10001 | 18/MAR/20 | 09:30 AM |   19 | NotReady | ACW      |
| 10001 | 18/MAR/20 | 09:45 AM |  391 | NotReady | ACW      |
| 10001 | 18/MAR/20 | 09:45 AM |  174 | NotReady | Busy     |
| 10001 | 18/MAR/20 | 09:45 AM |   35 | NotReady | ACW      |
+-------+-----------+----------+------+----------+----------+

我使用以下 table 进行测试:

create table mytable as
select '10001' ID,'18/MAR/20' DATE_, '09:06:41.000000000 AM' START_TIME,'09:09:31.000000000 AM' END_TIME,170  secs, 'Ready' State, 'Ready' substate from dual
union all select '10001' ID,'18/MAR/20' DATE_, '09:09:31.000000000 AM' START_TIME,'09:44:41.000000000 AM' END_TIME,2110 secs, 'NotReady' State, 'Busy' substate from dual
union all select '10001' ID,'18/MAR/20' DATE_, '09:44:41.000000000 AM' START_TIME,'09:51:31.000000000 AM' END_TIME,410  secs, 'NotReady' State, 'ACW' substate from dual
union all select '10001' ID,'18/MAR/20' DATE_, '09:51:31.000000000 AM' START_TIME,'09:54:25.000000000 AM' END_TIME,174  secs, 'NotReady' State, 'Busy' substate from dual
union all select '10001' ID,'18/MAR/20' DATE_, '09:54:25.000000000 AM' START_TIME,'09:55:00.000000000 AM' END_TIME,35   secs, 'NotReady' State, 'ACW' substate from dual
+-------+-----------+-----------------------+-----------------------+------+----------+----------+
|  ID   |   DATE_   |      START_TIME       |       END_TIME        | SECS |  STATE   | SUBSTATE |
+-------+-----------+-----------------------+-----------------------+------+----------+----------+
| 10001 | 18/MAR/20 | 09:06:41.000000000 AM | 09:09:31.000000000 AM |  170 | Ready    | Ready    |
| 10001 | 18/MAR/20 | 09:09:31.000000000 AM | 09:44:41.000000000 AM | 2110 | NotReady | Busy     |
| 10001 | 18/MAR/20 | 09:44:41.000000000 AM | 09:51:31.000000000 AM |  410 | NotReady | ACW      |
| 10001 | 18/MAR/20 | 09:51:31.000000000 AM | 09:54:25.000000000 AM |  174 | NotReady | Busy     |
| 10001 | 18/MAR/20 | 09:54:25.000000000 AM | 09:55:00.000000000 AM |   35 | NotReady | ACW      |
+-------+-----------+-----------------------+-----------------------+------+----------+----------+

我认为您的问题有更简单的答案。虽然我同意其他人的意见,即你应该真正改变你存储日期的方式,但我现在坚持了这一点。以下查询应该适合您的需要:

WITH time_intervals AS
(
  SELECT s.min_date+(15*(LEVEL-1)/1440) AS INTERVAL_START,
         s.min_date+(15*LEVEL/1440) AS INTERVAL_END
  FROM (SELECT MIN(TO_DATE(u.event_date, 'DD/MON/YY')) AS MIN_DATE,
               MAX(TO_DATE(u.event_date, 'DD/MON/YY')) AS MAX_DATE
        FROM user_data u) s
  CONNECT BY LEVEL <= (s.max_date-s.min_date+1)*96 --96 is the number of 15 min intervals in a day
)
SELECT u.id AS ID, 
       ti.interval_start, 
       SUM((LEAST(ti.interval_end, u.end_time) - GREATEST(ti.interval_start, u.start_time))*86400) AS SECONDS,
       u.state,
       u.substate
FROM time_intervals ti
INNER JOIN (SELECT ud.id, 
                   TO_DATE(ud.event_date||' '||ud.start_time, 'DD/MON/YY HH:MI:SS PM') AS START_TIME,
                   TO_DATE(ud.event_date||' '||ud.end_time, 'DD/MON/YY HH:MI:SS PM') AS END_TIME,
                   ud.state, ud.substate
            FROM user_data ud) u ON GREATEST(ti.interval_start, u.start_time) < LEAST(ti.interval_end, u.end_time)
GROUP BY u.id, ti.interval_start, u.state, u.substate;

首先,我们计算出您 table 中每一天的 15 分钟间隔。如果您想限制您的报告,只需将此 CTE 更改为仅考虑您要报告的天数。然后,我们根据 table 中与给定时间间隔重叠的记录,将 CTE 加入到您的现有数据中。每个重叠间隔乘以 86400(一天中的秒数)得到结果。最后,我们使用聚合函数 sum 来计算同一 15 分钟间隔内具有相同 ID、状态和子状态的多个事件。

我创建了一个 SQLFiddle 供您检查 (Link)。