为什么在 PostgreSQL 中创建生成列时出现错误?
Why am I getting a an error when creating a generated column in PostgreSQL?
CREATE TABLE my_app.person
(
person_id smallserial NOT NULL,
first_name character varying(50),
last_name character varying(50),
full_name character varying(100) generated always as (concat(first_name, ' ', last_name)) STORED,
birth_date date,
created_timestamp timestamp default current_timestamp,
PRIMARY KEY (person_id)
);
错误:生成表达式不是不可变的
目标是将名字和姓氏填充到全名列中。
这适用于 ||
运算符:
CREATE TABLE person (
person_id smallserial NOT NULL,
first_name character varying(50),
last_name character varying(50),
full_name character varying(100) generated always as (first_name || ' ' || last_name) STORED,
birth_date date,
created_timestamp timestamp default current_timestamp,
PRIMARY KEY (person_id)
);
我不确定 concat()
被认为是可变的,但 ||
不是的技术原因。
如果要处理列中的 NULL
个值,则稍微复杂一些。我可能会推荐:
trim(both ' ' from
(' ' || coalesce(first_name, '') || ' ' || coalesce(last_name, '')
)
)
当然,这与您的表达式不完全相同,因为它删除了名称开头和结尾的空格。
concat()
函数不是 IMMUTABLE
(仅 STABLE
),因为它可以调用依赖于语言环境设置的数据类型输出函数(如 timestamptz_out
)。 Tom Lane (core developer) explains it here.
而first_name || ' ' || last_name
是不是等同于concat(first_name, ' ', last_name)
而至少一列可以是NULL
.
详细解释:
- Combine two columns and add into one new column
解决方案
要使其正常工作,请完全按照您演示的方式进行:
CREATE TABLE person (
person_id smallserial PRIMARY KEY
, first_name varchar(50)
, last_name varchar(50)
, full_name varchar(101) GENERATED ALWAYS AS
(CASE WHEN first_name IS NULL THEN last_name
WHEN last_name IS NULL THEN first_name
ELSE first_name || ' ' || last_name END) STORED
, ...
);
db<>fiddle here
CASE
表达式的速度非常快 - 比多重连接和函数调用快得多。完全正确。
或、如果您知道自己在做什么并且拥有必要的权限,创建一个IMMUTABLE
concat 函数 如此处所示(以替换 CASE
表达式):
旁白:full_name
需要 varchar(101)
(50+50+1) 才有意义。或者只使用 text
列。参见:
- Any downsides of using data type "text" for storing strings?
一般建议
最佳解决方案取决于您计划如何准确处理 NULL 值(和空字符串)。我可能 不会 添加生成的列。这通常比即时连接全名更昂贵且更容易出错。考虑封装串联逻辑的视图或函数。
相关:
- Computed / calculated / virtual / derived columns in PostgreSQL
- Store common query as column?
CREATE TABLE my_app.person
(
person_id smallserial NOT NULL,
first_name character varying(50),
last_name character varying(50),
full_name character varying(100) generated always as (concat(first_name, ' ', last_name)) STORED,
birth_date date,
created_timestamp timestamp default current_timestamp,
PRIMARY KEY (person_id)
);
错误:生成表达式不是不可变的
目标是将名字和姓氏填充到全名列中。
这适用于 ||
运算符:
CREATE TABLE person (
person_id smallserial NOT NULL,
first_name character varying(50),
last_name character varying(50),
full_name character varying(100) generated always as (first_name || ' ' || last_name) STORED,
birth_date date,
created_timestamp timestamp default current_timestamp,
PRIMARY KEY (person_id)
);
我不确定 concat()
被认为是可变的,但 ||
不是的技术原因。
如果要处理列中的 NULL
个值,则稍微复杂一些。我可能会推荐:
trim(both ' ' from
(' ' || coalesce(first_name, '') || ' ' || coalesce(last_name, '')
)
)
当然,这与您的表达式不完全相同,因为它删除了名称开头和结尾的空格。
concat()
函数不是 IMMUTABLE
(仅 STABLE
),因为它可以调用依赖于语言环境设置的数据类型输出函数(如 timestamptz_out
)。 Tom Lane (core developer) explains it here.
而first_name || ' ' || last_name
是不是等同于concat(first_name, ' ', last_name)
而至少一列可以是NULL
.
详细解释:
- Combine two columns and add into one new column
解决方案
要使其正常工作,请完全按照您演示的方式进行:
CREATE TABLE person (
person_id smallserial PRIMARY KEY
, first_name varchar(50)
, last_name varchar(50)
, full_name varchar(101) GENERATED ALWAYS AS
(CASE WHEN first_name IS NULL THEN last_name
WHEN last_name IS NULL THEN first_name
ELSE first_name || ' ' || last_name END) STORED
, ...
);
db<>fiddle here
CASE
表达式的速度非常快 - 比多重连接和函数调用快得多。完全正确。
或、如果您知道自己在做什么并且拥有必要的权限,创建一个IMMUTABLE
concat 函数 如此处所示(以替换 CASE
表达式):
旁白:full_name
需要 varchar(101)
(50+50+1) 才有意义。或者只使用 text
列。参见:
- Any downsides of using data type "text" for storing strings?
一般建议
最佳解决方案取决于您计划如何准确处理 NULL 值(和空字符串)。我可能 不会 添加生成的列。这通常比即时连接全名更昂贵且更容易出错。考虑封装串联逻辑的视图或函数。
相关:
- Computed / calculated / virtual / derived columns in PostgreSQL
- Store common query as column?