PostgreSQL函数的最后插入的ID


321

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

在MS SQL中,有SCOPE_IDENTITY()。

请不要建议我使用以下内容:

select max(id) from table

为什么您讨厌max函数?我认为这很简单。有安全性等问题吗?
jeongmin.cha 2016年

9
@ jeongmin.cha问题在于,如果在它们之间还有其他操作,并且有更多插入(并发操作),则意味着最大id已更改,除非并且直到您显式获得了一个锁并且不释放它
rohanagarwal

如果预期的用例是将最后插入的ID用作后续插入值的一部分,请参见此问题
Flux

Answers:


606

tl;dr:转到选项3:使用RETURNING插入)

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

如果您有兴趣获取新插入的行的ID,可以采用以下几种方法:


选项1 :CURRVAL(<sequence name>);

例如:

  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();

这与前面的相似,只是您不需要指定序列名称:它会查找最新的修改序列(始终在会话中,与上面相同)。


这两个CURRVALLASTVAL是完全并行的安全。设计PG中序列的行为是为了使不同的会话不会受到干扰,因此不会出现竞争状况的风险(如果另一个会话在我的INSERT和SELECT之间插入了另一行,我仍然会得到正确的值)。

但是,它们确实有一个潜在的潜在问题。如果数据库中有一些TRIGGER(或RULE),它们在插入persons表时会在其他表中进行一些额外的插入……那么LASTVAL可能会给我们错误的值。CURRVAL如果在同persons一张表中进行了额外的插入操作,则甚至可能会发生问题(这种情况不太常见,但是风险仍然存在)。


方案3: INSERTRETURNING

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

这是获取ID的最干净,有效和安全的方法。它没有以前的任何风险。

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


结论:如果可以,请选择选项3。在其他地方,请选择1。

注意:如果您打算全局获取最后插入的ID(不一定要通过会话),则所有这些方法都无用。为此,您必须诉诸SELECT max(id) FROM table(当然,这不会从其他事务中读取未提交的插入)。

相反,您绝不应该使用SELECT max(id) FROM table上述3个选项之一来获取由您的INSERT语句刚生成的ID ,因为(除性能之外)这不是并发安全的:在您INSERTSELECT另一个会话之间可能已插入另一个记录。


25
LASTVAL()可能非常邪恶,以防您添加触发器/规则将自身上的行插入到另一个表中。
库伯·萨帕列夫

3
SELECT max(id)不幸的是,一旦您开始删除行,该操作也不会执行。
Simon A. Eugster

2
@leonbloy除非我错过了一些东西,如果你有ID的行1,2,3,4,5和删除行4和5,最后插入的ID仍然是5,但max()返回3
西蒙A. EUGSTER

9
RETURNING id;欢迎使用如何将其插入另一个表的示例 !
奥利维尔·庞斯

8
如何RETURNING id在提供给psql命令行工具的SQL脚本中使用?
Amoe

82

请参阅INSERT语句的RETURNING子句。基本上,INSERT会作为查询加倍,并为您提供插入的值。


4
从8.2版开始工作,是最好,最快的解决方案。
Frank Heikens 2010年

9
也许快速解释如何使用所说的返回ID?
Andrew

@Andrew我不确定我是否理解这个问题。您不知道如何从查询中检索结果吗?这取决于语言/库,无论您执行选择插入还是返回插入,其工作方式都应相同。我唯一能想到的另一种解释是,您已经从呼叫中成功检索了ID,却不知道它的用途……在这种情况下,为什么要检索它?
kwatford '16

8
@Andrew,如果你在psql命令行运行这样的:insert into names (firstname, lastname) values ('john', 'smith') returning id;,那么它只是输出ID就像如果你跑select id from names where id=$lastid直接。如果要将return保存到a变量中,则insert into names (firstname, lastname) values ('john', 'smith') returning id into other_variable;如果包含return的语句是函数中最后一条语句,则returning整个函数将返回正被编辑的id 。
亚历山大·伯德

32

您可以在INSERT语句中使用RETURNING子句,如下所示

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 

28

伦勃朗的答案很完整。我只添加一种特殊情况,即需要从选项3不完全适合的PL / pgSQL函数中获取最后插入的值。

例如,如果我们有下表:

CREATE TABLE person(
   id serial,
   lastname character varying (50),
   firstname character varying (50),
   CONSTRAINT person_pk PRIMARY KEY (id)
);

CREATE TABLE client (
    id integer,
   CONSTRAINT client_pk PRIMARY KEY (id),
   CONSTRAINT fk_client_person FOREIGN KEY (id)
       REFERENCES person (id) MATCH SIMPLE
);

如果需要插入客户记录,则必须引用个人记录。但是,假设我们要设计一个PL / pgSQL函数,该函数可将新记录插入客户端,但还要负责插入新的人记录。为此,我们必须使用leonbloy的OPTION 3的微小变化:

INSERT INTO person(lastname, firstname) 
VALUES (lastn, firstn) 
RETURNING id INTO [new_variable];

请注意,有两个INTO子句。因此,PL / pgSQL函数的定义如下:

CREATE OR REPLACE FUNCTION new_client(lastn character varying, firstn character varying)
  RETURNS integer AS
$BODY$
DECLARE
   v_id integer;
BEGIN
   -- Inserts the new person record and retrieves the last inserted id
   INSERT INTO person(lastname, firstname)
   VALUES (lastn, firstn)
   RETURNING id INTO v_id;

   -- Inserts the new client and references the inserted person
   INSERT INTO client(id) VALUES (v_id);

   -- Return the new id so we can use it in a select clause or return the new id into the user application
    RETURN v_id;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE;

现在我们可以使用以下命令插入新数据:

SELECT new_client('Smith', 'John');

要么

SELECT * FROM new_client('Smith', 'John');

然后,我们获得了新创建的ID。

new_client
integer
----------
         1

9

对于需要获取所有数据记录的用户,可以添加

returning *

到查询末尾以获取包括id在内的所有对象。


8

见下面的例子

CREATE TABLE users (
    -- make the "id" column a primary key; this also creates
    -- a UNIQUE constraint and a b+-tree index on the column
    id    SERIAL PRIMARY KEY,
    name  TEXT,
    age   INT4
);

INSERT INTO users (name, age) VALUES ('Mozart', 20);

然后,为获取最后插入的ID,请将其用于表“ user”的seq列名称“ id”

SELECT currval(pg_get_serial_sequence('users', 'id'));


1

尝试这个:

select nextval('my_seq_name');  // Returns next value

如果返回1(或序列的start_value),则将序列重置为原始值,并传递false标志:

select setval('my_seq_name', 1, false);

除此以外,

select setval('my_seq_name', nextValue - 1, true);

这会将序列值恢复到原始状态,“ setval”将返回您要查找的序列值。


1

Postgres具有相同的内置机制,该机制在同一查询中返回id或您希望查询返回的任何内容。这是一个例子。考虑您创建了一个包含2列column1和column2的表,并且希望在每次插入后返回column1。

# 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)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.