为啥在 PostgreSQL 中创建生成的列时出现错误?

Posted

技术标签:

【中文标题】为啥在 PostgreSQL 中创建生成的列时出现错误?【英文标题】:Why am I getting a an error when creating a generated column in PostgreSQL?为什么在 PostgreSQL 中创建生成的列时出现错误? 【发布时间】:2020-05-31 14:46:08 【问题描述】:
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)
);

错误:生成表达式不是不可变的

目标是将名字和姓氏填充到全名列中。

【问题讨论】:

【参考方案1】:

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小提琴here

CASE 表达式的速度非常快 - 比多个连接和函数调用快得多。而且完全正确。

或者如果你知道你在做什么并且拥有必要的权限,创建一个IMMUTABLE concat-function,如下所示(替换CASE 表达式):

Create an immutable clone of concat_ws

除此之外: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?

【讨论】:

【参考方案2】:

这适用于|| 运算符:

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, '')
     )
    )

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

【讨论】:

是的,这没有意义。谢谢! 替换不等价。

以上是关于为啥在 PostgreSQL 中创建生成的列时出现错误?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Python 在 Flask 中创建端点时出现问题 [重复]

在 Postgresql 中创建一个数量递增的列

使用表中的列在 PostgreSQL 中创建视图

为啥以编程方式添加我的文本视图时出现 NullPointerException?

在 Postgresql 中创建一个具有多列作为参数的函数

尝试在 Kafka 中创建分区时出现 NoClassDefFoundError