问题 如何引用在ON CONFLICT中使用函数的唯一索引?


我正在使用postgres 9.5.3,我有一个这样的表:

CREATE TABLE packages (
  id   SERIAL PRIMARY KEY,
  name VARCHAR NOT NULL
);

我已经定义了一个函数, canonical_name, 喜欢这个:

CREATE FUNCTION canonical_name(text) RETURNS text AS $$
    SELECT replace(lower($1), '-', '_')
$$ LANGUAGE SQL;

我为这个表添加了一个使用该函数的唯一索引:

CREATE UNIQUE INDEX index_package_name
ON packages (canonical_name(name));
CREATE INDEX
# \d+ packages
                                                  Table "public.packages"
 Column |       Type        |                       Modifiers                       | Storage  | Stats target | Description
--------+-------------------+-------------------------------------------------------+----------+--------------+-------------
 id     | integer           | not null default nextval('packages_id_seq'::regclass) | plain    |              |
 name   | character varying | not null                                              | extended |              |
Indexes:
    "packages_pkey" PRIMARY KEY, btree (id)
    "index_package_name" UNIQUE, btree (canonical_name(name::text))

而这个独特的指数正如我所期望的那样工作;它可以防止重复插入:

INSERT INTO packages (name) 
VALUES ('Foo-bar');

INSERT INTO packages (name) 
VALUES ('foo_bar');

ERROR:  duplicate key value violates unique constraint "index_package_name"
DETAIL:  Key (canonical_name(name::text))=(foo_bar) already exists.

我的问题是我想使用这个唯一索引进行upsert,我无法弄清楚我需要如何指定冲突目标。 文档 好像我说我可以指定一个索引表达式:

where conflict_target can be one of:

    ( { index_column_name | ( index_expression ) } [ COLLATE collation ] [ opclass ] [, ...] ) [ WHERE index_predicate ]
    ON CONSTRAINT constraint_name

但是我所尝试的所有这些事情都会产生如图所示的错误,而不是工作上的错误。

我已经尝试匹配索引表达式,因为我指定它:

INSERT INTO packages (name)
VALUES ('foo_bar')
ON CONFLICT (canonical_name(name)) 
DO UPDATE SET name = EXCLUDED.name;

ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification

将索引表达式匹配为 \d+ 表明:

INSERT INTO packages (name)
VALUES ('foo_bar')
ON CONFLICT (canonical_name(name::text)) 
DO UPDATE SET name = EXCLUDED.name;


ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification

只需命名唯一索引所在的列:

INSERT INTO packages (name)
VALUES ('foo_bar')
ON CONFLICT (name) 
DO UPDATE SET name = EXCLUDED.name;

ERROR:  there is no unique or exclusion constraint matching the ON CONFLICT specification

改为使用索引名称:

INSERT INTO packages (name)
VALUES ('foo_bar')
ON CONFLICT (index_package_name) 
DO UPDATE SET name = EXCLUDED.name;

ERROR:  column "index_package_name" does not exist
LINE 3: ON CONFLICT (index_package_name)

又怎样  我指定我想使用这个索引?或者这是一个错误?


6925
2017-08-14 17:57


起源



答案:


不幸的是,你不能用PostgreSQL做到这一点。

如您所见,您只能指定唯一的表达式 约束 而不是一个独特的 指数。 这有点令人困惑,因为在引擎盖下,唯一约束只是一个唯一索引(但这被认为是一个实现细节)。

更糟糕的是,您无法在包含表达式的唯一索引上定义唯一约束 - 我不确定原因是什么,但怀疑SQL标准。

您可以这样做的一种方法是添加一个人工列,通过触发器填充“规范名称”并在该列上定义约束。我承认这不好。

或者,您可以在整个过程中使用可序列化事务,并定义一个BEFORE触发器来验证您的状况。但这需要您处理代码中的序列化失败,这很麻烦。


9
2017-08-14 19:21



经过一些实验,(使用函数提升索引和检查约束)我认为 人造柱 是唯一的解决方法。 - wildplasser
BTW,不确定9.5.3但是9.5.4 ON CONFLICT (canonical_name(name)) 变体工作正常。 PostgreSQL 9.5.4 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2, 64-bit - Abelisto
@Abelisto:该死的:我以为我测试了9.5 #head测试,但它看起来是9.5.2 ...... - wildplasser
Laurenz:谢谢你的详细解答,你对postgres <= 9.5.3是正确的,你的答案对于无法升级的人来说可能是最有帮助的。 @Abelisto:我升级到postgres 9.6beta2(现在可以在homebrew中使用下一个版本)并确认ON CONFLICT(canonical_name(name))工作得很好!如果您将评论添加为答案,我很乐意接受它! - carols10cents
我不明白,目前的文件(postgresql.org/docs/current/static/sql-insert.html)明确指出您可以为constraint_name使用多个选项,包括索引名称。 - Pak


答案:


不幸的是,你不能用PostgreSQL做到这一点。

如您所见,您只能指定唯一的表达式 约束 而不是一个独特的 指数。 这有点令人困惑,因为在引擎盖下,唯一约束只是一个唯一索引(但这被认为是一个实现细节)。

更糟糕的是,您无法在包含表达式的唯一索引上定义唯一约束 - 我不确定原因是什么,但怀疑SQL标准。

您可以这样做的一种方法是添加一个人工列,通过触发器填充“规范名称”并在该列上定义约束。我承认这不好。

或者,您可以在整个过程中使用可序列化事务,并定义一个BEFORE触发器来验证您的状况。但这需要您处理代码中的序列化失败,这很麻烦。


9
2017-08-14 19:21



经过一些实验,(使用函数提升索引和检查约束)我认为 人造柱 是唯一的解决方法。 - wildplasser
BTW,不确定9.5.3但是9.5.4 ON CONFLICT (canonical_name(name)) 变体工作正常。 PostgreSQL 9.5.4 on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2, 64-bit - Abelisto
@Abelisto:该死的:我以为我测试了9.5 #head测试,但它看起来是9.5.2 ...... - wildplasser
Laurenz:谢谢你的详细解答,你对postgres <= 9.5.3是正确的,你的答案对于无法升级的人来说可能是最有帮助的。 @Abelisto:我升级到postgres 9.6beta2(现在可以在homebrew中使用下一个版本)并确认ON CONFLICT(canonical_name(name))工作得很好!如果您将评论添加为答案,我很乐意接受它! - carols10cents
我不明白,目前的文件(postgresql.org/docs/current/static/sql-insert.html)明确指出您可以为constraint_name使用多个选项,包括索引名称。 - Pak