执行以下类型的插入时出现以下错误:
查询:
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)
在这种情况下,我确定该帐户不存在。此外,当该人已经有帐户时,它永远不会输出错误。问题是,在某些情况下,如果还没有帐户,它也可以工作。查询完全相同。
根据 文档,
所有 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;
那么就没有错误,并且不做任何事情是被尊重的。
首先让我们通过一个简单的例子来看看错误原因。这是将产品映射到类别的表格。
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
,它们都应该具有唯一的约束。
解决此问题的简单方法是将冲突列设置为 UNIQUE
我没有机会玩 UPSERT,但我认为你有一个案例 文档:
请注意,这意味着非部分唯一索引(唯一索引 没有谓词)将被推断(因此被 ON CONFLICT 使用) 如果有这样一个满足所有其他标准的指数可用。如果 尝试推理不成功,出现错误。
我通过为要包含在 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;
如果列已存在,您可以键入以下命令:
ALTER TABLE table_name
ADD CONSTRAINT unique_column_name UNIQUE (column_name);
替换为您的
table_name
和 column_name