返回在UPDATE中实际更改的表的行

Jam*_*ies 6 sql postgresql sql-update

使用Postgres,我可以执行更新语句并返回受推荐影响的行.

UPDATE accounts
SET status = merge_accounts.status,
    field1 = merge_accounts.field1,
    field2 = merge_accounts.field2,
    etc.
FROM merge_accounts WHERE merge_accounts.uid =accounts.uid
RETURNING accounts.*
Run Code Online (Sandbox Code Playgroud)

这将为我提供与该WHERE子句匹配的所有记录的列表,但是不会告诉我操作实际更新了哪些行.

在这个简化的用例中,当然简单地添加另一个防护是微不足道的AND status != 'Closed,但是我的真实世界用例涉及从10,000个行的合并表中更新潜在的几十个字段,我希望能够检测哪些行实际上已经改变了,它们与之前的版本相同.(期望很少的行实际上会改变).

到目前为止我得到的最好的是

UPDATE accounts
SET x=..., y=...
FROM accounts as old WHERE old.uid = accounts.uid
FROM merge_accounts WHERE merge_accounts.uid = accounts.uid
RETURNING accounts, old
Run Code Online (Sandbox Code Playgroud)

这将返回一个新旧行的元组,然后可以在我的Java代码库本身内部进行区分 - 但这需要大量额外的网络流量并且可能容易出错.

理想的情况是能够让postgres返回实际有任何值更改的行 - 这可能吗?

在github上这是一个更真实的例子,我正在做的事情,结合到目前为止的一些建议.
使用Postgres 9.1,但如果需要可以使用9.4.要求是有效的

  • 能够执行新数据的upsert
  • 我们可能只知道要在任何给定行上更新的特定键/值对
  • 获取仅包含upsert实际更改的行的结果
  • 奖金 - 获取旧记录的副本.

由于这个问题已经打开,我现在已经完成了大部分工作,虽然我不确定我的方法是否是一个好主意 - 它有点被黑客攻击.

Erw*_*ter 9

仅更新实际更改的行.

这节省了昂贵的更新昂贵的支票UPDATE.

要使用提供的新值更新每个列(如果有任何更改):

UPDATE accounts a
SET   (status,   field1,   field2)  -- short syntax for  ..
  = (m.status, m.field1, m.field2)  -- .. updating multiple columns
FROM   merge_accounts m
WHERE  m.uid = a.uid
AND   (a.status IS DISTINCT FROM m.status OR
      a.field1 IS DISTINCT FROM m.field1 OR 
      a.field2 IS DISTINCT FROM m.field2)
RETURNING a.*;
Run Code Online (Sandbox Code Playgroud)

由于PostgreSQL的MVCC模型,对行的任何更改都会写入新的行版本.更新单个列几乎与一次更新行中的每个列一样昂贵.只要你需要更新任何东西,重写其余部分几乎不需要任何费用.

细节:

整行的简写

如果行类型的accountsmerge_accounts相同的,并要采取一切merge_accountsaccounts,有比较全行类型的快捷方式:

UPDATE accounts a
SET   (status,   field1,   field2)
  = (m.status, m.field1, m.field2)
FROM   merge_accounts m
WHERE  a.uid = m.uid
AND   m IS DISTINCT FROM a
RETURNING a.*;
Run Code Online (Sandbox Code Playgroud)

这甚至适用于NULL值.手册中的详细信息.
但它不适用于您的本土解决方案(引用):

merge_accounts 除了所有非pk列都是数组类型之外,它们是相同的

它要求行类型兼容,即每个列共享相同的数据类型,或者至少在两种类型之间注册了隐式转换.

为您的特殊情况

UPDATE accounts a
SET   (status, field1, field2)
   = (COALESCE(m.status[1], a.status)  -- default to original ..
   , COALESCE(m.field1[1], a.field1)   -- .. if m.column[1] IS NULL
   , COALESCE(m.field2[1], a.field2))
FROM   merge_accounts m
WHERE  m.uid = a.uid
AND  (m.status[1] IS NOT NULL AND a.status IS DISTINCT FROM m.status[1]
   OR m.field1[1] IS NOT NULL AND a.field1 IS DISTINCT FROM m.field1[1]
   OR m.field2[1] IS NOT NULL AND a.field2 IS DISTINCT FROM m.field2[1])
RETURNING a.*
Run Code Online (Sandbox Code Playgroud)

m.status IS NOT NULL如果不应更新的列为NULL,则可以正常工作merge_accounts.
m.status <> '{}'如果你操作数组.
m.status[1] IS NOT NULL涵盖两种选择.

返回新旧价值:

就像Jayadevan发布的那样,我之前已经回答过:

  • 同一作者的相关答案:https://dba.stackexchange.com/a/118214/154985 (2认同)