Сгенерированный столбец Postgres не является неизменным

Я добавляю несколько столбцов в таблицу и хочу создать сгенерированный столбец, объединяющий их вместе, который я буду использовать для уникального индекса. Когда я пытаюсь добавить столбец, я получаю сообщение об ошибке ERROR: generation expression is not immutable.

Я следовал решению из этого вопроса и специально использую CASE и || для конкатенации строк, которые должны быть immutable.

ALTER TABLE tag
  ADD COLUMN prefix VARCHAR(4) NOT NULL,
  ADD COLUMN middle BIGINT NOT NULL,
  ADD COLUMN postfix VARCHAR(4), -- nullable
  -- VARCHAR size is 4 prefix + 19 middle + 4 postfix + 2 delimiter
  ADD COLUMN tag_id VARCHAR(29) NOT NULL GENERATED ALWAYS AS
    (CASE WHEN postfix IS NULL THEN prefix || '-' || middle
          ELSE prefix || '-' || middle || '-' || postfix
          END
    ) STORED;
CREATE UNIQUE INDEX unq_tag_tag_id ON tag(tag_id);

В списке рассылки postgres один из участников уточняет, что:

преобразование целого числа в текст, [...] не обязательно неизменный

Однако он не использует неизменяемую функцию преобразования целых чисел в текст. Кто-нибудь знает, существует ли он?

Хороший вопрос. Я так и не понял, почему этот пример работает для VARCHAR, но не для INT.

The Impaler 27.04.2023 19:25

Кажется, это работает с приведением к text, т.е. middle::text

Marmite Bomber 27.04.2023 19:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Тест в 14.1 показывает, что причиной является неявное преобразование столбца bigint в text при конкатенации (даже без case)

Явное приведение к text не приводит к ошибке - middle::text

ALTER TABLE tag
  ADD COLUMN prefix VARCHAR(4) NOT NULL,
  ADD COLUMN middle BIGINT NOT NULL,
  ADD COLUMN postfix VARCHAR(4), -- nullable
  -- VARCHAR size is 4 prefix + 19 middle + 4 postfix + 2 delimiter
  ADD COLUMN tag_id VARCHAR(29) NOT NULL GENERATED ALWAYS AS
    (CASE WHEN postfix IS NULL THEN prefix || '-' || middle::text
          ELSE prefix || '-' || middle::text || '-' || postfix
          END
    ) STORED;

Ответ Marmite Bomber показывает решение; позвольте мне добавить объяснение.

Есть два оператора конкатенации для text:

SELECT oid, oprname,
       oprleft::regtype,
       oprright::regtype,
       oprcode
FROM pg_operator
WHERE oprname = '||'
  AND oprleft = 'text'::regtype;

 oid  │ oprname │ oprleft │  oprright   │  oprcode   
══════╪═════════╪═════════╪═════════════╪════════════
  654 │ ||      │ text    │ text        │ textcat
 2779 │ ||      │ text    │ anynonarray │ textanycat
(2 rows)

Первый оператор объединяет text с text, второй объединяет text с чем-либо еще.

Давайте рассмотрим волатильность этих двух функций:

SELECT oid, proname, provolatile
FROM pg_proc
WHERE pronamespace = 'pg_catalog'::regnamespace
  AND proname IN ('textcat', 'textanycat');

 oid  │  proname   │ provolatile 
══════╪════════════╪═════════════
 1258 │ textcat    │ i
 2003 │ textanycat │ s
(2 rows)

Таким образом, если вы объедините text и bigint, эта операция не будет IMMUTABLE, но приведение bigint к text сначала создаст операцию IMMUTABLE.

Этот оператор не является неизменным не из-за объединения с целыми числами. Но anynonarray может быть любым типом данных, например timestamp with time zone, строковое представление которого зависит от текущей настройки timezone.

Спасибо за внутренний взгляд на двигатель. Однако я до сих пор не могу концептуально понять, почему объединение INT может сделать его неизменяемым. Можете ли вы показать пример? Или... может быть, должен быть третий вариант оператора для варианта (text.int)?

The Impaler 28.04.2023 01:40

@TheImpaler Я добавил пояснение к ответу.

Laurenz Albe 28.04.2023 07:33

Спасибо за замечание. ИМХО, я думаю, что второй случай должен быть разделен на два разных, так как BIGINT ведет себя совсем по-другому по сравнению с TIMESTAMP WITH TIME ZONE.

The Impaler 28.04.2023 15:30

@TheImpaler Вы предлагаете, чтобы у нас была функция конкатенации для каждого типа данных (или, по крайней мере, для каждого предпочтительного типа данных)? Вы можете отправить патч...

Laurenz Albe 28.04.2023 16:58

Ты прав. Жаловаться легко; принятие мер, с другой стороны, требует гораздо больше усилий. Не могу сделать это прямо сейчас, но я хотел бы помочь с PostgreSQL в будущем. Спасибо за ругань ;)

The Impaler 28.04.2023 18:56

Другие вопросы по теме