PrestoSQL 将事件数据转换为每小时摘要

PrestoSQL transform event data into hourly summary

我有一个 table 包含事件和时间戳

ts event name
1650000000 everything is ok process_a
1650003700 something is broken! process_a
1650007100 everything is ok process_a
1650010000 everything is ok process_b
1650013100 something is broken! process_b
1650017400 everything is ok process_b

我想计算,对于每个进程,每小时有多少百分比的时间“一切正常”以及有多少百分比的时间“有问题!”。最终结果应该是这样的

hour name ok_perc
... ... ...
2022-04-22 7:00:00 process_a .912
2022-04-22 8:00:00 process_a .634
2022-04-22 9:00:00 process_a 1
2022-04-22 1:00:00 process_b .354
2022-04-22 2:00:00 process_b .533
2022-04-22 3:00:00 process_b .987
... ... ...

我已经研究过各种子查询来帮助我达到我想要的目的。我意识到我将要遇到的第一个问题是我不会有所有的时间,只有状态发生变化的时间。所以我创建了:

    select 
        timestamp_column
    from
        (values 
            (sequence(cast('2022-01-01' as timestamp),  --don't bother that this doesn't match my pseudo timestamps in the events table
                      cast(now() as timestamp),
                      interval '1' hour
                     )
            )
        ) as t(timestamp_array)
    cross join 
        unnest(timestamp_array) as t1(timestamp_column)

上面给出了事件将要发生的所有小时间隔。

然后我在 date_truncfrom_unixtime(ts) 上加入了我的事件 table 的每小时时间戳,所以如果那个小时有一个事件,我有其他值空值。请注意,一个小时内可以有多个事件。

出于某种原因,我认为将最后一个事件保留到下一个小时(如果下一个小时的事件为空)是个好主意

coalesce
            (
                event,
                lag(event) ignore nulls over 
                    (
                        partition by name
                        order by timestamp_column
                    )
            )

而且我还认为可以帮助我解决这个问题的其他方法是计算每个事件的持续时间,以这种方式(用前一个事件时间戳减去当前事件时间戳):

        lead(ts) over 
                (
                    partition by 
                        name
                    order by ts
                ) - ts as seconds_in_state

出于某种原因,我认为我会知道在给定的一小时内,我处于一种状态有多少秒,我处于另一种状态有多少秒。但是我的seconds_in_state有时会超过一个小时,这说明我的方向不对。

总的来说,这似乎是一个很常见的问题:如何按特定时间间隔总结事件 table,每个事件是否都有一个事物的隐式状态 name

不知何故,我被困在了这个问题上,我固执地在 Presto SQL 中修复它,而不是下载事件数据并在 Python 中进行一些操作 - 这绝对是可能的!

这是一种可能的方法 - 按名称为分区生成当前时间戳和下一个时间戳之间的每小时间隔,然后使用 unnest 展平生成的数组并使用 group by 名称和小时来执行所需的计算:

-- sample data
WITH dataset (ts, event, name) AS (
    VALUES (1650000000, 'everything is ok', 'process_a'),
      (1650003700, 'something is broken!', 'process_a'),
      (1650007100,  'everything is ok', 'process_a'),
      (1650010000,  'everything is ok', 'process_b'),
      (1650013100,  'something is broken!', 'process_b'),
      (1650017400,  'everything is ok','process_b')
) 

-- query
select name, 
    ts_hour_exp hour, 
    count_if(is_ok) * 1.0 / count(*) ok_perc
from (
        select date_trunc('hour', from_unixtime(ts)) ts_hour,
            if(event = 'everything is ok', true, false) is_ok, -- reduced strings to boolean flag 
            lead(date_trunc('hour', from_unixtime(ts))) over(
                partition by name
                order by ts
            ) next_ts,
            name
        from dataset
    )
cross join unnest ( -- some magic for interval generation
        coalesce(
            array_except(
                sequence(ts_hour, next_ts, interval '1' hour),
                array [ ts_hour, next_ts ] -- exclude borders
            ),
            array [ ] -- in case of null
        ) || ts_hour -- attach current hour
    ) as t(ts_hour_exp)
group by name, ts_hour_exp
order by name, ts_hour_exp

输出:

name hour ok_perc
process_a 2022-04-15 05:00:00.000 1.0
process_a 2022-04-15 06:00:00.000 0.0
process_a 2022-04-15 07:00:00.000 1.0
process_b 2022-04-15 08:00:00.000 0.5
process_b 2022-04-15 09:00:00.000 0.0
process_b 2022-04-15 10:00:00.000 1.0