如何在PostgreSQL中用SQL RETURNING子句获取刚插入行的自增ID?

作者:袖梨 2026-06-24
RETURNING必须作为INSERT/UPDATE/DELETE的子句紧随其后,不可单独执行;它支持返回多列、表达式及函数结果,批量插入时返回完整结果集,应用层须完整读取而非仅取首行。

RETURNING 必须跟在 INSERT 之后,不能单独执行

很多人写完 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() 可事后调用。

RETURNING 可返回多列、表达式甚至函数结果

不只是主键 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 时 RETURNING 返回全部插入行的结果集

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 会返回更新后的行(含新值)。

在应用代码里获取 RETURNING 结果要读取完整结果集,不能只取第一行

很多开发者用 Python 的 cursor.execute() 执行带 RETURNING 的语句后,直接调 cursor.fetchone() 就认为完事了——这在单行插入时碰巧有效,但一旦改成批量插入,就会丢掉后续所有 ID。

  • Python + psycopg2:必须用 cursor.fetchall() 或遍历 cursor
  • Node.js + pg:result.rows 是数组,不要只取 result.rows[0]
  • Go + pgx:rows := pgx.QueryRows() 需循环 rows.Next() 读取全部

更隐蔽的问题是:某些 ORM(如 SQLAlchemy 的 execute())默认不暴露 RETURNING 结果,需显式启用 returning=True 或用 insert().returning() 构造对象。

最易被忽略的一点:事务回滚时,RETURNING 返回的 ID 已被消耗,即使最终没提交,该 ID 也不会复用——PostgreSQL 的序列是预分配且不可逆的。

相关文章

精彩推荐