RETURNING必须作为INSERT/UPDATE/DELETE的子句紧随其后,不可单独执行;它支持返回多列、表达式及函数结果,批量插入时返回完整结果集,应用层须完整读取而非仅取首行。
很多人写完 INSERT INTO users (name) VALUES ('Alice'),再另起一行写 RETURNING id,结果报错 ERROR: syntax error at or near "RETURNING"。这是因为 RETURNING 不是独立语句,它必须作为 INSERT(或 UPDATE/DELETE)的子句存在,中间不能换行或加分号隔开。
正确写法只有一条语句:
INSERT INTO users (name) VALUES ('Alice') RETURNING id;
常见错误场景还包括在 ORM 中手动拼 SQL 时漏掉绑定,或者误以为它像 MySQL 的 LAST_INSERT_ID() 可事后调用。
不只是主键 ID,RETURNING 能返回插入行的任意列,包括默认值生成的字段(如 created_at)、表达式(如 id * 2),甚至 now() 这类函数(会再次求值,不是插入时的快照)。
RETURNING id, name, created_at
RETURNING id, UPPER(name) AS upper_name
DEFAULT now(),RETURNING created_at 拿到的是实际插入时的值;但 RETURNING now() 拿到的是语句执行时刻的时间,二者可能不同用 INSERT ... VALUES (...), (...), (...) 或 INSERT ... SELECT 批量插入时,RETURNING 不只返回一行,而是返回一个结果集,每行对应一条插入记录。这点和 MySQL 的单值 LAST_INSERT_ID() 有本质区别。
例如:
INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Charlie') RETURNING id, name;
会返回三行,id 是按插入顺序分配的自增 ID(除非显式指定)。如果业务需要一一对应,别依赖“第一条就是第一个”,而应直接消费整个结果集。
注意:若使用 ON CONFLICT DO NOTHING,被忽略的冲突行不会出现在 RETURNING 结果中;但 ON CONFLICT DO UPDATE RETURNING 会返回更新后的行(含新值)。
很多开发者用 Python 的 cursor.execute() 执行带 RETURNING 的语句后,直接调 cursor.fetchone() 就认为完事了——这在单行插入时碰巧有效,但一旦改成批量插入,就会丢掉后续所有 ID。
cursor.fetchall() 或遍历 cursor
result.rows 是数组,不要只取 result.rows[0]
rows := pgx.QueryRows() 需循环 rows.Next() 读取全部更隐蔽的问题是:某些 ORM(如 SQLAlchemy 的 execute())默认不暴露 RETURNING 结果,需显式启用 returning=True 或用 insert().returning() 构造对象。
最易被忽略的一点:事务回滚时,RETURNING 返回的 ID 已被消耗,即使最终没提交,该 ID 也不会复用——PostgreSQL 的序列是预分配且不可逆的。