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)。
我有一个数据库可以在员工登录到不同状态时提供信息,但问题是它将整个持续时间与 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)。