8

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

2

1 Answer 1

9

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;
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.