根据更改时间戳进行插入或更新

问题描述 投票:0回答:2

我有一张记录事件的桌子。这些事件将按每个用户每分钟进入系统一次 - 每分钟大约有数十个或数千个。但我不需要保存全部
如果一个事件在前一个事件发生后 90 秒内发生,我想更新上一行。如果已经超过 90 秒,我想插入新行。

示例表:

create table events (
  id serial,
  user_id int references users(id) not null,
  created_at timestamp not null default now(),
  updated_at timestamp not null default now(),
  ...some other event columns here...
);

create index idx_events_user_id_updated_at on events (user_id, updated_at desc);

伪代码类似于:

  1. 获取用户的最后一个事件
  2. 如果
    updated_at
    在最后 90 秒内:
    1. 更新行,包括
      updated_at
      now()
  3. 否则:
    1. 插入新行

有没有办法用单个 Postgres 语句来做到这一点?

我知道

on conflict
,但我认为它不适用于这个用例。
(user_id, updated_at)
could 可以定义为唯一约束,可用于触发
on conflict
,但时间戳是任意的。这些事件“每分钟”发生一次,但不是“恰好”每分钟发生一次(或者甚至“恰好”间隔一分钟,由于网络延迟、服务器延迟等原因,因此使用 90 秒来提供 30 秒的缓冲区)。将时间戳截断为分钟会降低该功能的实用性,因此我不想这样做只是为了更干净地处理更新插入。 我认为最好的选择是创建一个带有 upsert 块的存储过程,即:

sql postgresql upsert exclusion-constraint
2个回答
0
投票

首先假设记录存在并执行

UPDATE
。包含一个 
WHERE

子句,用于检查记录是否在 90 秒前更新。

如果没有任何更新,则意味着之前没有记录或之前的记录更新时间超过 90 秒 - 无论哪种方式,您都需要执行 
INSERT

从客户端的角度来看,只会有一个调用(执行过程),并且大多数时候服务器上只会有一个

UPDATE
,但是有时需要第二个

INSERT

    

有没有办法用单个 Postgres 语句来做到这一点?

0
投票
可以使用 UPSERT 命令来完成。您需要对时间戳范围进行

EXCLUSION

约束:

ALTER TABLE events ADD CONSTRAINT user_90sec EXCLUDE USING gist (user_id WITH =, tsrange(updated_at, updated_at + interval '90 sec') WITH &&);

值得注意的是,这对于 timestamptz 不起作用,因为 

timestamptz + interval
 只是 
STABLE

,而

timestamp + interval
IMMUTABLE
,这是隐含 GiST 索引(或任何与此相关的索引)所需的。
查询:
WITH input_rows(user_id, data) AS (
   VALUES
      (1, 'foo_new')  -- your input here
    , (2, 'bar_new')
    , (3, 'baz_new')
    -- more?
   )
, ins AS (
   INSERT INTO events (user_id, data) 
   SELECT user_id, data FROM input_rows
   ON CONFLICT ON CONSTRAINT user_90sec DO NOTHING
   RETURNING user_id
   )
UPDATE events e
SET    updated_at = LOCALTIMESTAMP
     , data = i.data
FROM   input_rows i
LEFT   JOIN ins USING (user_id)
WHERE  ins.user_id IS NULL
AND    e.user_id = i.user_id
AND    e.updated_at > LOCALTIMESTAMP - interval '90 sec';

小提琴

这假设输入中每个 user_id 最多有

one

行。 并且没有并发、竞争的写入。否则,由于

INSERT

UPDATE

之间不可避免的时间差距,可能会出现竞争条件。 (排除约束不允许

ON CONFLICT ... DO UPDATE
。)
此外,输入中没有不明确的数据类型。参见:

如何在 PostgreSQL 中使用 RETURNING 和 ON CONFLICT?

tsrange

添加生成的列并以此为基础排除约束(及其隐含的 GiST 索引)可能会更快。不过,会使表格行膨胀。参见:

PostgreSQL 中的计算/计算/虚拟/派生列

© www.soinside.com 2019 - 2024. All rights reserved.