Как включить исключенные строки В возврат из INSERT ... on CONFLICT

у меня есть эта таблица (сгенерированная Django):

CREATE TABLE feeds_person (
  id serial PRIMARY KEY,
  created timestamp with time zone NOT NULL,
  modified timestamp with time zone NOT NULL,
  name character varying(4000) NOT NULL,
  url character varying(1000) NOT NULL,
  email character varying(254) NOT NULL,
  CONSTRAINT feeds_person_name_ad8c7469_uniq UNIQUE (name, url, email)
);

Я пытаюсь массово вставить много данных, используя INSERT С ON CONFLICT предложения.

морщина в том, что мне нужно получить id обратно все строк, независимо от того, существуют они уже или нет.

в других случаях я бы сделал что-то вроде:

INSERT INTO feeds_person (created, modified, name, url, email)
VALUES blah blah blah
ON CONFLICT (name, url, email) DO UPDATE SET url = feeds_person.url
RETURNING id

делать UPDATE заставляет оператор возвращать id из этого ряда. Кроме того, это не работает с этим столом. Я!--13-->думаю это не работает, потому что у меня есть несколько уникальных полей вместе, тогда как в других случаях я использовал этот метод, у меня было только одно уникальное поле.

Я получаю эту ошибку при попытке запустить SQL через курсор Django:

django.db.utils.ProgrammingError: ON CONFLICT DO UPDATE command cannot affect row a second time
HINT:  Ensure that no rows proposed for insertion within the same command have duplicate constrained values.

как сделать массовую вставку с этой таблицей и вернуть вставленные и существующие идентификаторы?

1 ответов


ошибка, которую вы получаете:

при конфликте команда обновления не может повлиять на строку во второй раз

указывает, что вы пытаетесь вставки в одну и ту же строку более одного раза в одной команде. Другими словами: у вас есть dupes на (name, url, email) в своем VALUES список. Сложите дубликаты (если это опция), и это должно работать. Но вам придется решить, какую строку выбрать из каждого набора дураков.

INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM  (
   VALUES
   ('blah', 'blah', 'blah', 'blah', 'blah')
   -- ... more
   ) v(created, modified, name, url, email)  -- match column list
ON     CONFLICT (name, url, email) DO UPDATE
SET    url = feeds_person.url
RETURNING id;

так как мы используем автономный VALUES выражение теперь вам нужно добавить явные приведения типов для типов, не являющихся типами по умолчанию. Например:

VALUES
    (timestamptz '2016-03-12 02:47:56+01'
   , timestamptz '2016-03-12 02:47:56+01'
   , 'n3', 'u3', 'e3')
   ...

код timestamptz столбцам нужен явный тип cast, в то время как строковые типы могут работать с default text. (Вы все еще можете бросить varchar(n) сразу.)

есть способы определить, какую строку выбрать из каждого набора дураков:

вы правы, есть (в настоящее время) нет способа получить исключено строки RETURNING предложения. Я цитирую Postgres Wiki:

отметим, что RETURNING не делает видимым "EXCLUDED.*" псевдоним от UPDATE (только общий"TARGET.*" псевдоним виден там.) Это мысль создать раздражает неопределенность для простые, распространенные случаи [30] практически без пользы. В какой-то в будущем, мы можем продолжить путь разоблачения, если RETURNING-были вставлены и обновлены проецируемые кортежи, но это вероятно, не нужно делать это в первую совершенную итерацию функция [31].

, вы не должны обновлять строки, которые не должны обновляться. Пустые обновления почти так же дороги, как обычные обновления - и могут иметь непреднамеренные побочные эффекты. Вам не нужно строго UPSERT для начала, ваш случай больше похож на " Выбрать или ВСТАВЛЯТЬ." По теме:

один более чистый способ вставки набора строк будет с помощью CTEs для изменения данных:

WITH val AS (
   SELECT DISTINCT ON (name, url, email) *
   FROM  (
      VALUES 
      (timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
    , ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
      -- more (type cast only needed in 1st row)
      ) v(created, modified, name, url, email)
   )
, ins AS (
   INSERT INTO feeds_person (created, modified, name, url, email)
   SELECT created, modified, name, url, email FROM val
   ON     CONFLICT (name, url, email) DO NOTHING
   RETURNING id, name, url, email
   )
SELECT 'inserted' AS how, id FROM ins  -- inserted
UNION  ALL
SELECT 'selected' AS how, f.id         -- not inserted
FROM   val v
JOIN   feeds_person f USING (name, url, email);

добавленная сложность должна платить за большие таблицы, где INSERT это правило и SELECT исключение.

Первоначально я добавил NOT EXISTS сказуемое на последнем SELECT предотвратить дубликаты в результат. Но это было излишне. все CTEs одного запроса видят одни и те же снимки таблиц. набор вернулся с ON CONFLICT (name, url, email) DO NOTHING является взаимоисключающим для набора, возвращаемого после INNER JOIN на тех же столбцах.

к сожалению, это также открывает крошечное окно для гонки. Если. ..

  • параллельная транзакция вставляет конфликтующие строки
  • не совершал еще
  • но в конечном итоге совершает

... некоторые строки могут быть потеряны.

вы могли бы просто INSERT .. ON CONFLICT DO NOTHING, за которым следует отдельный SELECT запрос для всех строк в одной транзакции, чтобы преодолеть это. Что, в свою очередь, открывает еще один крошечное окно для гонки если параллельные транзакции могут совершать записи в таблицу между INSERT и SELECT (по умолчанию READ COMMITTED уровень изоляции). Может быть избежать с REPEATABLE READ изоляции транзакции (или строже). Или с (возможно, дорогой или даже неприемлемой) блокировкой записи на всей таблице. Вы можете получить любое поведение, которое вам нужно,но может быть цена.

по теме: