使用 postgres、timescaledb 获取时间戳至少在 5 分钟前的最新行

Posted

技术标签:

【中文标题】使用 postgres、timescaledb 获取时间戳至少在 5 分钟前的最新行【英文标题】:Get most recent row whose timestamp is at least 5 minutes ago using postgres, timescaledb 【发布时间】:2022-01-15 00:34:27 【问题描述】:

我遇到了一些我认为 timescaledb 可以提供帮助的问题。

假设我有这张桌子:

CREATE TABLE purchase
(
    id integer NOT NULL DEFAULT nextval('purchase_id_seq'::regclass),
    "timestamp" timestamp without time zone NOT NULL,
    country character varying(128) COLLATE pg_catalog."default",
    product character varying(128) COLLATE pg_catalog."default",
    quantity numeric(64,32),
    price numeric(64,32)
)

代表购买:

身份证 购买时间戳 发生的国家 正在购买的产品 购买数量 按 1 数量支付的价格

对于每次购买,我要计算(伪代码):

price - last price for given (country, product) where timestamp - timestamp of old record > 5 minutes

例如,如果我有这些购买:

id timestamp              country product quantity price
1  2021-12-09 07:12:11.13 US      apple   1        1.2
2  2021-12-09 07:13:11.13 US      apple   2        1.3
3  2021-12-09 07:19:12.13 US      apple   2        1.4
4  2021-12-09 07:20:19.13 US      apple   2        0.9

然后我会有这些增量

id timestamp              country product quantity price last_price_at_least_five_minutes_ago
1  2021-12-09 07:12:11.13 US      apple   1        1.2   NULL
2  2021-12-09 07:13:11.13 US      apple   2        1.3   NULL
3  2021-12-09 07:17:12.13 US      apple   2        1.4   1.2
4  2021-12-09 07:20:19.13 US      apple   2        0.9   1.3

对于每个 CURRENTROW,询问“具有小于 CURRENTROW 的最高时间戳的行的价格 - '5 分钟' 的最简单方法是什么?

我愚蠢地尝试了这个:

SELECT
    t1.country,
    t1.product,
    t1.timestamp,
    t1.id,
    t1.price,
    t2.id AS last_id,
    t2.timestamp AS last_timestamp,
    t2.price AS last_price
FROM
    purchase t1
LEFT JOIN purchase t2
ON
    t2.timestamp < t1.timestamp - INTERVAL '5m' AND
    t1.country = t2.country AND
    t1.product = t2.product
GROUP BY
    t1.country,
    t1.product,
    t1.id,
    t1.price,
    t1.timestamp,
    t2.id,
    t2.price,
    t2.timestamp

但这会挂起,我确信它做了很多不必要的工作,因为连接的结果集很大(每行 * N,其中 N 是 5m 前的行数)

【问题讨论】:

【参考方案1】:

您也可以使用窗口函数,尽管您必须确定较大数据集上的任何性能指标以及您要查询的范围。

此示例选择分区中的最后一个值(在本例中为按产品),按时间排序 ASC。为了获得至少 5 分钟前的值,您必须设置一个开始时间超过 5 分钟(这使用 UNBOUNDED PRECEDING)和 5 分钟前('5 minutes'::interval PRECEDING)的窗口范围。

使用UNBOUNDED PRECEDING 最终可能会很昂贵,具体取决于您拥有的行数,因此如果您知道/期望您的数据具有某种规律性(例如range between '20 minute'::interval PRECEDING and '5 minute'::interval PRECEDING),您可以设置某种开始间隔

select ts, country, product,quantity,price, 
    last_value(purchase.price) over w as last_price_at_least_five_minutes_ago
from purchase
window w as (partition by product order by ts range between unbounded PRECEDING and '5 minutes'::interval PRECEDING)
order by ts asc;

结果:

ts                     |country|product|quantity|price|last_price_at_least_five_minutes_ago|
-----------------------+-------+-------+--------+-----+------------------------------------+
2021-12-09 07:12:11.130|US     |apple  |     1.0|  1.2|                                    |
2021-12-09 07:13:11.130|US     |apple  |     2.0|  1.3|                                    |
2021-12-09 07:17:12.130|US     |apple  |     2.0|  1.4|                                 1.2|
2021-12-09 07:20:19.130|US     |apple  |     2.0|  0.9|                                 1.3|

【讨论】:

【参考方案2】:

您可以使用标量子查询,字面意思是“具有小于 CURRENTROW 的最高时间戳的行的价格 - 5 分钟”。

select *,      
(
  select price 
  from purchase 
  where product = currentrow.product -- more conditions can be added here
  and "timestamp" < currentrow."timestamp" - interval '5 minutes' -- "less than CURRENTROW - 5 minutes"
  order by "timestamp" desc limit 1 -- "the highest timestamp"
) as last_price_at_least_five_minutes_ago
from purchase as currentrow;

currentrow 不是purchase 表的足够好别名,但很适合子查询的逻辑。

【讨论】:

以上是关于使用 postgres、timescaledb 获取时间戳至少在 5 分钟前的最新行的主要内容,如果未能解决你的问题,请参考以下文章

timescaledb 时序库备份还原 遇到的问题与解决

timescaledb 时序库备份还原 遇到的问题与解决

Centos7 安装 PostgreSql 14 数据库 和 timescaledb 时序库

问题:如何在具有卷的ARM体系结构上在Docker上运行TimeScaleDB?

如何使用 JPQL、Spring Data Repositories 和 Hibernate 为 TimescaleDB `time_bucket` 函数参数化 Postgresql 间隔

TimescaleDB 简单试用