Postgresql
如何在 Postgres 的主鍵上處理 UPDATE 中的重複項?
讓我們假設一個場景,其中包含實體
Person
和Company
一個表格,該表格PersonCompanyStocks
模擬了一個人擁有某家公司的股票數量(N:M 基數)。例如:person | company | num_stocks ----------------------------- Alice | foo | 300 Bob | foo | 100 Bob | bar | 200
該表用
(person, company)
作主鍵來保證唯一的條目,以及各個個人/公司表的外鍵(為簡單起見,我只使用字元串 ID)。現在讓我們假設 company
bar
收購 companyfoo
。我們希望以如下方式更新表:person | company | num_stocks ----------------------------- Alice | bar | 300 Bob | bar | 300
僅查看
Alice
’s 記錄建議使用天真的方法,例如:UPDATE PersonCompanyStocks SET company = "bar" WHERE company = "foo"
但是,此更新失敗,
duplicate key value violates unique constraint ...
因為Bob
已經有一行帶有 key("Bob", "bar")
。ForINSERT
的 Postgres 支持ON CONFLICT DO ...
,但似乎沒有對應的 forUPDATE
。顯然,我們還必須處理正確合併num_stock
兩行的值。解決這個問題的最佳策略是什麼?我只看到一個相對難看的解決方案:
- 一個查詢來確定重複項。
- 一個
UPDATE
將重複項合併到最後一行。- 一個
DELETE
刪除有問題的重複項。- 以上
UPDATE
是在沒有重複的行中進行重命名。這感覺很複雜,並且可能容易出現競爭條件。Postgres 是否提供任何技巧來更優雅地解決這個問題?
我們需要在公司之間轉移股票,對吧?這意味著我們需要向現有使用者添加股票並為新使用者更換公司。或者,同樣的,我們可以刪除公司“foo”的所有股票和公司“
insert .. on conflict
bar”的新行with rows as ( delete from PersonCompanyStocks where company = 'foo' returning person, num_stocks ) insert into PersonCompanyStocks (person, company, num_stocks) select person, 'bar', num_stocks from rows on conflict(person,company) do update set num_stocks = PersonCompanyStocks.num_stocks + excluded.num_stocks;
事務性的,由於刪除期間的行鎖定,這裡沒有競爭條件。