在PostgreSQL中,如何将最后一个id插入到表中?

在MS SQL中有SCOPE_IDENTITY()。

请不要建议我使用这样的东西:

select max(id) from table

当前回答

(tl;dr: goto选项3:INSERT with RETURNING)

回想一下,在postgresql中,表没有“id”概念,只有序列(通常但不一定用作代理主键的默认值,具有SERIAL伪类型)。

如果你想获取新插入行的id,有以下几种方法:


选项1:CURRVAL(<序列名>);

例如:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval('persons_id_seq');

序列的名字必须知道,这是任意的;在这个例子中,我们假设表persons有一个id列是用SERIAL伪类型创建的。为了避免依赖于此,并且感觉更干净,您可以使用pg_get_serial_sequence代替:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval(pg_get_serial_sequence('persons','id'));

注意:currval()只在同一个会话中的INSERT(已经执行了nextval())之后生效。


选项 2: LASTVAL();

这与前一个类似,只是您不需要指定序列名称:它查找最近修改的序列(总是在会话中,与上面相同的警告)。


CURRVAL和LASTVAL都是完全并行安全的。PG中的序列行为被设计成不同的会话不会干扰,因此不存在竞争条件的风险(如果另一个会话在我的INSERT和SELECT之间插入另一行,我仍然得到正确的值)。

然而,他们确实有一个微妙的潜在问题。如果数据库有一些TRIGGER(或RULE),在插入到persons表时,会在其他表中进行一些额外的插入……那么LASTVAL可能会给我们错误的值。如果对同一个人员表进行额外的插入,甚至在CURRVAL中也会出现这个问题(这种情况不太常见,但风险仍然存在)。


选项3:插入与返回

INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John') RETURNING id;

这是最干净、有效和安全的获取id的方法。它没有之前的任何风险。

缺点呢?几乎没有:你可能需要修改你调用INSERT语句的方式(在最坏的情况下,也许你的API或DB层不期望INSERT返回一个值);它不是标准SQL(谁在乎呢);它在Postgresql 8.2(2006年12月…)


结论:如果可以的话,选择第三种方法。其他地方,首选1。

注意:如果你想全局获取最后插入的id(不一定是通过会话),那么所有这些方法都是无用的。为此,必须使用SELECT max(id) FROM表(当然,这不会从其他事务中读取未提交的插入)。

相反,你不应该使用SELECT max(id) FROM表,而不是上面的3个选项之一,来获得你的INSERT语句生成的id,因为(除了性能)这不是并发安全的:在你的INSERT和SELECT之间,另一个会话可能插入了另一条记录。

其他回答

Postgres有一个内置的机制,可以在同一个查询中返回id或任何您希望查询返回的内容。 这里有一个例子。假设您创建了一个有两列columnn1和column2的表,并且希望在每次插入后返回columnn1。

# create table users_table(id serial not null primary key, name character varying);
CREATE TABLE
#insert into users_table(name) VALUES ('Jon Snow') RETURNING id;
 id 
----
  1
(1 row)

# insert into users_table(name) VALUES ('Arya Stark') RETURNING id;
 id 
----
  2
(1 row)

你可以在INSERT语句中使用return子句,如下所示

wgzhao=# create table foo(id int,name text);
CREATE TABLE
wgzhao=# insert into foo values(1,'wgzhao') returning id;
 id 
----
  1
(1 row)

INSERT 0 1
wgzhao=# insert into foo values(3,'wgzhao') returning id;
 id 
----
  3
(1 row)

INSERT 0 1

wgzhao=# create table bar(id serial,name text);
CREATE TABLE
wgzhao=# insert into bar(name) values('wgzhao') returning id;
 id 
----
  1
(1 row)

INSERT 0 1
wgzhao=# insert into bar(name) values('wgzhao') returning id;
 id 
----
  2
(1 row)

INSERT 0 

(tl;dr: goto选项3:INSERT with RETURNING)

回想一下,在postgresql中,表没有“id”概念,只有序列(通常但不一定用作代理主键的默认值,具有SERIAL伪类型)。

如果你想获取新插入行的id,有以下几种方法:


选项1:CURRVAL(<序列名>);

例如:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval('persons_id_seq');

序列的名字必须知道,这是任意的;在这个例子中,我们假设表persons有一个id列是用SERIAL伪类型创建的。为了避免依赖于此,并且感觉更干净,您可以使用pg_get_serial_sequence代替:

  INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John');
  SELECT currval(pg_get_serial_sequence('persons','id'));

注意:currval()只在同一个会话中的INSERT(已经执行了nextval())之后生效。


选项 2: LASTVAL();

这与前一个类似,只是您不需要指定序列名称:它查找最近修改的序列(总是在会话中,与上面相同的警告)。


CURRVAL和LASTVAL都是完全并行安全的。PG中的序列行为被设计成不同的会话不会干扰,因此不存在竞争条件的风险(如果另一个会话在我的INSERT和SELECT之间插入另一行,我仍然得到正确的值)。

然而,他们确实有一个微妙的潜在问题。如果数据库有一些TRIGGER(或RULE),在插入到persons表时,会在其他表中进行一些额外的插入……那么LASTVAL可能会给我们错误的值。如果对同一个人员表进行额外的插入,甚至在CURRVAL中也会出现这个问题(这种情况不太常见,但风险仍然存在)。


选项3:插入与返回

INSERT INTO persons (lastname,firstname) VALUES ('Smith', 'John') RETURNING id;

这是最干净、有效和安全的获取id的方法。它没有之前的任何风险。

缺点呢?几乎没有:你可能需要修改你调用INSERT语句的方式(在最坏的情况下,也许你的API或DB层不期望INSERT返回一个值);它不是标准SQL(谁在乎呢);它在Postgresql 8.2(2006年12月…)


结论:如果可以的话,选择第三种方法。其他地方,首选1。

注意:如果你想全局获取最后插入的id(不一定是通过会话),那么所有这些方法都是无用的。为此,必须使用SELECT max(id) FROM表(当然,这不会从其他事务中读取未提交的插入)。

相反,你不应该使用SELECT max(id) FROM表,而不是上面的3个选项之一,来获得你的INSERT语句生成的id,因为(除了性能)这不是并发安全的:在你的INSERT和SELECT之间,另一个会话可能插入了另一条记录。

我在使用Java和Postgres时遇到了这个问题。 我通过更新一个新的Connector-J版本来解决这个问题。

postgresql - 9.2 - 1002. - jdbc4.jar

https://jdbc.postgresql.org/download.html: 版本42.2.12

https://jdbc.postgresql.org/download/postgresql-42.2.12.jar

参见INSERT语句的RETURNING子句。基本上,INSERT可兼做查询,并返回插入的值。