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

发布于 2024-11-04 13:16:47 字数 273 浏览 0 评论 0原文

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

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

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

我怎样才能让它返回所有这些?

谢谢

Im working with Postgres, using SERIAL as my primary key. After I insert a row I can get the generated key either by using 'RETURNING' or CURRVAL().

Now my problem is that I want to do a batch insert inside a transaction and get ALL the generated keys.

All I get with RETURNING and CURRVAL is the last generated id, the rest of the result get discarded.

How can I get it to return all of them?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

挽清梦 2024-11-11 13:16:48

您可以将 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 值。

您可以尝试在事务开始和结束时获取序列的当前值,并使用它们来确定使用了哪些序列值,但是 这不可靠

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

因此,即使您的序列的缓存值为 1,您的事务中仍然可以具有非连续的序列值。但是,如果序列的缓存值与事务中的插入数量匹配,您可能是安全的,但我猜这会太大而没有意义。

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

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

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.

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

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

You can use RETURNING with multiple values:

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)

So you want something more like this:

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.

And then you'll have to gather the returned EntityKey values from each statement in your transaction.

You could try to grab the sequence's current value at the beginning and end of the transaction and use those to figure out which sequence values were used but that is not reliable:

Furthermore, although multiple sessions are guaranteed to allocate
distinct sequence values, the values might be generated out of
sequence when all the sessions are considered. For example, with a
cache setting of 10, session A might reserve values 1..10 and return
nextval=1, then session B might reserve values 11..20 and return
nextval=11 before session A has generated nextval=2. Thus, with a
cache setting of one it is safe to assume that nextval values are
generated sequentially; with a cache setting greater than one you
should only assume that the nextval values are all distinct, not
that they are generated purely sequentially. Also, last_value will
reflect the latest value reserved by any session, whether or not
it has yet been returned by nextval.

So, even if your sequences have cache values of one you can still have non-contiguous sequence values in your transaction. However, you might be safe if the sequence's cache value matches the number of INSERTs in your transaction but I'd guess that that's going to be too large to make sense.

UPDATE: I just noticed (thanks to the questioner's comments) that there are two tables involved, got a bit lost in the wall of text.

In that case, you should be able to use the current 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.

And grab the EntityKey values one at a time from the INSERTs on AutoEntityKey. Some sort of script might be needed to handle the RETURNING values. You could also wrap the AutoKeyEntity and related AutoKeyEntityListed INSERTs in a function, then use INTO to grab the EntityKey value and return it from the function:

INSERT INTO AutoKeyEntity /*...*/ RETURNING EntityKey INTO ek;
/* AutoKeyEntityListed INSERTs ... */
RETURN ek;
删除会话 2024-11-11 13:16:48

您可以使用此方法预先分配连续的 id:

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

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

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

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;

you can pre-assign consecutive ids using this:

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

it should be a faster alternative to calling nextval() gazillions of times.

you could also store ids in a temporary table:

create temporary blah (
  id int
) on commit drop;

insert into table1 (...) values (...)
returning id into blah;

in postgres 9.1, can able to use CTEs:

with
ids as (
insert into table1 (...) values (...)
    returning id
)
insert into table2 (...)
select ...
from ids;
倚栏听风 2024-11-11 13:16:48

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

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

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

In your application, gather values from the sequence :

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

Create your rows using those values, and simply insert (using a multiline insert). It's safe (SERIAL works as you'd expect, no reuse of values, concurrent proof, etc) and fast (you insert all the rows at once without many client-server roundtrips).

东风软 2024-11-11 13:16:48

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

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

因此,解决方案可能是:

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

Replying to Scott Marlowe's comment in more detail :

Say you have a tree table with the usual parent_id reference to itself, and you want to import a large tree of records. Problem is you need the parent's PK value to be known to insert the children, so potentially this can need lots of individual INSERT statements.

So a solution could be :

  • build the tree in the application
  • grab as many sequence values as nodes to insert, using "SELECT nextval( ... ) FROM generate_series( 1, number_of_values ) n" (the order of the values does not matter)
  • assign those primary key values to the nodes
  • do a bulk insert (or COPY) traversing the tree structure, since the PKs used for relations are known
手长情犹 2024-11-11 13:16:48

有三种方法可以做到这一点。使用 currval()、使用返回或编写一个存储过程将这些方法中的任何一个包装在一个漂亮的小毯子中,这样您就不必在半客户端半 postgres 中完成这一切。

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;

There are three ways to do this. Use currval(), use returning, or write a stored procdure to wrap either of those methods in a nice little blanket that keeps you from doing it all in half client half postgres.

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;
人生戏 2024-11-11 13:16:48

执行FOR循环并逐条处理记录。它的性能可能较差,但它是并发安全的。

示例代码:

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 $;

Perform a FOR LOOP and process records one by one. It might be less performant but it is concurrency safe.

Example code:

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 $;

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文