使用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。要求是有效的
由于这个问题已经打开,我现在已经完成了大部分工作,虽然我不确定我的方法是否是一个好主意 - 它有点被黑客攻击。
答案 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模型任何行的更改都会写入新的行版本。更新单个列几乎与一次更新行中的每个列一样昂贵。只要你必须更新任何东西,重写行的其余部分几乎不需要任何费用。
详细说明:
如果accounts
和merge_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
的结果动态地迭代到动态建立SET
和WHERE
条款。
答案 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并阅读整个讨论。