【问题标题】:Postgres: upsert a row and update a primary key columnPostgres:插入一行并更新主键列
【发布时间】:2017-08-23 13:26:17
【问题描述】:

假设我的 Postgres 数据库中有两个表:

create table transactions
(
    id bigint primary key,
    doc_id bigint not null,
    -- lots of other columns...
    amount numeric not null
);

-- same columns
create temporary table updated_transactions
(
    id bigint primary key,
    doc_id bigint not null,
    -- lots of other columns...
    amount numeric not null
);

两个表都只有一个主键,没有唯一索引。

我需要使用以下规则将行从updated_transactions 插入transactions

  • transactionsupdated_transactions 中的 id 列值不匹配
  • doc_id 等其他列(amount 除外)应匹配
  • 找到匹配行时,更新amountid
  • 当没有找到匹配的行时,插入它

updated_transactions 中的id 值取自序列。 一个业务对象只是填充updated_transactions 然后合并 使用 upsert 查询将新的或更新的行从它添加到 transactions。 所以我的旧交易保持不变ids 和更新的 被分配了新的ids。

在 MSSQL 和 Oracle 中,这将是一个类似于此的 merge 语句:

merge into transactions t
using updated_transactions ut on t.doc_id = ut.doc_id, ...
when matched then
    update set t.id = ut.id, t.amount = ut.amount
when not matched then
    insert (t.id, t.doc_id, ..., t.amount)
    values (ut.id, ut.doc_id, ..., ut.amount);

在 PostgreSQL 中,我想应该是这样的:

insert into transactions(id, doc_id, ..., amount)
select coalesce(t.id, ut.id), ut.doc_id, ... ut.amount
from updated_transactions ut
left join transactions t on t.doc_id = ut.doc_id, ....
    on conflict
    on constraint transactions_pkey
    do update
    set amount = excluded.amount, id = excluded.id

问题在于do update 子句:excluded.id 是旧值 来自transactions 表,而我需要来自updated_transactions 的新值。

ut.id 值对于 do update 子句是不可访问的,我唯一能做的 使用的是excluded 行。但是excluded 行只有coalesce(t.id, ut.id) 返回现有行的旧 id 值的表达式。

是否可以使用 upsert 查询同时更新 idamount 列?

【问题讨论】:

  • other columns like doc_id, etc (except of the amount) should match 听起来像是我的候选键。
  • doc_id 和其他列的值,除了数量,不是唯一的。我简化了问题中的设置,使我的示例查询更易于理解。在我的真实案例中,我必须添加一个 row_number() over (partition by doc_id, ... order by id) 来匹配行。
  • 在这种情况下,如果不回退到id,您将无法执行更新。
  • 我仍然希望有办法做到这一点。实际上,我看到了一个丑陋的解决方法,将一个未使用的new_id 列添加到transactions 并使用excluded.new_idupdated_transactions 获得id。但我相信这是可以避免的。
  • when a matching row is found, update both amount and id columns 如果多个记录匹配怎么办? (你没有唯一匹配,因为你没有其他唯一键 tha id

标签: sql postgresql merge upsert


【解决方案1】:

在您用作键的那些列上创建唯一索引,并在您的 upsert 表达式中传递其名称,以便它使用它而不是 pkey。 然后,如果没有找到匹配项,它将使用来自updated_transactions 的 ID 插入行。如果找到匹配项,则可以使用excluded.id 从updated_transactions 获取ID。

我认为left join transactions 是多余的。

所以它看起来有点像这样:

insert into transactions(id, doc_id, ..., amount)
select ut.id, ut.doc_id, ... ut.amount
from updated_transactions ut
    on conflict
    on constraint transactions_multi_column_unique_index
    do update
    set amount = excluded.amount, id = excluded.id

【讨论】:

  • 感谢 Łukasz 的帮助。你是对的,这与 Wildplasser 在他的 cmets 中建议的基本相同。不幸的是,我无法在这些列上创建唯一索引,也许我应该在我的问题中明确说明。
【解决方案2】:

看起来该任务可以使用writable CTEs 而不是普通的 upsert 来完成。

首先,我将发布更简单的查询版本,以回答原始问题。此解决方案假定 doc_id, unit_id 列寻址候选键,但不需要这些列上的唯一索引。

测试数据:

create temp table transactions
(
    id bigint primary key,
    doc_id bigint,
    unit_id bigint,
    amount numeric
);

create temp table updated_transactions
(
    id bigint primary key,
    doc_id bigint,
    unit_id bigint,
    amount numeric
); 

insert into transactions(id, doc_id, unit_id, amount)
values (1, 1, 1, 10), (2, 1, 2, 15), (3, 1, 3, 10);

insert into updated_transactions(id, doc_id, unit_id, amount)
values (6, 1, 1, 11), (7, 1, 2, 15), (8, 1, 4, 20); 

updated_transactions 合并到transactions 的查询:

with new_values as
(
    select ut.id new_id, t.id old_id, ut.doc_id, ut.unit_id, ut.amount 
    from updated_transactions ut
    left join transactions t 
        on t.doc_id = ut.doc_id and t.unit_id = ut.unit_id
),
updated as
(
    update transactions tr
    set id = nv.new_id, amount = nv.amount
    from new_values nv
    where id = nv.old_id
    returning tr.*
)
insert into transactions(id, doc_id, unit_id, amount)
select ut.new_id, ut.doc_id, ut.unit_id, ut.amount
from new_values ut
where ut.new_id not in (select id from updated);

结果:

select * from transactions

-- id | doc_id | unit_id | amount
------+--------+---------+-------
--  3 |   1    |    3    |  10    -- not changed
--  6 |   1    |    1    |  11    -- updated
--  7 |   1    |    2    |  15    -- updated 
--  8 |   1    |    4    |  20    -- inserted

在我的实际应用程序中,doc_id, unit_id 并不总是唯一的,因此它们不代表候选键。为了匹配行,我考虑了行号,为按ids 排序的行计算。所以这是我的第二个解决方案。

测试数据:

-- the tables are the same as above
insert into transactions(id, doc_id, unit_id, amount)
values (1, 1, 1, 10), (2, 1, 1, 15), (3, 1, 3, 10);

insert into updated_transactions(id, doc_id, unit_id, amount)
values (6, 1, 1, 11), (7, 1, 1, 15), (8, 1, 4, 20); 

合并查询:

with trans as
(
    select id, doc_id, unit_id, amount,
        row_number() over(partition by doc_id, unit_id order by id) row_num
    from transactions
),
updated_trans as
(
    select id, doc_id, unit_id, amount,
        row_number() over(partition by doc_id, unit_id order by id) row_num
    from updated_transactions
),
new_values as
(
    select ut.id new_id, t.id old_id, ut.doc_id, ut.unit_id, ut.amount 
    from updated_trans ut
    left join trans t 
        on t.doc_id = ut.doc_id and t.unit_id = ut.unit_id and t.row_num = ut.row_num
),
updated as
(
    update transactions tr
    set id = nv.new_id, amount = nv.amount
    from new_values nv
    where id = nv.old_id
    returning tr.*
)
insert into transactions(id, doc_id, unit_id, amount)
select ut.new_id, ut.doc_id, ut.unit_id, ut.amount
from new_values ut
where ut.new_id not in (select id from updated);

结果:

select * from transactions;

-- id | doc_id | unit_id | amount
------+--------+---------+-------
--  3 |   1    |    3    | 10     -- not changed
--  6 |   1    |    1    | 11     -- updated
--  7 |   1    |    1    | 15     -- updated
--  8 |   1    |    4    | 20     -- inserted

参考资料:

【讨论】:

  • 如果只有on t.doc_id = ut.doc_id and t.unit_id = ut.unit_id 会处理候选/自然键,这将是正确的解决方案。 (事实并非如此,请参阅 cmets)
  • 是的,我真正的解决方案更复杂,因为我需要解决可能的重复问题,如 cmets 中所述。但这不是原始问题的一部分。也许我还应该在我的答案中包含复杂的查询。
  • 我添加了第二个考虑到重复项的查询,如 cmets 中所述。感谢您的反馈。
猜你喜欢
  • 1970-01-01
  • 2012-01-18
  • 1970-01-01
  • 2019-08-07
  • 2018-04-03
  • 1970-01-01
  • 2016-04-23
  • 2023-03-25
  • 1970-01-01
相关资源
最近更新 更多