没有与 ON CONFLICT 匹配的唯一或排除约束

问题描述 投票:0回答:6

执行以下类型的插入时出现以下错误:

查询:

INSERT INTO accounts (type, person_id) VALUES ('PersonAccount', 1) ON
CONFLICT (type, person_id) WHERE type = 'PersonAccount' DO UPDATE SET
updated_at = EXCLUDED.updated_at RETURNING *

错误:

SQL执行失败(原因:错误:没有唯一或排除 与 ON CONFLICT 规范匹配的约束)

我还有一个独特的INDEX:

CREATE UNIQUE INDEX uniq_person_accounts ON accounts USING btree (type,
person_id) WHERE ((type)::text = 'PersonAccount'::text);

问题是有时它会起作用,但并非每次都有效。我随机得到 那个异常,确实很奇怪。好像无法访问 INDEX 或者它不知道它存在。

有什么建议吗?

我使用的是 PostgreSQL 9.5.5。

执行尝试查找或创建帐户的代码时的示例:

INSERT INTO accounts (type, person_id, created_at, updated_at) VALUES ('PersonAccount', 69559, '2017-02-03 12:09:27.259', '2017-02-03 12:09:27.259') ON CONFLICT (type, person_id) WHERE type = 'PersonAccount' DO UPDATE SET updated_at = EXCLUDED.updated_at RETURNING *
 SQL execution failed (Reason: ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification)

在这种情况下,我确定该帐户不存在。此外,当该人已经有帐户时,它永远不会输出错误。问题是,在某些情况下,如果还没有帐户,它也可以工作。查询完全相同。

sql postgresql postgresql-9.5
6个回答
61
投票

根据 文档

所有 table_name 唯一索引,无论顺序如何,都准确包含 冲突目标指定的列/表达式被推断(选择)作为仲裁者 索引。如果指定了index_predicate,则作为进一步的要求,它必须 为了进行推理,满足仲裁者索引。

文档继续说,

[index_predicate]用于允许推断部分唯一索引

文档以一种低调的方式说,当使用部分索引和 在发生冲突时进行更新插入,必须指定index_predicate。它不是 为你推断。我学到了这个 这里,下面的例子演示了这一点。

CREATE TABLE test.accounts (
    id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
    type text,
    person_id int);
CREATE UNIQUE INDEX accounts_note_idx on accounts (type, person_id) WHERE ((type)::text = 'PersonAccount'::text);
INSERT INTO  test.accounts (type, person_id) VALUES ('PersonAccount', 10);

这样我们就有:

unutbu=# select * from test.accounts;
+----+---------------+-----------+
| id |     type      | person_id |
+----+---------------+-----------+
|  1 | PersonAccount |        10 |
+----+---------------+-----------+
(1 row)

如果没有

index_predicate
,我们会得到一个错误:

INSERT INTO  test.accounts (type, person_id) VALUES ('PersonAccount', 10) ON CONFLICT (type, person_id) DO NOTHING;
-- ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification

但是如果您包含index_predicate,

WHERE ((type)::text = 'PersonAccount'::text)

INSERT INTO  test.accounts (type, person_id) VALUES ('PersonAccount', 10)
ON CONFLICT (type, person_id)
WHERE ((type)::text = 'PersonAccount'::text) DO NOTHING;

那么就没有错误,并且不做任何事情是被尊重的。


52
投票

此错误的简单解决方案

首先让我们通过一个简单的例子来看看错误原因。这是将产品映射到类别的表格。

create table if not exists product_categories (
    product_id uuid references products(product_id) not null,
    category_id uuid references categories(category_id) not null,
    whitelist boolean default false
);

如果我们使用这个查询:

INSERT INTO product_categories (product_id, category_id, whitelist)
VALUES ('123...', '456...', TRUE)
ON CONFLICT (product_id, category_id)
DO UPDATE SET whitelist=EXCLUDED.whitelist;

这会给您带来错误

No unique or exclusion constraint matching the ON CONFLICT
,因为
product_id
category_id
没有唯一约束。可能有多行具有相同的产品和类别 ID 组合(因此它们永远不会发生冲突)。

解决方案:

product_id
category_id
使用唯一约束,如下所示:

create table if not exists product_categories (
    product_id uuid references products(product_id) not null,
    category_id uuid references categories(category_id) not null,
    whitelist boolean default false,
    primary key(product_id, category_id) -- This will solve the problem
    -- unique(product_id, category_id) -- OR this if you already have a primary key
);

现在您可以对两列使用

ON CONFLICT (product_id, category_id)
,不会出现任何错误。

简而言之: 无论您使用什么列

on conflict
,它们都应该具有唯一的约束。


11
投票

解决此问题的简单方法是将冲突列设置为 UNIQUE


3
投票

我没有机会玩 UPSERT,但我认为你有一个案例 文档

请注意,这意味着非部分唯一索引(唯一索引 没有谓词)将被推断(因此被 ON CONFLICT 使用) 如果有这样一个满足所有其他标准的指数可用。如果 尝试推理不成功,出现错误。


3
投票

我通过为要包含在 ON CONFLICT 子句中的所有列创建一个 UNIQUE INDEX 解决了同样的问题,而不是为每一列创建一个 UNIQUE INDEX。

CREATE TABLE table_name (
  element_id UUID NOT NULL DEFAULT gen_random_uuid(),
  timestamp TIMESTAMP NOT NULL DEFAULT now():::TIMESTAMP,
  col1 UUID NOT NULL,
  col2 STRING NOT NULL ,
  col3 STRING NOT NULL ,
  CONSTRAINT "primary" PRIMARY KEY (element_id ASC),
  UNIQUE (col1 asc, col2 asc, col3 asc)
);

这将允许查询类似

INSERT INTO table_name (timestamp, col1, col2, col3) VALUES ('timestamp', 'uuid', 'string', 'string')
ON CONFLICT (col1, col2, col3)
DO UPDATE timestamp = EXCLUDED.timestamp, col1 = EXCLUDED.col1, col2 = excluded.col2, col3 = col3.excluded;

0
投票

如果列已存在,您可以键入以下命令:

ALTER TABLE table_name
ADD CONSTRAINT unique_column_name UNIQUE (column_name);

替换为您的

table_name
column_name

© www.soinside.com 2019 - 2024. All rights reserved.