从行之间的差异中获取总计

时间:2019-05-22 12:27:13

标签: sql postgresql

我有一个表,其结构如下:

(
    id SERIAL PRIMARY KEY,
    user_id integer NOT NULL REFERENCES user(id) ON UPDATE CASCADE,
    status text NOT NULL,
    created_at timestamp with time zone NOT NULL,
    updated_at timestamp with time zone NOT NULL
)

示例数据:

"id","user_id","status","created_at","updated_at"
416,38,"ONLINE","2018-08-07 14:40:51.813+00","2018-08-07 14:40:51.813+00"
417,39,"ONLINE","2018-08-07 14:45:00.717+00","2018-08-07 14:45:00.717+00"
418,38,"OFFLINE","2018-08-07 15:43:22.678+00","2018-08-07 15:43:22.678+00"
419,38,"ONLINE","2018-08-07 16:21:30.725+00","2018-08-07 16:21:30.725+00"
420,38,"OFFLINE","2018-08-07 16:49:10.3+00","2018-08-07 16:49:10.3+00"
421,38,"ONLINE","2018-08-08 11:37:53.639+00","2018-08-08 11:37:53.639+00"
422,38,"OFFLINE","2018-08-08 12:29:08.234+00","2018-08-08 12:29:08.234+00"
423,39,"ONLINE","2018-08-14 15:22:00.539+00","2018-08-14 15:22:00.539+00"
424,39,"OFFLINE","2018-08-14 15:22:02.092+00","2018-08-14 15:22:02.092+00"

当我的应用程序上的用户上线时,将插入状态为ONLINE的新行。当他们离线时,将插入状态为OFFLINE的行。还创建了其他条目来记录不同的事件,但是对于此查询,仅OFFLINEONLINE很重要。

我想制作一个图表,显示一个日期范围内一段时间(例如5分钟)内的在线用户总数。如果该时间段内用户在线,则应将其计算在内。

示例:

datetime, count
2019-05-22T12:00:00+0000, 53
2019-05-22T12:05:00+0000, 47
2019-05-22T12:10:00+0000, 49
2019-05-22T12:15:00+0000, 55
2019-05-22T12:20:00+0000, 59
2019-05-22T12:25:00+0000, 56

我可以通过获取日期范围内的所有状态行然后手动进行处理,为单个用户生成类似的图表,但是这种方法无法扩展到所有用户。

我相信可以使用窗口函数来完成此类操作,但是我不确定从哪里开始

1 个答案:

答案 0 :(得分:3)

由于您的问题非常模糊,没有人能真正帮助您100%。好了,您可以结合使用“ with”子句和窗口函数来实现所需的功能。使用“ with”子句,您可以轻松分解小部分的大问题。可能在查询后(不考虑任何性能)可能会有所帮助,您可以用表替换public.tbl_test:

with temp_online as (
    select
    *
    from public.tbl_test
    where public.tbl_test.status ilike 'online'
    order by created_at
),

temp_offline as (
    select
    *
    from public.tbl_test
    where public.tbl_test.status ilike 'offline'
    order by created_at
),

temp_change as (
    select
    * ,
    (
        select temp_offline.created_at from temp_offline where temp_offline.created_at > temp_online.created_at  and temp_offline.user_id = temp_online.user_id order by created_at asc limit 1
    ) as go_offline
    from temp_online
),

temp_result as 
(
select *,
go_offline - created_at as online_duration
from temp_change
),

temp_series as 
(
SELECT (generate_series || ' minute')::interval + '2019-05-22 00:00:00'::timestamp  as temp_date
    FROM generate_series(0, 1440,5)
)

select
 temp_series.temp_date,
(select count(*) from temp_result where temp_result.created_at <=  temp_series.temp_date and temp_result.go_offline >= temp_series.temp_date) as count_users
from 
temp_series