如何返回 postgres 中的列的旧值 INSERT ON CONFLICT DO UPDATE?

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

我正在尝试在 postgres 中运行“upsert”,例如:

INSERT INTO my_table (
    id, -- unique key
    name,
    hash
) VALUES (
    '4b544dea-b355-463c-8fba-40c36ac7cb0c',
    'example',
    '0x0123456789'
) ON CONFLICT (
    id
) DO UPDATE SET
    name = 'example',
    hash = '0x0123456789'
RETURNING
    OLD.hash;

我想返回

hash
列的先前值(上面的示例不是有效的查询,因为
OLD
不是有效的表别名)。重要的是,我正在尝试找到一种方法,以不会在负载下引起冲突的方式执行此操作。这可能吗?或者是在事务中进行先读后写的唯一解决方案?

sql postgresql
3个回答
7
投票

如果您愿意更新架构,另一个可能的答案是将以前的值存储在另一列中:

ALTER table my_table add column prev_hash text;

INSERT INTO my_table (
    id, -- unique key
    hash
) VALUES (
    1,
    'a'
) ON CONFLICT (
    id
) DO UPDATE SET
    hash = excluded.hash,
    prev_hash = my_table.hash
RETURNING
    id,
    hash,      -- will be the new hash
    prev_hash  -- will be the old hash

4
投票

我最终找到了一种解决方法,虽然它不能严格保证原子性,但总的来说可能是一个很好的策略,具体取决于您的用例。

INSERT INTO my_table (
    id, -- unique key
    name,
    hash
) VALUES (
    '4b544dea-b355-463c-8fba-40c36ac7cb0c',
    'example',
    '0x0123456789'
) ON CONFLICT (
    id
) DO UPDATE SET
    name = 'example',
    hash = '0x0123456789'
RETURNING
    name,
    hash, -- will be the new hash
    (SELECT hash FROM my_table WHERE my_table.id = '4b544dea-b355-463c-8fba-40c36ac7cb0c') -- will be the old hash
    ;

0
投票

您可以使用 CTE。

如果您需要精确的一列和一行,下面是更简单的方法。

with prev as (
    select name
    from student
    where id=$1
)
insert into student(id, name)
values($1, $2)
on conflict (id) do update
    set name=excluded.name
returning (select name from prev);

根据您的需要,可以使用 2 个 CTE:

with prev as (
    select *
    from student
    where id=$1
), upd as (
  insert into student(id, name)
    values($1, $2)
    on conflict (id) do update
        set name=excluded.name
)
select * from prev;

示例

-- Bootstrap script to create tables and insert some data
create table student (
  id serial primary key,
  name text
);

insert into student(name)
values ('st1'), ('st2');

-- returns 'st1'
with prev as (
    select name
    from student
    where id=1
)
insert into student(id, name)
values(1, 'st1-altered')
on conflict (id) do update
    set name=excluded.name
returning (select name from prev);

-- returns id: 2, name: 'st2'
with prev as (
    select *
    from student
    where id=2
), upd as (
  insert into student(id, name)
    values(2, 'st2-changed')
    on conflict (id) do update
        set name=excluded.name
)
select * from prev;

-- returns nil (since there's no previous value)
with prev as (
    select *
    from student
    where id=3
), upd as (
  insert into student(id, name)
    values(3, 'st3')
    on conflict (id) do update
        set name=excluded.name
)
select * from prev;
  
© www.soinside.com 2019 - 2024. All rights reserved.