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

时间:2015-01-15 02:53:52

标签: 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.*

这将为我提供与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

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

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

Here on github是我正在做的事情的一个更真实的例子,包含了迄今为止的一些建议。
使用Postgres 9.1,但如果需要可以使用9.4。要求是有效的

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

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

4 个答案:

答案 0 :(得分: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.*;

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

详细说明:

整行的简写

如果accountsmerge_accounts的行类型相同,并且您希望将{em>所有从merge_accounts采用到{{ 1}},有一个比较整个行类型的快捷方式:

accounts

这甚至适用于NULL值。 Details in the manual.
但它的将适用于您的本土解决方案,其中(quote):

  除了所有非pk列都是数组类型

之外,

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.*;是相同的

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

针对您的特殊情况

merge_accounts
如果不应更新的列在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.*中为空,则

m.status IS NOT NULL有效。
如果您使用数组进行操作,请merge_accounts m.status <> '{}'涵盖两个选项

返回旧值和新值:

Jayadevan posted一样,我之前已经回答过这个问题:

答案 1 :(得分:3)

如果您不依赖于更新的副作用,只需更新需要更改的记录

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
 AND NOT (status IS NOT DISTINCT FROM merge_accounts.status 
      AND field1 IS NOT DISTINCT FROM merge_accounts.field1 
      AND field2 IS NOT DISTINCT FROM merge_accounts.field2
      )
RETURNING accounts.* 

答案 2 :(得分:0)

我建议使用information_schema.columns表动态地内省列,然后使用 plpgsql 函数中的列动态生成UPDATE语句。

即。这个DDL:

create table foo
(
  id serial,
  val integer,
  name text
);

insert into foo (val, name) VALUES (10, 'foo'), (20, 'bar'), (30, 'baz');

这个查询:

select column_name
from information_schema.columns
where table_name = 'foo'
order by ordinal_position;

将按照表DDL中定义的顺序生成表的列。

基本上,您可以使用函数中的上述SELECT动态构建UPDATE语句,方法是将SELECT中上述FOR LOOP的结果动态地迭代到动态建立SETWHERE条款。

答案 3 :(得分:0)

这有什么变化吗?

SELECT * FROM old;
 id | val 
----+-----
  1 |   1
  2 |   2
  4 |   5
  5 |   1
  6 |   2

SELECT * FROM new;
 id | val 
----+-----
  1 |   2
  2 |   2
  3 |   2
  5 |   1
  6 |   1

SELECT * FROM old JOIN new ON old.id = new.id;
 id | val | id | val 
----+-----+----+-----
  1 |   1 |  1 |   2
  2 |   2 |  2 |   2
  5 |   1 |  5 |   1
  6 |   2 |  6 |   1
(4 rows)

 WITH sel AS (
               SELECT o.id , o.val  FROM old o JOIN new n ON o.id=n.id  ),
 upd AS (
              UPDATE old  SET val = new.val  FROM new WHERE new.id=old.id RETURNING old.*  )
 SELECT * from sel, upd WHERE sel.id = upd.id AND sel.val <> upd.val;
 id | val | id | val 
----+-----+----+-----
  1 |   1 |  1 |   2
  6 |   2 |  6 |   1
(2 rows)

参考SO answer并阅读整个讨论。