Why am I getting a an error when creating a generated column in PostgreSQL?

Sha*_*ars 9 sql postgresql database-design string-concatenation calculated-columns

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)
);
Run Code Online (Sandbox Code Playgroud)

Error: generation expression is not immutable

The goal is to populate the first name and last into the full name column.

Erw*_*ter 10

concat()是 not IMMUTABLE(仅STABLE),因为它可以调用timestamptz_out依赖于语言环境设置的数据类型输出函数(如)。Tom Lane(核心开发人员)在这里解释了它。

而且first_name || ' ' || last_name不是等同于concat(first_name, ' ', last_name)同时至少一列可以NULL

详细解释:

解决方案

要使其工作,完全按照您演示的方式:

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
, ...
);
Run Code Online (Sandbox Code Playgroud)

db<>在这里摆弄

CASE表达式尽可能快 - 比多个串联和函数调用快得多。并且完全正确。

或者如果您知道自己在做什么并拥有必要的权限,请按照此处所示创建一个IMMUTABLEconcat 函数(以替换CASE表达式):

旁白:full_name需要varchar(101)(50+50+1)才有意义。或者只是使用text列。看:

一般建议

最佳解决方案取决于您计划如何准确处理 NULL 值(和空字符串)。我可能不会添加生成的列,这通常比即时连接全名更昂贵且更容易出错。考虑一个视图。或者封装精确连接逻辑的函数。

有关的:


Gor*_*off 6

这适用于||运算符:

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)
);
Run Code Online (Sandbox Code Playgroud)

我不确定为什么concat()被认为是可变的技术原因,但||事实并非如此。

如果你想处理NULL列中的值,那就有点复杂了。我可能会推荐:

trim(both ' ' from
     (' ' || coalesce(first_name, '') || ' ' || coalesce(last_name, '')
     )
    )
Run Code Online (Sandbox Code Playgroud)

当然,这与您的表达式并不完全相同,因为它删除了名称开头和结尾的空格。