PostgreSQL部分唯一索引和upsert

时间:2017-10-13 13:17:22

标签: postgresql unique upsert

我试图对具有部分唯一索引的表进行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;

简单来说,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

然而,当我尝试使用唯一约束进行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

它仍会抛出约束违规

  

错误:重复键值违反了唯一约束" 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

1 个答案:

答案 0 :(得分:3)

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

$posts = Post::withCount('upvotes')
         ->having('upvotes_count', '>', 5)
         ->get();

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

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, ''));

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

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