I have two tables: demo at db<>fiddle
keywords has two columns id and v (which hold the keyword's value)
create table keywords(
id int generated always as identity primary key
,v text unique);
main has, among others, two columns that are each a foreign key into the keywords table's id, named key1_id and key2_id
create table main(
id int generated always as identity primary key
,key1_id int references keywords(id)
,key2_id int references keywords(id));
Now, I want to insert pairs of keywords (key1 and key2 as $1 and $2) into the main table, like this:
WITH key1 AS (INSERT INTO keywords (v)
VALUES ($1)
ON CONFLICT (v) DO UPDATE
SET v = EXCLUDED.v
RETURNING id),
key2 AS (INSERT INTO keywords (v)
VALUES ($2)
ON CONFLICT (v) DO UPDATE
SET v = EXCLUDED.v
RETURNING id)
INSERT INTO main (key1_id, key2_id)
SELECT key1.id, key2.id
FROM key1, key2
RETURNING id
Basically, the two keys often have recurring values, so I use the keywords table to keep a unique set of them (mostly for storage optimization, as I have millions of rows with them).
But if $1 and $2 have identical values, I get this error, whereas there's no issue if they're different:
pg_query_params(): Query failed: ERROR: ON CONFLICT DO UPDATE command cannot affect row a second time
HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
The goal is that both key1_id and key2_id point to the correct row in keywords based on the values passed as $1 and $2, even if they're both the same.
How do I modify the SQL statement (ideally, it should remain a single one) so that I can insert these keys without getting the error?
I am using Postgresql 16.9.
You can do exactly what the hint suggests:
Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
Reduced to a single insert, with a union making sure duplicate insert never happens and coalesce() just repeating key1.id in that case:
demo at db<>fiddle
WITH keys AS (INSERT INTO keywords (v)
SELECT $1
UNION--deduplicates
SELECT $2
ON CONFLICT (v) DO UPDATE
SET v = EXCLUDED.v
RETURNING id)
INSERT INTO main ( key1_id, key2_id )
SELECT min(id), max(id)
FROM keys
RETURNING id;