获取按事件分组的行值之间的时间差

时间:2014-07-11 13:29:19

标签: sql postgresql

我正在使用Postgres 9.3.3

我有一个包含多个事件的表格,其中两个是" AVAILABLE"和"无法使用"。这些事件将分配给特定对象。此表中还有其他对象ID(为清楚起见,已删除):

enter image description here

我需要的是"可用"每天的时间,类似的东西: enter image description here

3 个答案:

答案 0 :(得分:1)

SQL Fiddle

select
    object_id, day,
    sum(upper(available) - lower(available)) as available
from (
    select
        g.object_id, date_trunc('day', d) as day,
        (
            available *
            tsrange(date_trunc('day', d), date_trunc('day', d)::date + 1, '[)')
        ) as available
    from
        (
            select
                object_id, event,
                tsrange(
                    timestamp,
                    lead(timestamp) over(
                        partition by object_id order by timestamp
                    ),
                    '[)'
                ) as available
            from events
            where event in ('AVAILABLE', 'UNAVAILABLE')
        ) s
        right join
        (
            generate_series(
                (select min(timestamp) from events),
                (select max(timestamp) from events),
                '1 day'
            ) g (d) 
            cross join
            (select distinct object_id from events) s
        ) g on
            tsrange(date_trunc('day', d), date_trunc('day', d)::date + 1, '[)') && available and
            (event = 'AVAILABLE' or event is null) and
            g.object_id = s.object_id
) s
group by 1, 2
order by 1, 2

psql输出

 object_id |         day         | available 
-----------+---------------------+-----------
         1 | 1970-01-02 00:00:00 | 12:00:00
         1 | 1970-01-03 00:00:00 | 12:00:00
         1 | 1970-01-04 00:00:00 | 
         1 | 1970-01-05 00:00:00 | 1 day
         1 | 1970-01-06 00:00:00 | 1 day
         1 | 1970-01-07 00:00:00 | 12:00:00

表DDL

create table events (
    object_id int,
    event text,
    timestamp timestamp
);
insert into events (object_id, event, timestamp) values
(1, 'AVAILABLE', '1970-01-02 12:00:00'),
(1, 'UNAVAILABLE', '1970-01-03 12:00:00'),
(1, 'AVAILABLE', '1970-01-05 00:00:00'),
(1, 'UNAVAILABLE', '1970-01-07 12:00:00');

答案 1 :(得分:1)

您的示例输出表明您希望返回所有对象,但要进行分组。如果是这种情况,此查询可以执行该操作

select object_id, day, sum(upper(tsrange) - lower(tsrange))
from (
  select object_id, date(day) as day, e.tsrange * tsrange(day, day + interval '1' day) tsrange
  from generate_series(timestamp '1970-01-01', '1970-01-07', interval '1' day) day
  left join (
    select object_id,
           case event
             when 'AVAILABLE' then tsrange(timestamp, lead(timestamp) over (partition by object_id order by timestamp))
             else null
           end tsrange
    from events
    where event in ('AVAILABLE', 'UNAVAILABLE')
  ) e on e.tsrange && tsrange(day, day + interval '1' day)
) d
group by object_id, day
order by day, object_id

但是那会产生类似的东西(如果你有多个object_id):

 object_id | day          | sum
-----------+--------------+-----------
           | '1970-01-01' |
     1     | '1970-01-02' | '12:00:00'
     1     | '1970-01-03' | '12:00:00'
           | '1970-01-04' |
     1     | '1970-01-05' | '1 day'
     1     | '1970-01-06' | '1 day'
     2     | '1970-01-06' | '12:00:00'
     1     | '1970-01-07' | '12:00:00'

在我看来,如果你一次只查询一个对象,那就更有意义了:

select day, sum(upper(tsrange) - lower(tsrange))
from (
  select date(day) as day, e.tsrange * tsrange(day, day + interval '1' day) tsrange
  from generate_series(timestamp '1970-01-01', '1970-01-07', interval '1' day) day
  left join (
    select case event
             when 'AVAILABLE' then tsrange(timestamp, lead(timestamp) over (partition by object_id order by timestamp))
             else null
           end tsrange
    from events
    where event in ('AVAILABLE', 'UNAVAILABLE')
    and object_id = 1
  ) e on e.tsrange && tsrange(day, day + interval '1' day)
) d
group by day
order by day

这将输出一些内容,例如:

 day          | sum
--------------+----------
 '1970-01-01' |
 '1970-01-02' | '12:00:00'
 '1970-01-03' | '12:00:00'
 '1970-01-04' |
 '1970-01-05' | '1 day'
 '1970-01-06' | '1 day'
 '1970-01-07' | '12:00:00'

我将这个架构/数据用于我的输出:

create table events (
  object_id int,
  event text,
  timestamp timestamp
);

insert into events (object_id, event, timestamp)
values (1, 'AVAILABLE', '1970-01-02 12:00:00'),
       (1, 'UNAVAILABLE', '1970-01-03 12:00:00'),
       (1, 'AVAILABLE', '1970-01-05 00:00:00'),
       (1, 'UNAVAILABLE', '1970-01-07 12:00:00'),
       (2, 'AVAILABLE', '1970-01-06 00:00:00'),
       (2, 'UNAVAILABLE', '1970-01-06 06:00:00'),
       (2, 'AVAILABLE', '1970-01-06 12:00:00'),
       (2, 'UNAVAILABLE', '1970-01-06 18:00:00');

答案 2 :(得分:0)

这是部分答案。如果我们假设可用后的下一个事件不可用,那么lead()即可开始救援,以下是开始:

select object_id, to_char(timestamp, 'YYYY-MM-DD') as day,
       to_char(nextts - timestamp, 'HH24:MI') as interval
from (select t.*,
             lead(timestamp) over (partition by object_id order by timestamp) as nextts
      from table t
      where event in ('AVAILABLE', 'UNAVAILABLE')
     ) t
where event = 'AVAILABLE'
group by object_id, to_char(timestamp, 'YYYY-MM-DD');
但是,我怀疑,当间隔跨越多天时,您希望将日期分成不同的部分。这变得更具挑战性。