如何引用在 ON CONFLICT 中使用函数的唯一索引?
How do I reference a unique index that uses a function in ON CONFLICT?
我正在使用 postgres 9.5.3,我有一个 table 这样的:
CREATE TABLE packages (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL
);
我定义了一个函数,canonical_name
,像这样:
CREATE FUNCTION canonical_name(text) RETURNS text AS $$
SELECT replace(lower(), '-', '_')
$$ LANGUAGE SQL;
我已经为这个 table 添加了一个使用函数的唯一索引:
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,我想不通我需要如何指定冲突目标。 The documentation 好像说我可以指定一个索引表达式:
where conflict_target can be one of:
( { index_column_name | ( index_expression ) } [ COLLATE collation ] [ opclass ] [, ...] ) [ WHERE index_predicate ]
ON CONSTRAINT constraint_name
但是我尝试过的所有这些下面的东西都会产生如图所示的错误,而不是有效的 upsert。
我已经尝试匹配我指定的索引表达式:
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)
那么如何指定我要使用这个索引呢?或者这是一个错误?
重要说明:此行为只能在 9.5.4 之前的版本上观察到。这是在 9.5.4 中修复的错误。答案的其余部分描述了错误行为:
正如您所发现的,您只能为唯一的 constraint 指定表达式,而不能为唯一的 index 指定表达式。
这有点令人困惑,因为在幕后,唯一约束只是一个唯一索引(但这被认为是一个实现细节)。
更糟糕的是,您不能在包含表达式的唯一索引上定义唯一约束——我不确定原因是什么,但怀疑 SQL 标准。
您可以这样做的一种方法是添加一个人工列,由触发器填充“规范名称”并定义对该列的约束。我承认这样不好。
但是,正确的解决方案是升级到 PostgreSQL 9.5 的最新次要版本。
我正在使用 postgres 9.5.3,我有一个 table 这样的:
CREATE TABLE packages (
id SERIAL PRIMARY KEY,
name VARCHAR NOT NULL
);
我定义了一个函数,canonical_name
,像这样:
CREATE FUNCTION canonical_name(text) RETURNS text AS $$
SELECT replace(lower(), '-', '_')
$$ LANGUAGE SQL;
我已经为这个 table 添加了一个使用函数的唯一索引:
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,我想不通我需要如何指定冲突目标。 The documentation 好像说我可以指定一个索引表达式:
where conflict_target can be one of:
( { index_column_name | ( index_expression ) } [ COLLATE collation ] [ opclass ] [, ...] ) [ WHERE index_predicate ]
ON CONSTRAINT constraint_name
但是我尝试过的所有这些下面的东西都会产生如图所示的错误,而不是有效的 upsert。
我已经尝试匹配我指定的索引表达式:
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)
那么如何指定我要使用这个索引呢?或者这是一个错误?
重要说明:此行为只能在 9.5.4 之前的版本上观察到。这是在 9.5.4 中修复的错误。答案的其余部分描述了错误行为:
正如您所发现的,您只能为唯一的 constraint 指定表达式,而不能为唯一的 index 指定表达式。 这有点令人困惑,因为在幕后,唯一约束只是一个唯一索引(但这被认为是一个实现细节)。
更糟糕的是,您不能在包含表达式的唯一索引上定义唯一约束——我不确定原因是什么,但怀疑 SQL 标准。
您可以这样做的一种方法是添加一个人工列,由触发器填充“规范名称”并定义对该列的约束。我承认这样不好。
但是,正确的解决方案是升级到 PostgreSQL 9.5 的最新次要版本。