如何在PostgreSQL中将状态日志数据聚合成具有相同状态的时间间隔?
Posted
技术标签:
【中文标题】如何在PostgreSQL中将状态日志数据聚合成具有相同状态的时间间隔?【英文标题】:How to aggregate state log data into time intervals with the same state in PostgreSQL? 【发布时间】:2018-08-28 18:20:39 【问题描述】:我有一个看起来像这样的对象状态日志
timestamp, object_id, state, level
2018-01-01 123 f 100
2018-01-02 123 t 100
2018-01-02 123 f 100
2018-01-03 123 f 100
2018-01-03 123 f 100
2018-01-06 123 t 90
2018-01-07 123 t 90
2018-01-08 123 f 90
时间戳实际上是一个完整的日期/时间,为简洁起见,我没有包含时间部分。
我想要的是一个基于独特状态和级别的状态转换列表,看起来像这样
start end object_id, state, level
2018-01-01 2018-01-02 123 f 100
2018-01-02 2018-01-02 123 t 100
2018-01-02 2018-01-06 123 f 100
2018-01-06 2018-01-08 123 t 90
2018-01-08 NOW() 123 f 90
我试图想出一种方法来使用窗口函数来做到这一点,比如
SELECT
timestamp,
object_id,
timestamp as start,
lead(timestamp) OVER (ORDER BY timestamp) as end,
FROM (
SELECT
timestamp,
object_id,
state,
evel,
rank() OVER (PARTITION BY (state, level) ORDER BY timestamp) as rank
FROM state_log AS l
WHERE object_id=123 AND timestamp >= DATE '2018-01-01'
ORDER BY timestamp
) AS states
WHERE rank=1
但我想我不明白 rank() 是如何工作的,它不能满足我的需要。出于某种原因,我认为 rank() 会在每次分区更改时重置行数,但事实并非如此。我怎样才能做到这一点?
【问题讨论】:
你的时间戳有时间分量吗?如果不是,则排序不稳定,这使您的问题无法解决(除非另一列指定了排序)。 是的,它是一个完整的时间戳。没有包含它,因为它使我的帖子过于庞大。 编辑帖子以包含此信息 我不确定要求,但可能有助于您开始:dbfiddle.uk/… 【参考方案1】:这是一个孤岛问题。一个不错的解决方案使用row_number()
:
select object_id, level, state, min(timestamp), max(timestamp)
from (select t.*,
row_number() over (partition by object_id, level order by timestamp) as seqnum,
row_number() over (partition by object_id, level, state order by timestamp) as seqnum_2
from t
) t
group by (seqnum - seqnum_2), object_id, level, state;
很难解释为什么会这样。但是如果你看一下子查询的结果,你会发现在状态不变的情况下,两个seqnum
s 的差值是不变的。这定义了您想要的分组——连同其他列——所以剩下的只是聚合。
这是一个rextester,表明它正在工作。
【讨论】:
让我试试我的数据 不幸的是,它不起作用。返回结果,但在第一行之后,min和max报告相同的时间戳 但是谢谢你的“差距和岛屿”这个名字,至少现在我知道用谷歌搜索什么了 :) @MadWombat 。 . .当我对您问题中的示例数据进行尝试时,它工作正常。最后一行有点不同——我不太确定第二个日期是如何变成“now()”的,但这似乎很小。 那是样本数据。真实数据具有多个对象 ID 和许多不同的状态+级别组合。当我在预期结果中写 now() 时,我的意思是如果对象没有下一个状态,则假设它仍处于上一个状态,并将上一个间隔的结束设置为当前时间。【参考方案2】:这不是关于“差距和岛屿”。此技术由具有某些字段的相应常数值的组操作,但您需要使用此类组的边界进行操作。所以:
create table state_log(timestamp timestamp, object_id int, state boolean, level int);
insert into state_log values
('2018-01-01 00:00:01', 123, 'f', 100),
('2018-01-02 00:00:02', 123, 't', 100),
('2018-01-02 00:00:03', 123, 'f', 100),
('2018-01-03 00:00:04', 123, 'f', 100),
('2018-01-03 00:00:05', 123, 'f', 100),
('2018-01-06 00:00:06', 123, 't', 90),
('2018-01-07 00:00:07', 123, 't', 90),
('2018-01-08 00:00:08', 123, 'f', 90);
select
timestamp::date as start,
coalesce(lead(timestamp) over (order by timestamp), now()::timestamp)::date as end,
object_id, state, level
from (
select
*,
coalesce(lag(state) over (order by timestamp) <> state, true) as is_new_group
from state_log) as t
where
object_id = 123 and timestamp >= date '2018-01-01' and
is_new_group
order by timestamp;
结果(我删除了时间部分以使其更像您在问题中指定的结果):
┌────────────┬────────────┬──────────┬───────┬─── ────┐ │ start │ end │ object_id │ state │ level │ ├────────────┼────────────┼────────────┼────────┼─── ────┤ │ 2018-01-01 │ 2018-01-02 │ 123 │ f │ 100 │ │ 2018-01-02 │ 2018-01-02 │ 123 │ t │ 100 │ │ 2018-01-02 │ 2018-01-06 │ 123 │ f │ 100 │ │ 2018-01-06 │ 2018-01-08 │ 123 │ t │ 90 │ │ 2018-01-08 │ 2018-08-30 │ 123 │ f │ 90 │ └────────────┴────────────┴────────────┴────────┴─── ────┘【讨论】:
以上是关于如何在PostgreSQL中将状态日志数据聚合成具有相同状态的时间间隔?的主要内容,如果未能解决你的问题,请参考以下文章
如何在windows的“omnidb”中将csv文件数据导入postgresql
如何在python flask app中将数据从postgresql渲染到csv?