【问题标题】:Postgres upsert without incrementing serial IDs?Postgres upsert 不增加序列号?
【发布时间】:2020-12-20 06:15:44
【问题描述】:

考虑下表:

CREATE TABLE key_phrase(
    id SERIAL PRIMARY KEY NOT NULL,
    body TEXT UNIQUE
)

我想做以下事情:

  1. 使用给定的body 创建一条记录如果它尚不存在。
  2. 返回新创建记录的id,如果没有创建新记录,则返回现有记录的id
  3. 确保序列id在冲突时递增。

我尝试了几种方法,最简单的包括DO NOTHING的基本用法:

INSERT INTO key_phrase(body) VALUES ('example') ON CONFLICT DO NOTHING RETURNING id

但是,如果创建了新记录,这只会返回 id

我还尝试了以下方法:

WITH ins AS (
    INSERT INTO key_phrase (body)
    VALUES (:phrase)
    ON     CONFLICT (body) DO UPDATE
    SET    body = NULL
    WHERE  FALSE     
    RETURNING id
)
SELECT id FROM ins
UNION  ALL
SELECT id FROM key_phrase
WHERE  body = :phrase
LIMIT  1;

这将返回新创建记录的 id 或现有记录的 id。但是,它会导致串行主节点发生碰撞,从而在创建新记录时产生间隙。

那么如何执行满足前面提到的 3 个要求的条件插入(upsert)?

【问题讨论】:

  • 无论你尝试什么,使用连续剧总是会给你一个机会在编号中出现漏洞。没有办法避免这种情况,除非您继续检查和更改底层序列。来自手册:因为 smallserial、serial 和 bigserial 是使用序列实现的,所以即使没有删除任何行,列中出现的值序列也可能存在“漏洞”或间隙。

标签: sql postgresql sql-update sql-insert knex.js


【解决方案1】:

我怀疑您正在寻找类似的东西:

with 
    data as (select :phrase as body),
    ins as (
        insert into key_phrase (body)
        select body 
        from data d
        where not exists (select 1 from key_phrase kp where kp.body = d.body)
        returning id
    )
select id from ins
union all
select kp.id 
from key_phrase kp
inner join data d on d.body = kp.body

与原始代码的主要区别在于,它使用not exists 来跳过已插入的短语,而不是on conflict。我将参数的声明移至 CTE 以使事情更容易理解,但不一定非要这样,我们可以这样做:

with 
    ins as (
        insert into key_phrase (body)
        select body 
        from (values(:phrase)) d(body)
        where not exists (select 1 from key_phrase where body = :phrase)
        returning id
    )
select id from ins
union all
select kp.id from key_phrase where body = :phrase

不使用on conflict减少被烧毁的序列数量。然而,应该强调的是,没有办法保证连续剧始终是连续的。由于其他原因可能存在差距。这是设计使然;连续剧的目的是保证唯一性,而不是“顺序性”。如果你真的想要一个没有漏洞的自动增量,可以考虑row_number() 和一个视图。

【讨论】:

  • 感谢您的回答。由于缺少data 声明,第二个查询会出错,不是吗?我已经测试了第一个查询,它似乎“工作”——返回一个 id 无论是否创建了新记录并且序列 ID 没有增加(我知道 Postgres 没有保证顺序ID)。第一个查询究竟是如何工作的?
  • @Orbit:我修复了第二个查询。诀窍是not exists,它在尝试插入之前明确消除重复。
猜你喜欢
  • 1970-01-01
  • 2016-10-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-14
  • 2011-01-18
  • 1970-01-01
  • 1970-01-01
  • 2016-04-11
相关资源
最近更新 更多