I have two Postgres tables as shown below, primary key is id
:
table1:
id | name
----------
1 | Bob
3 | Steven
table2:
id | name
----------
2 | John
3 | Jack
I would like to combine these two tables by inserting table2
to table1
, and table1
should look like below after the operation.
Essentially, it can maintain the same primary key if there is no conflict, but when it has conflict, it will generate a new id for the incoming data from table2
, and insert that as a new row in table1
. In this example, ‘Jack’ from table2
will be inserted as a new row with a new id
of 4 (max id from table1 + 1).
id | name
----------
1 | Bob
2 | John
3 | Steven
4 | Jack
Below is my current approach. Which updates the id in conflicted row in table1
(not what I want):
INSERT INTO table1 (id, name)
SELECT id, name
FROM table2
ON CONFLICT(id) DO UPDATE SET id=nextval(pg_get_serial_sequence('table1', 'id'));
How to insert a new row with a new id?
3
Answers
There isn’t a direct and simple way to achieve the described results. Unless there is a compelling reason to preserve non-conflicting
id
values fromtable2
, the best option is insert thename
values fromtable2
and discard the originalid
values.The following establishes the demonstration environment:
The following demonstrates an approach that can be used to add the contents of
table2
totable1
while preserving non-conflictingid
values fromtable2
:The first part ensures that the sequence that provides
id
values will not conflict with theid
values of either table. After the preceding SQL runs, the contents oftable1
will be:Assuming
table1.id
draws from a sequence.Notably, the
MERGE
command (Postgres 15+) cannot be used (as single command), since it only allowsUPDATE
/DELETE
(or nothing)WHEN MATCHED
.Simple case: no concurrent writes possible
First, know the name of the underlying sequence for
table1.id
. Same path forserial
andIDENTITY
columns. See:If you are not sure, find out:
See:
Proceeding with the default name
public.table1_id_seq
here. Replace with your actual sequence name. Schema-qualify to be safe.If the maximum value in
table2.id
can be higher than the currentvalue of the sequence, set it to the higher value. Only write if necessary:
Compare to the current value of the sequence, rather than the maximum
tabl1.id
. Subtle difference. We wouldn’t want to decrease the sequence value, risking possible conflicts.Note
last_value + is_called::int
. Internally, every sequence has a boolean tagis_called
. The default istrue
– same as for the 2nd parameter of the functionsetval()
. Then the next serial number will be incremented. Cast tointeger
and add,true
→1
/false
→0
, and everything falls into place.Then, probably simplest and fastest:
Concurrent writes possible
If there can be concurrent writes in either table,
LOCK
both to avoid race conditions. The appropriate lock strength should beSHARE ROW EXCLUSIVE
to protect tables against concurrent data changes exclusively.And do it all in a single transaction. (Can’t hurt either way):
fiddle
Insert non-conflicting rows:
Insert conflicting rows with new IDs:
This will ensure that conflicting rows are inserted with a new ID.
I hope this will help you..