从Postgres批量插入返回多个SERIAL值

时间:2011-05-03 21:31:17

标签: postgresql

我正在使用Postgres,使用SERIAL作为我的主键。插入行后,我可以使用“RETURNING”或CURRVAL()来获取生成的密钥。

现在我的问题是我想在事务中进行批量插入并获取所有生成的密钥。

我使用RETURNINGCURRVAL获得的是最后生成的ID,其余结果将被丢弃。

如何让它归还所有这些?

由于

6 个答案:

答案 0 :(得分:20)

您可以将RETURNING与多个值结合使用:

psql=> create table t (id serial not null, x varchar not null);
psql=> insert into t (x) values ('a'),('b'),('c') returning id;
 id 
----
  1
  2
  3
(3 rows)

所以你想要更像这样的东西:

INSERT INTO AutoKeyEntity (Name,Description,EntityKey) VALUES
('AutoKey 254e3c64-485e-42a4-b1cf-d2e1e629df6a','Testing 5/4/2011 8:59:43 AM',DEFAULT)
returning EntityKey;
INSERT INTO AutoKeyEntityListed (EntityKey,Listed,ItemIndex) VALUES
(CURRVAL('autokeyentity_entityKey_seq'),'Test 1 AutoKey 254e3c64-485e-42a4-b1cf-d2e1e629df6a', 0),
(CURRVAL('autokeyentity_entityKey_seq'),'Test 2 AutoKey 254e3c64-485e-42a4-b1cf-d2e1e629df6a', 1),
(CURRVAL('autokeyentity_entityKey_seq'),'Test 3 AutoKey 254e3c64-485e-42a4-b1cf-d2e1e629df6a', 2)
returning EntityKey;
-- etc.

然后,您必须从事务中的每个语句中收集返回的EntityKey值。

你可以尝试在事务的开始和结束时获取序列的当前值,并使用它们来确定使用了哪些序列值但是that is not reliable

  

此外,尽管保证分配多个会话   不同的序列值,可能会生成值   考虑所有会话时的顺序。例如,用   缓存设置为10,会话A可能会保留值1..10并返回   nextval=1,然后会话B可能保留值11..20并返回   在会话A生成nextval = 2之前nextval=11。因此,用   缓存设置为一个可以安全地假设nextval值   顺序生成; 缓存设置大于1的情况   应该只假设nextval值都是不同的,而不是   它们是纯粹顺序生成的。此外,last_value会   反映任何会话保留的最新值,无论是否   它已由nextval返回。

因此,即使您的序列的缓存值为1,您仍然可以在事务中使用非连续的序列值。但是,如果序列的缓存值与您事务中INSERT的数量相匹配,那么您可能会安全,但我猜这会太大而无法理解。

更新:我刚刚注意到(感谢提问者的评论),涉及到两个表格,在文本墙上有点丢失。

在这种情况下,您应该能够使用当前的INSERTS:

INSERT INTO AutoKeyEntity (Name,Description,EntityKey) VALUES
('AutoKey 254e3c64-485e-42a4-b1cf-d2e1e629df6a','Testing 5/4/2011 8:59:43 AM',DEFAULT)
returning EntityKey;
INSERT INTO AutoKeyEntityListed (EntityKey,Listed,ItemIndex) VALUES
(CURRVAL('autokeyentity_entityKey_seq'),'Test 1 AutoKey 254e3c64-485e-42a4-b1cf-d2e1e629df6a', 0),
(CURRVAL('autokeyentity_entityKey_seq'),'Test 2 AutoKey 254e3c64-485e-42a4-b1cf-d2e1e629df6a', 1),
(CURRVAL('autokeyentity_entityKey_seq'),'Test 3 AutoKey 254e3c64-485e-42a4-b1cf-d2e1e629df6a', 2);
-- etc.

EntityKey上的INSERT中一次抓取AutoEntityKey个值。可能需要某种脚本来处理RETURNING值。您还可以将AutoKeyEntity和相关AutoKeyEntityListed INSERT包装在函数中,然后使用INTO获取EntityKey值并从函数返回:

INSERT INTO AutoKeyEntity /*...*/ RETURNING EntityKey INTO ek;
/* AutoKeyEntityListed INSERTs ... */
RETURN ek;

答案 1 :(得分:4)

您可以使用以下方法预先指定连续的ID:

SELECT setval(seq, nextval(seq) + num_rows - 1, true) as stop

它应该是调用nextval() gazillions次数的更快的替代方案。

您还可以将ID存储在临时表中:

create temporary blah (
  id int
) on commit drop;

insert into table1 (...) values (...)
returning id into blah;
在postgres 9.1中,能够使用CTE:

with
ids as (
insert into table1 (...) values (...)
    returning id
)
insert into table2 (...)
select ...
from ids;

答案 2 :(得分:3)

在您的应用程序中,从序列中收集值:

SELECT nextval( ... ) FROM generate_series( 1, number_of_values ) n

使用这些值创建行,然后简单地插入(使用多行插入)。这是安全的(SERIAL按照您的预期工作,不重用值,并发证明等)和快速(您一次插入所有行而无需许多客户端 - 服务器往返)。

答案 3 :(得分:1)

更详细地回复Scott Marlowe的评论:

假设您有一个树表,其中包含通常的parent_id引用,并且您想要导入一个大型记录树。问题是您需要知道插入子节点的父节点PK值,因此可能需要大量单独的INSERT语句。

所以解决方案可能是:

  • 在应用程序中构建树
  • 使用“SELECT nextval(...)FROM generate_series(1,number_of_values)n”获取与要插入的节点一样多的序列值(值的顺序无关紧要)
  • 将这些主键值分配给节点
  • 执行批量插入(或COPY)遍历树结构,因为用于关系的PK已知

答案 4 :(得分:0)

有三种方法可以做到这一点。使用currval(),使用返回或写一个存储的procdure将这些方法中的任何一个包装在一个漂亮的小毯子中,这样你就不会在半个客户端的一半中完成所有操作。

Currval method:
begin;
insert into table a (col1, col2) values ('val1','val2');
select currval('a_id_seq');
123  -- returned value
-- client code creates next statement with value from select currval
insert into table b (a_fk, col3, col4) values (123, 'val3','val4');
-- repeat the above as many times as needed then...
commit;

Returning method:
begin;
insert into table a (col1, col2) values ('val1','val2'), ('val1','val2'), ('val1','val2') returning a_id; -- note we inserted three rows
123  -- return values
124
126
insert into table b (a_fk, col3, col4) values (123, 'val3','val4'), (124, 'val3','val4'), (126, 'val3','val4');
commit;

答案 5 :(得分:0)

执行FOR LOOP并逐个处理记录。它可能性能较低,但并发安全。

示例代码:

DO $$
  DECLARE r record;
    
  BEGIN
    FOR r IN SELECT id FROM {table} WHERE {condition} LOOP
      WITH idlist AS (
        INSERT INTO {anotherTable} ({columns}) VALUES ({values})
        RETURNING id
      UPDATE {table} c SET {column} = (SELECT id FROM idlist) WHERE c.id = {table}.id;
    END LOOP;
END $$;