2017-03-29 52 views
0

假設我有我的Postgres數據庫的兩個表:Postgres的:UPSERT行和更新的主鍵列

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 
); 

兩個表都有隻是一個主鍵,沒有唯一索引。

我需要使用以下規則UPSERT從updated_transactions行到transactions:在transactions

  • ID列值updated_transactions不匹配像doc_id等(除amount的)
  • 其他列應匹配
  • 當找到匹配行時,更新amountid
  • 當matc沒有找到行,插入它

idupdated_transactions中的值取自一個序列。 業務對象只填充updated_transactions,然後使用upsert查詢將 新行或更新的行合併到transactions中。 所以我舊的不變的交易保持其id完好,並且更新的 被分配新的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.idtransactions表中的舊值 ,而我需要updated_transactions的新值。

ut.iddo update條款無法訪問該值,並且我可以使用的唯一一條 行是excluded行。但excluded行只有coalesce(t.id, ut.id) 表達式,它返回現有行的舊id值。

是否可以使用upsert查詢更新idamount列?

+0

'等欄目像DOC_ID等(除量)應該match'聽起來像一個候選鍵給我。 – wildplasser

+0

doc_id和其他列的值(數量除外)不唯一。我簡化了我的問題中的設置,使我的示例查詢更容易理解。在我的真實情況下,我必須添加一個'row_number()(通過doc_id分區,...按ID排序)'來匹配行。 – yallie

+0

在這種情況下,您不能執行更新而不回退到'id'。 – wildplasser

回答

0

貌似任務可以使用的替代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_transactionstransactions

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並不總是唯一的,所以它們不代表候選鍵。爲了匹配行,我考慮了行號,按行id s排序。所以這是我的第二個解決方案。

測試數據:

-- 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 

參考文獻:

+0

如果只有'on t.doc_id = ut.doc_id和t.unit_id = ut.unit_id'會解決候選/自然鍵,這將是正確的解決方案。 (它不是,請參閱評論) – joop

+0

是的,我的真實解決方案更復雜,因爲我需要解決可能的重複問題,如評論中所述。但這不是原始問題的一部分。也許我也應該在我的答案中包含複雜的查詢。 – yallie

+0

我已經添加了第二個查詢,其中考慮到了重複項,正如評論中所討論的那樣。感謝您的反饋joop。 – yallie

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 
+0

感謝Łukasz的幫助。你是對的,這與wildplasser在他的評論中提出的基本相同。不幸的是,我無法在這些列上創建唯一的索引,也許我應該在我的問題中明確地指出它。 – yallie