不能。PostgreSQL 的 UPDATE RETURNING 默认只返回更新后的值(NEW),不支持直接使用 OLD 引用旧值;需通过 FROM 自连接或 CTE 预先捕获旧值,再在 RETURNING 中显式引用。
UPDATE RETURNING 能否直接返回更新前的旧值?
不能。PostgreSQL 的
子句默认只返回更新后(NEW)的行数据,不提供隐式的
记录别名。想拿到旧值,必须显式地在
语句中引用原表字段——前提是这些字段在
子句中未被修改,或通过子查询/CTE 提前捕获。
用 FROM 子句关联原表获取旧值
最常用且兼容性好的方式:把目标表再以别名形式
一次,用主键或唯一约束条件连接,从而在
中引用原始列。
适用于所有 PostgreSQL 版本(≥ 8.2)
要求更新条件能精确匹配(如主键、唯一索引字段)
注意避免自连接时因 WHERE 条件写错导致多匹配或零匹配
用 CTE 预先 SELECT 旧值再 UPDATE
更清晰、更安全,尤其适合批量更新或多字段依赖场景。CTE 先锁定并读取旧值,再在主 UPDATE 中引用。
避免了
自连接可能引发的计划器误判
支持在
中同时返回旧值、新值、甚至计算字段(如变化量)
注意:CTE 中的
默认不加锁,若需强一致性,应加
为什么不能像触发器里那样用 OLD/NEW?
因为
是 DML 语句,不是触发器上下文。触发器函数内部才可访问
和
伪记录,而普通 SQL 执行层没有该机制。
试图写
会报错:
某些 ORM(如 SQLAlchemy)提供“returning old values”抽象,但底层仍是上述任一手法的封装
若频繁需要旧值,考虑在业务逻辑层先
再
,而非强求单条语句完成
真正要注意的是:当更新涉及表达式(如
)时,旧值只能靠 CTE 或
提前固化,否则在
里读到的仍是更新后的结果。
RETURNINGOLDUPDATESETFROMRETURNINGUPDATE orders
SET status = 'shipped', updated_at = NOW()
FROM orders AS old
WHERE orders.id = old.id AND orders.id = 123
RETURNING old.id, old.status AS old_status, orders.status AS new_status;FROMRETURNINGSELECTFOR UPDATEWITH old_data AS (
SELECT id, status, total FROM orders WHERE id = 123 FOR UPDATE
)
UPDATE orders
SET status = 'shipped', updated_at = NOW()
WHERE id = (SELECT id FROM old_data)
RETURNING
(SELECT status FROM old_data) AS old_status,
orders.status AS new_status,
orders.total;UPDATE ... RETURNINGOLDNEWRETURNING OLD.statusERROR: missing FROM-clause entry for table "old"SELECT ... FOR UPDATEUPDATEcounter = counter + 1FROMRETURNING