不能。postgresql 的 update returning 默认只返回更新后的值(new),不支持直接使用 old 引用旧值;需通过 from 自连接或 cte 预先捕获旧值,再在 returning 中显式引用。

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










