【问题标题】: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)
);

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

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

【问题讨论】:

    标签: sql postgresql database-design string-concatenation calculated-columns


    【解决方案1】:

    concat() 不是IMMUTABLE(仅STABLE),因为它可以调用依赖于区域设置的数据类型输出函数(如timestamptz_out)。 Tom Lane (core developer) explains it here.

    并且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
    , ...
    );
    

    db小提琴here

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

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

    除此之外:full_name 必须是 varchar(101) (50+50+1) 才有意义。或者只使用 text 列。见:

    一般建议

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

    相关:

    【讨论】:

      【解决方案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, '')
           )
          )
      

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

      【讨论】:

      • 是的,这没有意义。谢谢!
      • 替换不等价。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-16
      • 1970-01-01
      • 2019-02-06
      • 2016-08-13
      • 2021-07-27
      相关资源
      最近更新 更多