124

Is something like this possible?

INSERT INTO Table2 (val)
VALUES ((INSERT INTO Table1 (name) VALUES ('a_title') RETURNING id));

like using the return value as value to insert a row in a second table with a reference to the first table?

Timur Shtatland
  • 12,024
  • 2
  • 30
  • 47
Eike Cochu
  • 3,249
  • 7
  • 34
  • 57

6 Answers6

146

You can do so starting with Postgres 9.1:

with rows as (
INSERT INTO Table1 (name) VALUES ('a_title') RETURNING id
)
INSERT INTO Table2 (val)
SELECT id
FROM rows

In the meanwhile, if you're only interested in the id, you can do so with a trigger:

create function t1_ins_into_t2()
  returns trigger
as $$
begin
  insert into table2 (val) values (new.id);
  return new;
end;
$$ language plpgsql;

create trigger t1_ins_into_t2
  after insert on table1
for each row
execute procedure t1_ins_into_t2();
Denis de Bernardy
  • 75,850
  • 13
  • 131
  • 154
  • 3
    How to insert values beside the returning id ? for example: INSERT INTO TABLE2(val1, val2, val3) (1, 2, SELECT id FROM rows) – Mahmoud Hanafy Jun 21 '16 at 07:56
  • @MahmoudHanafy: replacing `rows` with `(some_query returning ...)` might work nowadays (haven't tried). – Denis de Bernardy Jun 21 '16 at 08:23
  • 4
    @MahmoudHanafy: To insert values beside the returning id you can do something like this: INSERT INTO TABLE2(val1, val2, val3) SELECT id, 1, 2 FROM rows – Bhindi Nov 07 '16 at 13:12
  • upvoted! is this atomic meaning if first insert succeeds and second one doesnt what happens then? – PirateApp Jun 25 '20 at 12:09
  • 2
    @PirateApp Just tested! v12.4. First INSERT is indeed roll-backed if the second one fails, but the series/autoincrement of the first INSERT is skipped – Madacol Oct 26 '20 at 04:47
87

The best practice for this situation. Use RETURNING … INTO.

INSERT INTO teams VALUES (...) RETURNING id INTO last_id;

Note this is for PLPGSQL

Nick Bull
  • 9,518
  • 6
  • 36
  • 58
Alexandre Assi
  • 871
  • 6
  • 4
  • 8
    Is this actually a thing? No part of the doc you linked to seems to mention `RETURNING ... INTO`. – alecbz Jul 31 '18 at 20:06
  • 6
    @Alec: I found [this documentation](http://www.postgresql.org/docs/current/interactive/plpgsql-statements.html#PLPGSQL-STATEMENTS-SQL-ONEROW) in [this answer](https://stackoverflow.com/a/12328286/4174789). – Bart Hofland Oct 11 '19 at 09:33
  • @PedroD: It does. – Bart Hofland Oct 11 '19 at 09:33
  • 3
    Very useful. In case it's not clear to other readers, you can also `RETURNING col1, col2 INTO var1, var2` – FlexYourData Oct 29 '21 at 16:26
  • @alecbz Wow. Finally. I just spent the past 3 hours looking for this answer, whicch ought to be marked as the correct one. I just want to store it in a variable to be reused. The whole `WITH` clause was far too complicated for what I was trying to achieve – rolling_codes Oct 02 '22 at 18:47
23
DO $$
DECLARE tableId integer;
BEGIN
  INSERT INTO Table1 (name) VALUES ('a_title') RETURNING id INTO tableId;
  INSERT INTO Table2 (val) VALUES (tableId);
END $$;

Tested with psql (10.3, server 9.6.8)

Anders B
  • 3,343
  • 1
  • 26
  • 17
19

In line with the answer given by Denis de Bernardy..

If you want id to be returned afterwards as well and want to insert more things into Table2:

with rows as (
INSERT INTO Table1 (name) VALUES ('a_title') RETURNING id
)
INSERT INTO Table2 (val, val2, val3)
SELECT id, 'val2value', 'val3value'
FROM rows
RETURNING val
Bhindi
  • 1,403
  • 12
  • 16
10

You can use the lastval() function:

Return value most recently obtained with nextval for any sequence

So something like this:

INSERT INTO Table1 (name) VALUES ('a_title');
INSERT INTO Table2 (val)  VALUES (lastval());

This will work fine as long as no one calls nextval() on any other sequence (in the current session) between your INSERTs.

As Denis noted below and I warned about above, using lastval() can get you into trouble if another sequence is accessed using nextval() between your INSERTs. This could happen if there was an INSERT trigger on Table1 that manually called nextval() on a sequence or, more likely, did an INSERT on a table with a SERIAL or BIGSERIAL primary key. If you want to be really paranoid (a good thing, they really are you to get you after all), then you could use currval() but you'd need to know the name of the relevant sequence:

INSERT INTO Table1 (name) VALUES ('a_title');
INSERT INTO Table2 (val)  VALUES (currval('Table1_id_seq'::regclass));

The automatically generated sequence is usually named t_c_seq where t is the table name and c is the column name but you can always find out by going into psql and saying:

=> \d table_name;

and then looking at the default value for the column in question, for example:

id | integer | not null default nextval('people_id_seq'::regclass)

FYI: lastval() is, more or less, the PostgreSQL version of MySQL's LAST_INSERT_ID. I only mention this because a lot of people are more familiar with MySQL than PostgreSQL so linking lastval() to something familiar might clarify things.

Community
  • 1
  • 1
mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • 2
    Better use currval(), though, in case a trigger on table1 does subsequent inserts. – Denis de Bernardy Jul 03 '11 at 06:19
  • @Denis: True, but then you need the name of the sequence. I'll add a little update to that effect just to cover all the bases. – mu is too short Jul 03 '11 at 06:26
  • LASTVAL() and CURRVAL() both work for the current database connection, not for other connections. Other users can update the sequence at the samen time, that won't change your results. Don't worry about others, they will never change your results for LASTVAL and/of CURRVAL. LASTVAL and CURRVAL can't be used at all when using a connectionpool without a TRANSACTION, that's when things go wrong: You don't control the database connection. – Frank Heikens Jul 03 '11 at 06:30
  • 1
    @Frank: Yes, they're all session specific but the problem with `lastval` is that there could be a sequence based INSERT behind your back from an AFTER INSERT trigger on Table1. That would be in the current session and would, presumably, change `lastval()` when you're not expecting it. – mu is too short Jul 03 '11 at 06:40
2

table_ex

id default nextval('table_id_seq'::regclass),

camp1 varchar

camp2 varchar

INSERT INTO table_ex(camp1,camp2) VALUES ('xxx','123') RETURNING id 
Community
  • 1
  • 1
kemado77
  • 29
  • 2