将时间分为小时(oracle)(CTE)

时间:2019-01-10 18:46:24

标签: sql oracle common-table-expression

因此,我的目标是能够计算在小时范围内用于某些活动的时间。

我的数据包含:某些活动的开始和结束, 例如我知道有人从'2019-01-09 17:04:34'中断到'2019-01-09 19:55:03'。

我的目标是计算此人在“ 17-18”间隔中休息55分钟,在“ 18-19”间隔60分钟,在“ 19-20”间隔55分钟。

我的想法是始终拆分源,因此对于包含start和活动的行,我将收到与时间范围(在小时范围内)划分的行一样多的行(对于此示例数据,我将收到3行: 2019-01-09 17:04:34'至'2019-01-09 17:59:59','2019-01-09 18:00:00'至'2019-01-09 18:59:59'和“ 2019-01-09 19:00:00”到“ 2019-01-09 19:55:03”)

如果我能获得类似的东西,我就可以算出我需要的所有东西。我预计要获得此结果,我应该使用CTE(因为我们不知道需要分割时间间隔的范围是多少),但是我对此没有经验。

希望我能清楚地解释我的问题。我在oracle sql开发人员上工作。

非常感谢您的帮助,至少提供了一些提示。

2 个答案:

答案 0 :(得分:0)

由于您提到了递归,因此使用了递归子查询分解:

-- CTE for sample data
with your_table (id, start_time, end_time) as (
  select 1, timestamp '2019-01-09 17:04:34', timestamp '2019-01-09 19:55:03' from dual
  union all
  select 2, timestamp '2019-01-09 23:47:01', timestamp '2019-01-10 02:05:03' from dual
  union all
  select 3, timestamp '2019-01-09 18:01:01', timestamp '2019-01-09 18:02:07' from dual
  union all
  select 4, timestamp '2019-01-09 13:00:00', timestamp '2019-01-09 14:00:01' from dual
),
-- recursive CTE
rcte (id, hour_period, minutes, period_start_time, end_time, hour_num) as (
  select id,
    -- first period is the original start hour
    extract(hour from start_time),
    -- minutes in first period, which can end at the end of that hour, or at original
    -- end time if earlier
    case when extract(minute from end_time) = 0
         and end_time >= cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour
         then 60
         else extract(minute from
           least(cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour, end_time)
           - start_time
         )
    end,
    -- calculate next period start
    cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour,
    -- original end time
    end_time,
    -- first hour period (for later ordering)
    1
  from your_table
  union all
  select id,
    -- this period's hour value
    extract(hour from period_start_time),
    -- minutes in this period - either 60 if we haven't reach the end time yet;
    -- or if we have then the number of minutes from the end time
    case when end_time < period_start_time + interval '1' hour
         then extract(minute from end_time)
         else 60
    end,
    -- calculate next period start
    period_start_time + interval '1' hour,
    -- original end time
    end_time,
    -- increment hour period (for later ordering)
    hour_num + 1
  from rcte
  where period_start_time < end_time
)
select id, hour_period, minutes
from rcte
order by id, hour_num;
        ID HOUR_PERIOD    MINUTES
---------- ----------- ----------
         1          17         55
         1          18         60
         1          19         55
         2          23         12
         2           0         60
         2           1         60
         2           2          5
         3          18          1
         4          13         60
         4          14          0

它找到在锚成员中该时间段的第一个小时所花费的时间,然后递归地查看随后的几小时,直到达到结束时间为止,每次都增加传递的结束时间;并在递归成员中检查是否使用固定的60分钟(如果知道尚未达到结束时间)或使用从结束时间开始的实际分钟。

我的示例时段包括跨越午夜,不到一小时的时段,并且开始于一小时的第一分钟-结束于一小时的第一分钟,无论如何,以我的计算无论如何,该小时连续一行,分钟数为零。如果您不想看到它,可以轻松过滤掉它。

答案 1 :(得分:0)

您的帖子中还不清楚您要如何处理非零秒分量(舍入和/或截断的组合)。无论如何,一旦达成一整套无矛盾的规则,就可以轻松进行编码。

除此之外,您的问题包括两部分:确定每个ID(每个活动或事件)的正确时数,以及该事件在该小时中的持续时间。在下面的查询中,使用CONNECT BY分层技术,我将小时和持续时间生成为一天到秒的间隔。正如我说的那样,一旦您阐明了舍入规则,就可以将其转换为分钟(0到60之间)。

with
  your_table (id, start_time, end_time) as (
    select 1, timestamp '2019-01-09 17:04:34', timestamp '2019-01-09 19:55:03' 
    from dual union all
    select 2, timestamp '2019-01-09 23:47:01', timestamp '2019-01-10 02:05:03'
    from dual union all
    select 3, timestamp '2019-01-09 18:01:01', timestamp '2019-01-09 18:02:07'
    from dual union all
    select 4, timestamp '2019-01-09 13:00:00', timestamp '2019-01-09 14:00:01'
    from dual
  )
select id,
       trunc(start_time, 'hh') + interval '1' hour * (level - 1) as hr,
       case when level = 1 and connect_by_isleaf = 1 
              then end_time - start_time
            when level = 1                           
              then trunc(start_time, 'hh') + interval '1' hour - start_time
            when connect_by_isleaf = 1 
              then end_time - trunc(end_time, 'hh')
            else interval '1' hour
       end  as duration
from   your_table
connect by trunc(start_time, 'hh') + interval '1' hour * (level - 1) < end_time
       and prior id = id
       and prior sys_guid() is not null
;

输出

        ID HR                  DURATION           
---------- ------------------- -------------------
         1 2019-01-09 17:00:00 +00 00:55:26.000000
         1 2019-01-09 18:00:00 +00 01:00:00.000000
         1 2019-01-09 19:00:00 +00 00:55:03.000000
         2 2019-01-09 23:00:00 +00 00:12:59.000000
         2 2019-01-10 00:00:00 +00 01:00:00.000000
         2 2019-01-10 01:00:00 +00 01:00:00.000000
         2 2019-01-10 02:00:00 +00 00:05:03.000000
         3 2019-01-09 18:00:00 +00 00:01:06.000000
         4 2019-01-09 13:00:00 +00 01:00:00.000000
         4 2019-01-09 14:00:00 +00 00:00:01.000000