PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como incluir linhas excluídas em RETURNING from INSERT ... ON CONFLICT


O erro que você recebe:

O comando ON CONFLICT DO UPDATE não pode afetar a linha uma segunda vez

indica que você está tentando upser a mesma linha mais de uma vez em um único comando. Em outras palavras:você tem dupes em (name, url, email) em seus VALUES Lista. Dobre duplicatas (se for uma opção) e deve funcionar. Mas você terá que decidir qual linha escolher de cada conjunto de dupes.
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;

Como usamos um VALUES independente expressão agora, você precisa adicionar conversões de tipo explícitas para tipos não padrão. Como:
VALUES
    (timestamptz '2016-03-12 02:47:56+01'
   , timestamptz '2016-03-12 02:47:56+01'
   , 'n3', 'u3', 'e3')
   ...

Seu timestamptz as colunas precisam de um cast de tipo explícito, enquanto os tipos de string podem operar com o padrão text . (Você ainda pode transmitir para varchar(n) agora mesmo.)

Existem maneiras de determinar qual linha escolher de cada conjunto de dupes:
  • Selecionar a primeira linha em cada grupo GROUP BY?

Você está certo, não há (atualmente) nenhuma maneira de ser excluído linhas no RETURNING cláusula. Cito o Wiki do Postgres:

Observe que RETURNING não torna visível o "EXCLUDED.* " alias do UPDATE (apenas o genérico "TARGET.* " o alias está visível lá). Acredita-se que isso cria uma ambiguidade irritante para os casos simples e comuns [30] com pouco ou nenhum benefício. Em algum momento no futuro, poderemos buscar uma maneira de expor seRETURNING - as tuplas projetadas foram inseridas e atualizadas, mas isso provavelmente não precisa ser feito na primeira iteração confirmada do recurso [31].

No entanto , você não deve atualizar linhas que não deveriam ser atualizadas. Atualizações vazias são quase tão caras quanto atualizações regulares - e podem ter efeitos colaterais indesejados. Você não precisa estritamente de UPSERT para começar, seu caso se parece mais com "SELECT ou INSERT". Relacionado:
  • O SELECT ou INSERT está em uma função propensa a condições de corrida?

Um maneira mais limpa de inserir um conjunto de linhas seria com CTEs de modificação de dados:
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);

A complexidade adicional deve pagar por grandes tabelas onde INSERT é a regra e SELECT a exceção.

Originalmente, eu adicionei um NOT EXISTS predicado no último SELECT para evitar duplicatas no resultado. Mas isso era redundante. Todas as CTEs de uma única consulta veem os mesmos instantâneos de tabelas. O conjunto retornou com ON CONFLICT (name, url, email) DO NOTHING é mutuamente exclusivo para o conjunto retornado após o INNER JOIN nas mesmas colunas.

Infelizmente, isso também abre uma pequena janela para uma condição de corrida . Se ...
  • uma transação simultânea insere linhas conflitantes
  • ainda não se comprometeu
  • mas se compromete eventualmente

... algumas linhas podem ser perdidas.

Você pode apenas INSERT .. ON CONFLICT DO NOTHING , seguido por um SELECT separado consulta para todas as linhas - dentro da mesma transação para superar isso. O que, por sua vez, abre outra pequena janela para uma condição de corrida se transações simultâneas podem confirmar gravações na tabela entre INSERT e SELECT (no padrão READ COMMITTED nível de isolamento). Pode ser evitado com REPEATABLE READ isolamento de transação (ou mais estrito). Ou com um bloqueio de gravação (possivelmente caro ou até inaceitável) em toda a mesa. Você pode obter qualquer comportamento que precisar, mas pode haver um preço a pagar.

Relacionado:
  • Como usar RETURNING com ON CONFLICT no PostgreSQL?
  • Retorne linhas de INSERT com ON CONFLICT sem precisar atualizar