0

In an external application I want to do the following:

  1. Insert an entry to table A
  2. Insert a bunch of entries to table B, with the id of my newly inserted item in A as a foreign key

So the tables would look like this:

A(_id, data)

B(_id, other_data)
     _id --> A._id

Is this possible to accomplish in only postgresql? Or is it possible to return the id to my application after the item in table A was created, so my application can add the rest of the values?

I've looked at the following post, but it has an incorrect syntax and it causes an infinite loop resulting in a stack overflow (how ironic).

Community
  • 1
  • 1
Robin
  • 1,927
  • 3
  • 18
  • 27
  • I don't see any "incorrect syntax" in those answers. What is the error you get? And where do you get the infinite loop? In your application? –  Jun 18 '15 at 09:22
  • The incorrect syntax is 'after insert into' when it should really be 'after insert on' in postgresql 9.1. Wasn't able to edit due to not changing enough characters. When syntax is fixed the trigger generates an infinite loop since they are triggering the insert into and using insert into in the trigger. – Robin Jun 18 '15 at 09:27
  • Show us the trigger you have that produces this infinite loop. Are you trying to insert into the same table in a insert trigger? But I don't really see how a trigger would be useful here. The query in a_horse's answer is useful and can be extended easily to insert multiple rows. – ypercubeᵀᴹ Jun 18 '15 at 09:30
  • The trigger is defined in the linked stack overflow thread. Since posting a question on SO requires the user to show what he/she has tested I included that post (that could well seem like a duplicate) to show what I've tested and why it didn't work. – Robin Jun 18 '15 at 09:39

1 Answers1

4

There are several ways to do it:

Assuming a._id is a serial column:

insert into a (data) values ('foo');
insert into b (_id, other_data) values (lastval(), 'foobar');

Edit (after discussion in comments): Note that lastval() is concurrency safe (as all sequence related functions). Even if other sessions insert something into a between the two statements, lastval() would still return the value relevant for the current session (i.e. the one that was generated by the previous insert)

Other ways of doing that are described in detail in the question you already linked to: https://stackoverflow.com/a/6560530/330315

Or using a data modifying CTE:

with insert_a as (
    insert into a (data) values ('foo')
    returning _id
)
insert into b (_id, other_data)
values 
  ((select _id from insert_a), 'one'), 
  ((select _id from insert_a), 'two'), 
  ((select _id from insert_a), 'three');
Community
  • 1
  • 1
  • The first example doesn't work if a new line in A has been added before the lines in B are added. So if something would have been added in between the two insertions, then the lastval() would have given the wrong id. – Robin Jun 18 '15 at 09:29
  • For the second example, how do i pass _n_ number of other_data to b? I'm looking to add one item to A and _many_ items to B. So it would be a one-to-many relationship. – Robin Jun 18 '15 at 09:31
  • 1
    @Robin: well there is no statement between the two and you did not mention anywhere that you want to do that. But _if_ you need to do that, you can use `currval()` with the sequence name to be sure - which is also part of the answers in the linked question: http://stackoverflow.com/a/6560530/330315 I'm really inclined to close your question as a duplicate –  Jun 18 '15 at 09:31
  • 1
    This answer at dba.se has a very nice way to insert multiple rows into 2 tables that are related with an FK: http://dba.stackexchange.com/questions/46410/how-do-i-insert-a-row-which-contains-a-foreign-key/46477#46477 – ypercubeᵀᴹ Jun 18 '15 at 09:34
  • Well since the two lines 'insert into a (data) values ('foo');' and 'insert into b (id, otherdata) values (lastval(), 'foobar');' are coming from an external application we can't know that that application doesn't send other database queries in between? For all we know the application has a query queue where queries are added from multiple threads at the same time. That was kind of _implied_ in the question. – Robin Jun 18 '15 at 09:36
  • Robin, it's not implied at all (not to my eyes at least). Why send the queries separate and not in one? – ypercubeᵀᴹ Jun 18 '15 at 09:37
  • @Robin: your question says: "first instert into A, second insert into B" - where in that description do you state that other things happen between "first" and "second"? But as I have written there are other ways of doing that. And you can always use the `returning` clause in your application to retrieve the generated ID, store it in a variable and then use it in subsequent inserts. –  Jun 18 '15 at 09:39
  • Why is it obvious that two different queries are always executed after each other? Have I missed something? – Robin Jun 18 '15 at 09:44
  • 1
    @robin A description like "*I'll do A first, then I'll do B*" implies to me that your program is doing these two steps and only those two steps (and most probably in a single transaction). If your code is doing some other database work between 1. and 2. that is part of the same transaction or is using the same physical connection you should have mentioned that. –  Jun 18 '15 at 09:47
  • But my thought is that a database server could have several connections at the same time, and therefore we can't be sure that no other application (or the same application) make changes in between? – Robin Jun 18 '15 at 09:49
  • That's what transactions are for. – ypercubeᵀᴹ Jun 18 '15 at 09:52
  • @robin: yes of course several connections can insert at the same time. And each one will see the correct value when calling `lastval()` that's the whole purpose of letting the database generate the values. –  Jun 18 '15 at 09:58
  • So until I commit the lastval() will be unaffected by other concurrent changes to the database? – Robin Jun 18 '15 at 10:05
  • @robin: Yes, but the commit is irrelevant here. The only thing that is relevant for `lastval()` (or `currval()`) is the last generated value in your _current_ database session/connection. sequences are not transactional. –  Jun 18 '15 at 10:09
  • I thought the lastval() and currval() was global between sessions/connections, glad we cleared that out. That explains why we couldn't agree that the concurrent modification was implied. – Robin Jun 18 '15 at 10:13
  • @robin: [quote from the manual](http://www.postgresql.org/docs/current/static/functions-sequence.html): "*Return the value most recently returned by nextval in the **current** session*" –  Jun 18 '15 at 10:16