为什么在 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?