PostgreSQL 部分唯一索引和 upsert

ubi*_*ubi 6 postgresql unique upsert

我正在尝试对具有部分唯一索引的表进行更新插入

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;
Run Code Online (Sandbox Code Playgroud)

通俗地说,p不为空,只有一个qr可以为空。

重复插入会按预期抛出约束违规

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
Run Code Online (Sandbox Code Playgroud)

但是,当我尝试对 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
Run Code Online (Sandbox Code Playgroud)

它仍然抛出约束违规

错误:重复键值违反唯一约束“test_pr_idx”详细信息:键 (p, r)=(p, r) 已经存在。

但我希望该on conflict条款能够捕获它并进行更新。

我究竟做错了什么?我应该使用index_predicate?

index_predicate 用于允许推断部分唯一索引。可以推断出满足谓词的任何索引(实际上不必是部分索引)。遵循 CREATE INDEX 格式。 https://www.postgresql.org/docs/9.5/static/sql-insert.html

kli*_*lin 6

我认为不可能使用多个部分索引作为冲突目标。您应该尝试使用单个索引来实现所需的行为。我能看到的唯一方法是在表达式上使用唯一索引:

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, ''));
Run Code Online (Sandbox Code Playgroud)

现在所有三个测试(执行两次)都违反了相同的索引:

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
Run Code Online (Sandbox Code Playgroud)

在插入命令中,您应该传递索引定义中使用的表达式:

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;
Run Code Online (Sandbox Code Playgroud)