Возвратите данные из подзапроса, используемого в INSERT в общем табличном выражении

Я пытаюсь переместить bytea данные из одной таблицы в другую, обновляя ссылки в одном запросе.

Поэтому я хотел бы вернуть данные из запроса, используемого для вставки, который не используется для вставки.

INSERT INTO file_data (data)
  select image from task_log where image is not null
RETURNING id as file_data_id, task_log.id as task_log_id

Но я получаю сообщение об ошибке для этого запроса:

[42P01] ERROR: missing FROM-clause entry for table "task_log"

Я хочу сделать что-то вроде:

WITH inserted AS (
  INSERT INTO file_data (data)
    SELECT image FROM task_log WHERE image IS NOT NULL
  RETURNING id AS file_data_id, task_log.id AS task_log_id
)
UPDATE task_log
SET    task_log.attachment_id = inserted.file_data_id,
       task_log.attachment_type = 'INLINE_IMAGE'
FROM   inserted
WHERE  inserted.task_log_id = task_log.id;

Но мне не удается получить все данные, используемые для вставки, я не могу вернуть идентификатор из подвыборки.

Меня вдохновил этот ответ о том, как это сделать с помощью Common Table Expressions, но я не могу найти способ заставить его работать.


person flob    schedule 09.11.2017    source источник
comment
удалите псевдоним таблицы для L-значения в операторах UPDATE: SET task_log.attachment_id = ... --›› SET attachment_id = ... и т. д.   -  person joop    schedule 09.11.2017
comment
Так у вас есть ответ?   -  person Erwin Brandstetter    schedule 10.11.2017
comment
@ErwinBrandstetter Да! Спасибо за вашу помощь :-) Я опубликовал свое решение в качестве ответа, поскольку я прибегал к добавлению временного столбца, чтобы сохранить дубликаты. Если вы хотите, вы (или я) можете добавить его к своему ответу, чтобы сохранить один единственный ответ со всеми этими возможными решениями.   -  person flob    schedule 10.11.2017
comment
Ваш отдельный ответ просто прекрасен. И я бы не хотел, чтобы этот образ действий был в моем ответе в любом случае. Производительность намного хуже, чем у метода с использованием серийного номера, который я предложил в связанном ответе. Для добавления и удаления столбца требуются привилегии владельца, полная перезапись таблицы и эксклюзивные блокировки таблицы, что является ядом для одновременного доступа. :)   -  person Erwin Brandstetter    schedule 10.11.2017
comment
Хе-хе, хорошо, я добавил это как предупреждение к моему ответу, цитируя ваши слова. Надеюсь, это нормально :-)   -  person flob    schedule 10.11.2017
comment
Это идеально. :)   -  person Erwin Brandstetter    schedule 11.11.2017


Ответы (2)


Вам нужно правильно указать имена таблиц и псевдонимы. Кроме того, связью между двумя таблицами является столбец image (data в новой таблице file_data):

WITH inserted AS (
  INSERT INTO file_data (data)
  SELECT image
  FROM   task_log
  WHERE  image IS NOT NULL
  RETURNING id, data  -- can only reference target row
)
UPDATE task_log t
SET    attachment_id = i.id
     , attachment_type = 'INLINE_IMAGE'
FROM   inserted i
WHERE  t.image = i.data;

Как объяснялось в моем старом ответе, на который вы ссылались, image должен быть уникальным в task_log, чтобы это работало:

Я добавил метод устранения неоднозначности неуникальных значений в указанном ответе. Однако не уверен, что вам нужны дубликаты изображений в file_data.

В предложении RETURNING строки INSERT вы можете ссылаться только на столбцы из вставленной строки. Руководство:

Необязательное предложение RETURNING заставляет INSERT вычислять и возвращать значения на основе каждой фактически вставленной строки (...) Однако разрешено любое выражение, использующее столбцы таблицы.

Жирный акцент мой.

Свернуть повторяющиеся исходные значения

Если вам нужны разные записи в целевой таблице INSERT (task_log), все, что вам нужно в этом случае, это DISTINCT в начальном SELECT:

WITH inserted AS (
  INSERT INTO file_data (data)
  SELECT DISTINCT image  -- fold duplicates
  FROM   task_log
  WHERE  image IS NOT NULL
  RETURNING id, data  -- can only reference target row
)
UPDATE task_log t
SET    attachment_id = i.id
     , attachment_type = 'INLINE_IMAGE'
FROM   inserted i
WHERE  t.image = i.data;

Полученное file_data.id используется несколько раз в task_log. Имейте в виду, что несколько строк в task_log теперь указывают на одно и то же изображение в file_data. Осторожнее с обновлениями и удалениями...

person Erwin Brandstetter    schedule 09.11.2017
comment
Значит, невозможно добавить task_log.id к результату вставки? - person flob; 09.11.2017
comment
Не из коробки. Я добавил больше. - person Erwin Brandstetter; 09.11.2017
comment
Изображения не уникальны, но я пытаюсь сослаться на них по отдельности. Я попробовал ваш другой пример с row_number, но я не знаю, что вы едите на завтрак, чтобы ваш мозг дошел до понимания postgres :-) - person flob; 09.11.2017
comment
Если я использую ваш обновленный запрос, думаю, мне следует добавить select **distinct** image from task_log, чтобы не добавлять неиспользуемые записи. ? - person flob; 09.11.2017
comment
@flob: Да, чтобы складывать дубликаты, DISTINCT (или GROUP BY) в начальном SELECT — это все, что вам нужно. Однако помните о последствиях. Я добавил больше. - person Erwin Brandstetter; 09.11.2017
comment
Отлично, большое спасибо! - person flob; 09.11.2017
comment
В настоящее время я пытаюсь избежать этих осложнений, добавляя временный столбец и удаляя его в той же транзакции, я не знаю, как последствия могут быть для большой базы данных, но поскольку наша довольно мала (менее 20 тыс. изображений). alter table file_data add column task_log_id bigint; раньше, добавив этот идентификатор во вставку INSERT INTO file_data (data, task_log_id) select image, id from task_log where image is not null, а затем используя его в обновлении where inserted.task_log_id = task_log.id;. Это работает, но имеет довольно неприятный запах ... :-/ - person flob; 09.11.2017
comment
@flob: начните с четкого определения требований: вы хотите разрешить повторяющиеся значения data в file_data? То есть: вы хотите избыточно хранить изображения, если они используются в нескольких строках в task_log? Как правило, ответ нет, и в этом случае вам придется сбрасывать дубликаты. Кроме того, вам может потребоваться сделать больше, чтобы избежать дублирования между вновь вставленными и существующими строками в file_data. См.: stackoverflow.com/a/30878260/939860. - person Erwin Brandstetter; 09.11.2017
comment
Сейчас мне нужны эти дубликаты из-за приложения, но позже я должен добавить дедупликацию :-) Спасибо! - person flob; 09.11.2017

Мне нужно было реплицировать дубликаты, поэтому в итоге я добавил временный столбец для идентификатора используемой строки данных.

alter table file_data add column task_log_id bigint;
-- insert & update data
alter table file_data drop column task_log_id;

Полный сценарий перемещения был

-- A new table for any file data
CREATE TABLE file_data (
  id         BIGSERIAL PRIMARY KEY,
  data  bytea
);

-- Move data from task_log to bytes

-- Create new columns to reference file_data
alter table task_log add column attachment_type VARCHAR(50);
alter table task_log add column attachment_id bigint REFERENCES file_data;

-- add a temp column for the task_id used for the insert
alter table file_data add column task_log_id bigint;

-- insert data into file_data and set references
with inserted as (
  INSERT INTO file_data (data, task_log_id)
    select image, id from task_log where image is not null
  RETURNING id, task_log_id
)
UPDATE task_log
SET   attachment_id = inserted.id,
      attachment_type = 'INLINE_IMAGE'
FROM  inserted
where inserted.task_log_id = task_log.id;
-- delete the temp column
alter table file_data drop column task_log_id;
-- delete task_log images
alter table task_log drop column image;

Поскольку это создает некоторые мертвые данные, я впоследствии запустил vacuum full для очистки.

Но, пожалуйста, позвольте мне повторить предупреждение от @ErwinBrandstetter:

Производительность намного хуже, чем у метода с использованием серийного номера, который я предложил в связанном ответе. Для добавления и удаления столбца требуются привилегии владельца, полная перезапись таблицы и эксклюзивные блокировки таблицы, что является ядом для одновременного доступа.

person flob    schedule 10.11.2017