冲突部分索引的 Postgres 唯一或排除约束无法更新票证

Posted

技术标签:

【中文标题】冲突部分索引的 Postgres 唯一或排除约束无法更新票证【英文标题】:Postgres unique or exclusion constraint for partial index on conflict fails to update tickets 【发布时间】:2022-01-17 03:25:27 【问题描述】:

典型票务系统的 PostgreSQL 数据库问题。为什么我的 upsert 没有更新现有票证?

设置

    门票表:
CREATE TABLE ticket (
    ticket_id SERIAL PRIMARY KEY,
    user_id uuid NOT NULL
    coach_id uuid,
    status text NOT NULL,
    last_message text NOT NULL,
    last_updated_at timestamp with time zone NOT NULL,
    completed_at timestamp with time zone
);

-- Indices -------------------------------------------------------

CREATE UNIQUE INDEX ticket_ak1 ON ticket(ticket_id int4_ops);
CREATE UNIQUE INDEX ticket_user_id_not_completed_idx ON ticket(user_id uuid_ops,(status <> 'completed'::text) bool_ops DESC NULLS LAST);

    在代码中开发的约束(不是 db 的枚举类型的一部分):

    status 只能是以下值之一:openunreadcompleted

    之前可用的门票:

INSERT INTO "ticket" ("user_id","coach_id","status","last_message","last_updated_at","ticket_id") VALUES ('d5948d24-6fce-4712-896a-e15cd6db6837',NULL,'open','Accusantium perferendis voluptatem sit aut consequatur.','2021-12-13 17:24:48.389',1) RETURNING "ticket_id";

INSERT INTO "ticket" ("user_id","coach_id","status","last_message","last_updated_at","completed_at","ticket_id") VALUES ('d5948d24-6fce-4712-896a-e15cd6db6837',NULL,'completed','Aut consequatur perferendis sit accusantium voluptatem.','2021-12-13 17:24:48.391','2021-12-13 17:24:48.391',2) RETURNING "ticket_id";

问题

运行此 SQL 以尝试插入第一个 ticket 失败:

INSERT INTO "ticket" ("user_id","coach_id","status","last_message","last_updated_at") VALUES ('ab45ae3f-e84a-4a0a-8072-8896a902d488',NULL,'unread','You are tearing me apart, Brandon!','2021-12-13 17:24:48.389')
    ON CONFLICT ("user_id") DO UPDATE SET "status"="excluded"."status",
        "last_updated_at"="excluded"."last_updated_at",
        "last_message"="excluded"."last_message"
WHERE "excluded"."status" <> 'completed' RETURNING "ticket_id"

带有错误信息:

错误:没有符合 ON CONFLICT 规范的唯一或排除约束

我试过改成:

INSERT INTO "ticket" ("user_id","coach_id","status","last_message","last_updated_at") VALUES ('ab45ae3f-e84a-4a0a-8072-8896a902d488',NULL,'unread','You are tearing me apart, Brandon!','2021-12-13 17:24:48.389')
    ON CONFLICT (user_id) WHERE status <> 'completed' DO UPDATE
    SET "status"="excluded"."status",
        "last_updated_at"="excluded"."last_updated_at",
        "last_message"="excluded"."last_message"

WHERE 子句移到DO UPDATE 之前以触发部分索引查询,但无济于事。

我只想更新“未完成”票证的statuslast_updated_atlast_message(根据该表上定义的部分唯一索引,每个用户应该只有一个)。那么,为什么这个 upsert 不会更新现有票证呢?

【问题讨论】:

要提出解决方案,我必须知道您要达到的目标。 【参考方案1】:

有错误信息是赠品。您的冲突约束与针对表声明的任何唯一约束不匹配。 ?

user_id 列与索引ticket_user_id_not_completed_idx 中的其他列一起存在,因此不存在完全匹配。您的冲突需要与您的索引完全匹配。要么将您的唯一索引更改为 user_id 列,要么将所有列添加到您的 on conflict 子句中。

或者

您可以在 on conflict 子句中按名称引用约束。

from the documentation

【讨论】:

【参考方案2】:

因此,要拥有一个票务系统,每个用户只有一张未完成的票,我所要做的就是修复索引,通过将两列指定为该索引的一部分,即使有一个 where 子句来定义偏倚该索引的:

CREATE UNIQUE INDEX ticket_user_id_not_completed_idx
  ON ticket(user_id, status) WHERE status <> 'completed'

代替:

CREATE UNIQUE INDEX ticket_user_id_not_completed_idx
ON ticket(user_id uuid_ops,(status <> 'completed'::text) bool_ops DESC NULLS LAST);

这也是 VynlJunkie 在之前的评论中所说的。我只是想记录一下我最终是如何解决的。

【讨论】:

以上是关于冲突部分索引的 Postgres 唯一或排除约束无法更新票证的主要内容,如果未能解决你的问题,请参考以下文章

Postgres 唯一约束与索引

Postgres ON CONFLICT 缺少我声明支持唯一索引的主键冲突

如何在 Postgres 中更新 2 列之一的冲突?

在 Postgres 上使用 sqlalchemy 创建部分唯一索引

解决插入数据时,唯一索引/主键冲突问题

使用 Postgres 排除约束限制匹配特定条件的表行数?