I'm trying to do an upsert to a table that has partial unique indexes
create table test (
p text not null,
q text,
r text,
txt text,
unique(p,q,r)
);
create unique index test_p_idx on test(p) where q is null and r is null;
create unique index test_pq_idx on test(p, q) where r IS NULL;
create unique index test_pr_idx on test(p, r) where q is NULL;
In plain terms, p
is not null and only one of q
or r
can be null.
Duplicate inserts throw constraint violations as expected
insert into test(p,q,r,txt) values ('p',null,null,'a'); -- violates test_p_idx
insert into test(p,q,r,txt) values ('p','q',null,'b'); -- violates test_pq_idx
insert into test(p,q,r,txt) values ('p',null, 'r','c'); -- violates test_pr_idx
However, when I'm trying to use the unique constraint for an upsert
insert into test as u (p,q,r,txt) values ('p',null,'r','d')
on conflict (p, q, r) do update
set txt = excluded.txt
it still throws the constraint violation
ERROR: duplicate key value violates unique constraint "test_pr_idx" DETAIL: Key (p, r)=(p, r) already exists.
But I'd expect the on conflict
clause to catch it and do the update.
What am I doing wrong? Should I be using an index_predicate
?
index_predicate Used to allow inference of partial unique indexes. Any indexes that satisfy the predicate (which need not actually be partial indexes) can be inferred. Follows CREATE INDEX format. https://www.postgresql.org/docs/9.5/static/sql-insert.html
PostgreSQL lets you either add or modify a record within a table depending on whether the record already exists. This is commonly known as an "upsert" operation (a portmanteau of "insert" and "update").
In relational databases, the term upsert is referred to as merge. The idea is that when you insert a new row into the table, PostgreSQL will update the row if it already exists, otherwise, it will insert the new row. That is why we call the action is upsert (the combination of update or insert).
A unique index guarantees that the table won't have more than one row with the same value. It's advantageous to create unique indexes for two reasons: data integrity and performance. Lookups on a unique index are generally very fast.
I don't think it's possible to use multiple partial indexes as a conflict target. You should try to achieve the desired behaviour using a single index. The only way I can see is to use a unique index on expressions:
drop table if exists test;
create table test (
p text not null,
q text,
r text,
txt text
);
create unique index test_unique_idx on test (p, coalesce(q, ''), coalesce(r, ''));
Now all three tests (executed twice) violate the same index:
insert into test(p,q,r,txt) values ('p',null,null,'a'); -- violates test_unique_idx
insert into test(p,q,r,txt) values ('p','q',null,'b'); -- violates test_unique_idx
insert into test(p,q,r,txt) values ('p',null, 'r','c'); -- violates test_unique_idx
In the insert command you should pass the expressions used in the index definition:
insert into test as u (p,q,r,txt)
values ('p',null,'r','d')
on conflict (p, coalesce(q, ''), coalesce(r, '')) do update
set txt = excluded.txt;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With