在修改数据的同时,即时获取被操作行的最新属性数据往往极为便利。INSERT、UPDATE、DELETE 以及 MERGE 命令均原生支持可选的 RETURNING 子句。引入 RETURNING 能够省去为了回显数据而额外发起的二次数据库往返查询,并在难以单靠过滤条件重新锁定刚修改行的并发场景下具备不可替代的价值。
RETURNING 子句内部允许编写的内容与普通 SELECT 查询的目标输出列表完全一致(详见 Section 7.3)。它可以包含目标表的任意字段列名,或基于这些列构建的计算表达式。最常见的简写是 RETURNING *,它按物理定义顺序将目标表的全部列原样返回。
在 INSERT 命令中,RETURNING 默认可访问的数据是新插入后的行数据。在传入完全固定常量的简单插入中,这仅仅是重复回显客户端自身传入的值;但在依赖服务端动态生成的计算字段或自增主键时尤为关键。例如当使用 serial 自增序列生成唯一主键时,通过附加 RETURNING 可以在写入完成的瞬间直接将系统新分发的自增 ID 捕获回传给应用层:
CREATE TABLE users (firstname text, lastname text, id serial primary key);
INSERT INTO users (firstname, lastname) VALUES ('Joe', 'Cool') RETURNING id;
在 INSERT ... SELECT 批量转存场景下,RETURNING 同样极为实用。
在 UPDATE 命令中,RETURNING 默认返回的是修改完成之后的行最新内容。例如:
UPDATE products SET price = price * 1.10 WHERE price <= 99.99 RETURNING name, price AS new_price;
在 DELETE 命令中,RETURNING 默认返回的是刚刚被物理抹除的数据行原始内容。例如:
DELETE FROM products WHERE obsoletion_date = 'today' RETURNING *;
在 MERGE 命令中,RETURNING 默认返回的是源表记录行外加对应被插入、更新或删除的目标表行内容。由于源表与目标表通常包含大量同名字段,直接指定 RETURNING * 会导致结果集中出现大量重名字段,因此更实用的做法是附加表名限定,单独返回源表行或目标表行:
MERGE INTO products p USING new_products n ON p.product_no = n.product_no WHEN NOT MATCHED THEN INSERT VALUES (n.product_no, n.name, n.price) WHEN MATCHED THEN UPDATE SET name = n.name, price = n.price RETURNING p.*;
在上述所有命令中,还支持显式分别返回被修改行在变更前后的旧值与新值:
UPDATE products SET price = price * 1.10
WHERE price <= 99.99
RETURNING name, old.price AS old_price, new.price AS new_price,
new.price - old.price AS price_change;
在上述示例中,显式声明 new.price 在取值上等价于直接书写 price,但这种显式对比记法使业务意图更加直观清晰。
这种返回新旧两套值的语法在 INSERT、UPDATE、DELETE 与 MERGE 中全部受到通用支持。在常规语义下,对于纯 INSERT 其旧值自然为 NULL,而对于纯 DELETE 其新值自然为 NULL。但在某些复合分支场景下它们依然拥有独特的价值:例如在带有 ON CONFLICT DO UPDATE 冲突更新子句的 INSERT 中,遭遇唯一键冲突而转入更新的行其旧值将呈现为非 NULL。
如果目标表上绑定了触发器(Chapter 37),RETURNING 能感知并返回经由触发器逻辑加工处理后的最终数据。因此,实时检查由触发器动态计算写入的衍生字段值,构成了 RETURNING 的另一大经典应用场景。