Почему я получаю сообщение об ошибке при создании сгенерированного столбца в PostgreSQL? - PullRequest
0 голосов
/ 16 февраля 2020
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)
);

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

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

Ответы [ 2 ]

2 голосов
/ 16 февраля 2020

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

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

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

1 голос
/ 16 февраля 2020

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

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

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

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

Общие рекомендации

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

Связанные:

...