Postgres 18:RETURNING子句中的OLD和NEW行
Postgres 18 今日发布。在异步I/O和UUIDv7支持等头条功能下方,我们发现了这项实用的小改进:
本次发布新增功能:在INSERT、UPDATE、DELETE和MERGE命令的RETURNING子句中,可同时访问旧值(OLD)和新值(NEW)。
虽然不像异步I/O那样具有颠覆性,但这确实是那些在特定场景下不可或缺的小功能之一。
以下通过UPDATE操作获取所有新旧值的简易演示:
UPDATE fruit
SET quantity = 300
WHERE item = 'Apples'
RETURNING OLD.*, NEW.*;
id | item | quantity | id | item | quantity
----+--------+----------+----+--------+----------
5 | Apples | 200 | 5 | Apples | 300
(1 row)
利用OLD检测更新插入操作中的新行
假设我们正在执行upsert操作,需要区分RETURNING返回的行是新插入还是更新的现有行。虽然以前也能实现,但依赖于对xmax = 0的反直觉检查(见下文最后一行):
INSERT INTO webhook (
id,
data
) VALUES (
@id,
@data
)
ON CONFLICT (id)
DO UPDATE SET id = webhook.id -- force upsert to return a row
RETURNING webhook.*,
(xmax = 0) AS is_new;
该语句依赖于Postgres锁机制实现的特性——新插入行会将xmax设为零(详见完整原理说明)。虽然有效,但并非API的保证行为,随时可能变更。
在 Postgres 18 中,我们可以重构上述实现,使其更易于理解且不依赖具体实现细节。实现也很简单——只需在返回子句中检查 OLD 是否为空:
INSERT INTO webhook (
id,
data
) VALUES (
@id,
@data
)
ON CONFLICT (id)
DO UPDATE SET id = webhook.id -- force upsert to return a row
RETURNING webhook.*,
(OLD IS NULL)::boolean AS is_new;
访问 OLD 和 NEW 无疑还有许多其他实用场景,但这个示例能让我们立即改进 18 版之前的代码。
