我该如何在TimescaleDB中高效地查询“每日值变化量”?

4

问题

我有一个使用PostgreSQL+TimescaleDB的表:

create table heatingElectricityMeter(
    time timestamptz(0) not null,
    import real not null
);

select create_hypertable('heatingElectricityMeter', 'time', chunk_time_interval => interval '1 month');

"import" 是自能量计生产以来消耗的总能量。因此,“import”是单调递增的。大约每10秒钟,我会向该表插入一个值(但当前在数据库中每10秒钟不到一个值)。
现在,我想创建一个条形图,显示过去X天每个本地日消耗的能量。比如说...最近90天。
我已经尝试过的方法:
这是我的第一次尝试:
select
    time_bucket(interval '1 day', time::timestamp) "day",
    first(import, "time"),
    last(import, "time")
from heatingelectricitymeter
where time between '2021-07-28T02:00:00Z' and '2021-10-28T02:00:00Z'
group by "day"
order by 1

这个查询在我的树莓派2上,在查询时间范围内有大约85K个数据点,需要大约2.5秒的时间。虽然不算是“问题”,但速度可以更快。我下一步的尝试:
select
    time_bucket(interval '1 day', time::timestamp) "day",
    max(import) - min(import)
from heatingelectricitymeter
where time between '2021-07-28T02:00:00Z' and '2021-10-28T02:00:00Z'
group by "day"
order by 1

效果应该是一样的,我也期望它表现出相同的效果。然而,令我惊讶的是,它要快得多:只需要大约1.5秒。
(旁注:不将其转换为::timestamp可以将查询加速到1.7秒和1.0秒。但是,我最终想在本地时间内使用午夜到午夜,如果我正确理解手册,那么我将需要进行此转换)
现在有一件事是文档说

last和first命令不使用索引,而是通过它们的组执行顺序扫描。

这并不能真正回答为什么minmax更快的原因。它们也不能使用索引。同时,使用索引似乎是有道理的。我们只需要每个组的第一个和最后一个值。
我的下一个尝试是直接收集所需的值,而不使用分组。
select "day", "import" - lag("import") over (order by "day") from (
    select ('2021-10-28'::date - v) "day"
    from generate_series(0, 90) s(v)
)x
join lateral (
    select time, import from heatingElectricityMeter where time >= day order by time limit 1
)y on true

哇!只用了0.7秒!

结果并不完全与min/max/first/last相同,因为在此查询中,我将始终将每天的第一个值与前一天的第一个值进行比较。但在我看来,这实际上更好,因为数据可能会落在错误的日期,但不会丢失。(在完美的世界中,我会在每天凌晨准确获得插值值,但出于简单起见,我将推迟这一点。对于我的用例,我可以接受轻微的不准确性)。

然而,感觉我以一种过于复杂的方式做到了这一点。在我看来,这似乎是使用时间数据库完成的完全正常的任务。是否有更简单有效的方法来执行此查询?


你为什么认为前两个查询的效果应该是相同的?即first/lastmin/max在做不同的事情。 - TmTron
1
@TmTron:在问题的开头,我解释了“import”是单调递增的。这是有保证的,因为计数器在物理上无法向后计数。因此,“first”值必须始终是“min”值,同样适用于“last”和“max”。 - yankee
大家好,我们实际上已经更新了一些东西,现在有更好的东西可用了,我可能会修改这个并将答案移动到这里,但我在我们的论坛上回答了一个问题,这可能对这里有用:https://www.timescale.com/forum/t/dear-eon-measuring-deltas-correctly-for-energy-meters/1266 - davidk
1个回答

1
有时你需要进行一些优化工作,横向查询将使用索引,并且只需要在一个侧面进行索引扫描,然后滞后将获取由索引扫描查找的上一个值。
请注意,最后一个查询结果稍有不同,而不是在给定日期获取最小值和最大值,而是从当前日期的最后一个值中减去前一天的最后一个值,这是略微不同的事情。只要你能接受这个结果,就没问题了。
现在,“min”和“max”实际上可以使用索引,它们的优化方式不同,可以根据你拥有的索引来进行索引扫描,因此可能是为什么速度更快的原因。
我不确定这是否足够好的答案或者是否解释得足够清楚,但我认为这就是这里发生的事情。
注:我们在TimescaleDB中添加了一些功能来解决这个问题,如果你感兴趣,建议阅读我们论坛中有关此问题的文章:https://www.timescale.com/forum/t/dear-eon-measuring-deltas-correctly-for-energy-meters/1266

谢谢您的回答。是的,我知道最后一个查询结果会略有不同。我编辑了我的问题,明确提到了这一点。我没有创建任何索引。Timescale会自动为“时间”创建索引,但其他内容则没有(据我所知)。因此,我认为min/max在这里无法使用任何索引。我想我理解了正在发生的事情,但实际上我正在寻求一个更简单和更快速的解决方案。 - yankee
一个连续的聚合可能会最大限度地加快速度。如果有帮助的话?https://docs.timescale.com/timescaledb/latest/how-to-guides/continuous-aggregates/about-continuous-aggregates/#about-continuous-aggregates - davidk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接