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)
);

Ошибка: выражение поколения не является неизменным

Цель состоит в том, чтобы заполнить имя и фамилию в столбце полного имени.

3
ShadyBears 16 Фев 2020 в 17:19

2 ответа

Лучший ответ

concat() не является IMMUTABLE (только STABLE), потому что он может вызывать выходные функции типа данных (например, timestamptz_out), которые зависят от настроек локали. Том Лейн (основной разработчик) объясняет это здесь.

И 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 <> fiddle здесь

Выражение CASE выполняется настолько быстро, насколько это возможно - существенно быстрее, чем множественные конкатенации и вызовы функций. И точно правильно.

Или , , если вы знаете, что делаете и у вас есть необходимые привилегии, создайте IMMUTABLE concat-функцию , как показано здесь ( заменить выражение CASE):

В сторону: full_name должно быть varchar(101) (50 + 50 + 1), чтобы иметь смысл. Или просто используйте text столбцы. Видеть:

Генеральный Совет

Лучшее решение зависит от того, как именно вы планируете работать со значениями NULL (и пустыми строками). Я бы, вероятно, не добавил сгенерированный столбец, который обычно более дорогой и подвержен ошибкам, чем объединение полного имени на лету. Рассмотрим мнение. Или функция, инкапсулирующая точную логику конкатенации.

Связанный:

1
Erwin Brandstetter 17 Фев 2020 в 10:02

Это работает с оператором ||:

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

Конечно, это не точно совпадает с вашим выражением, потому что оно удаляет пробелы в начале и конце имен.

2
Gordon Linoff 16 Фев 2020 в 16:36