我有一张看起来像这样的桌子:
CREATE TABLE IF NOT EXISTS list (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tok TEXT,
sid TEXT NOT NULL,
aid TEXT,
hash TEXT,
qtt SMALLINT,
p DECIMAL,
UNIQUE (tok, sid, aid),
UNIQUE (sid, qtt, hash)
);
我想用node-pg创建一个动态插入函数,其查询大致如下:
INSERT (tok, sid, aid, qtt, hash, p)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (tok, sid, aid, qtt, hash)
DO UPDATE SET p = $6;
当我尝试插入
(NULL, string, NULL, int, string, decimal)
的值(与组合键之一匹配的值)时,它会抛出此错误:
错误:没有与 ON CONFLICT 规范相匹配的唯一或排除约束。
这与node-pg关系不大,但看起来我是如何制作
INSERT
查询本身的。我的目的是让单个 upsert 查询在发生冲突时处理两种“UNIQUE
类型”的列表项,关于复合唯一性的其他问题没有给出关于如何使用具有重叠列的复合键来执行此操作的明确答案。
假设只有与任一唯一键匹配的行,我该如何处理插入或约束才能使其正常工作?有没有办法让它成为一个动态的单查询解决方案?
DO UPDATE
的INSERT ... ON CONFLICT ...
变体适用于单个“冲突目标”。 说明书:
对于
,可以选择指定ON CONFLICT DO NOTHING
;省略时,将处理与所有可用约束(和唯一索引)的冲突。对于conflict_target
,必须提供ON CONFLICT DO UPDATE
。conflict_target
您不能将多个
UNIQUE
约束中的列合并为一个“冲突目标”。这使得 Postgres 在所有五列上寻找单多列约束,但该约束不存在并导致您报告的错误消息。
ON CONFLICT (tok, sid, aid, qtt, hash)
您可以使用
ON CONFLICT ... DO NOTHING
“检查”,它处理所有约束,包括您提到的两个。但这只会抑制异常,并不能避免稍后 UPDATE
可能出现的竞争条件。没有干净的“单查询解决方案”。
您想要的工作流程本质上是有缺陷的。
如果仅违反两个唯一约束之一,然后您更新值列,但不更新其他列,则结果状态会产生问题。该行最终的其余列具有不同的(预先存在的)值,这反过来又会触发下一个
INSERT
的不同值的唯一违规。批量 UPSERT 的结果突然取决于输入行的顺序。
此外,两行可能会同时引发独特的违规。应该更新哪一个? (UPSERT 是围绕更新one 的前提构建的。)这是一团糟。
干净的解决方案是拥有两个单独的桌子,每个桌子一个 PK。
或者可能是一个表的两个互斥的部分唯一索引。所有列都是 nullable,除了
sid
之外,它是两个唯一索引之间的交集。那将指向那个方向。每行只有 (tok, aid)
和 (qtt, hash)
NOT NULL
之一吗?
如果运气好的话,您可以运行 Postgres 15 或更高版本,并且您可以使用
NULLS NOT DISTINCT
子句将两个唯一约束合并为一个:
ALTER TABLE list
DROP CONSTRAINT list_sid_qtt_hash_key
, DROP CONSTRAINT list_tok_sid_aid_key
, ADD CONSTRAINT list_uni_key UNIQUE NULLS NOT DISTINCT (sid, qtt, hash, tok, aid);
然后你所有的问题都会消失。 参见:
相关: