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

Como UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) no PostgreSQL?

9.5 e mais recente:


PostgreSQL 9.5 e suporte mais recente INSERT ... ON CONFLICT (key) DO UPDATE (e ON CONFLICT (key) DO NOTHING ), ou seja, upsert.

Comparação com ON DUPLICATE KEY UPDATE .

Explicação rápida.

Para uso veja o manual - especificamente o conflict_action cláusula no diagrama de sintaxe e o texto explicativo.

Ao contrário das soluções para 9.4 e anteriores fornecidas abaixo, esse recurso funciona com várias linhas conflitantes e não requer bloqueio exclusivo ou um loop de repetição.

O commit que adiciona o recurso está aqui e a discussão em torno de seu desenvolvimento está aqui.

Se você está no 9.5 e não precisa ser compatível com versões anteriores, pode parar de ler agora .

9.4 e anteriores:


PostgreSQL não tem nenhum UPSERT embutido (ou MERGE ) e fazê-lo com eficiência em face do uso simultâneo é muito difícil.

Este artigo discute o problema em detalhes úteis.

Em geral, você deve escolher entre duas opções:
  • Operações de inserção/atualização individuais em um loop de repetição; ou
  • Bloqueando a tabela e fazendo a mesclagem em lote

Loop de repetição de linha individual


Usar upserts de linha individuais em um loop de repetição é a opção razoável se você quiser muitas conexões tentando executar inserções simultaneamente.

A documentação do PostgreSQL contém um procedimento útil que permite fazer isso em um loop dentro do banco de dados. Ele protege contra atualizações perdidas e corridas de inserção, ao contrário da maioria das soluções ingênuas. Só funcionará em READ COMMITTED e só é seguro se for a única coisa que você fizer na transação. A função não funcionará corretamente se gatilhos ou chaves exclusivas secundárias causarem violações exclusivas.

Essa estratégia é muito ineficiente. Sempre que possível, você deve enfileirar o trabalho e fazer um upsert em massa conforme descrito abaixo.

Muitas tentativas de soluções para esse problema não consideram reversões, portanto, resultam em atualizações incompletas. Duas transações competem entre si; um deles INSERT com sucesso s; o outro recebe um erro de chave duplicada e faz um UPDATE em vez de. A UPDATE blocos esperando pelo INSERT para reverter ou confirmar. Ao reverter, o UPDATE a nova verificação de condição corresponde a zero linhas, portanto, mesmo que o UPDATE commits, ele não fez o upsert que você esperava. Você deve verificar as contagens de linhas de resultados e tentar novamente quando necessário.

Algumas soluções tentadas também não consideram as corridas SELECT. Se você tentar o óbvio e simples:
-- THIS IS WRONG. DO NOT COPY IT. It's an EXAMPLE.

BEGIN;

UPDATE testtable
SET somedata = 'blah'
WHERE id = 2;

-- Remember, this is WRONG. Do NOT COPY IT.

INSERT INTO testtable (id, somedata)
SELECT 2, 'blah'
WHERE NOT EXISTS (SELECT 1 FROM testtable WHERE testtable.id = 2);

COMMIT;

então, quando dois são executados ao mesmo tempo, existem vários modos de falha. Um é o problema já discutido com uma nova verificação de atualização. Outra é onde ambos UPDATE ao mesmo tempo, combinando zero linhas e continuando. Então ambos fazem o EXISTS teste, que acontece antes o INSERT . Ambos recebem zero linhas, então ambos fazem o INSERT . Um falha com um erro de chave duplicada.

É por isso que você precisa de um loop de repetição. Você pode pensar que pode evitar erros de chave duplicados ou atualizações perdidas com SQL inteligente, mas não pode. Você precisa verificar a contagem de linhas ou lidar com erros de chave duplicada (dependendo da abordagem escolhida) e tentar novamente.

Por favor, não role sua própria solução para isso. Como no enfileiramento de mensagens, provavelmente está errado.

Upsert em massa com trava


Às vezes, você deseja fazer um upsert em massa, onde você tem um novo conjunto de dados que deseja mesclar em um conjunto de dados existente mais antigo. Isso é muito mais eficientes do que upserts de linha individuais e devem ser preferidos sempre que possível.

Nesse caso, você normalmente segue o seguinte processo:

  • CREATE a TEMPORARY tabela

  • COPY ou insira os novos dados em massa na tabela temporária

  • LOCK a tabela de destino IN EXCLUSIVE MODE . Isso permite que outras transações SELECT , mas não faça nenhuma alteração na tabela.

  • Faça um UPDATE ... FROM de registros existentes usando os valores da tabela temporária;

  • Faça um INSERT de linhas que ainda não existem na tabela de destino;

  • COMMIT , liberando a trava.

Por exemplo, para o exemplo fornecido na pergunta, usando INSERT de valores múltiplos para preencher a tabela temporária:
BEGIN;

CREATE TEMPORARY TABLE newvals(id integer, somedata text);

INSERT INTO newvals(id, somedata) VALUES (2, 'Joe'), (3, 'Alan');

LOCK TABLE testtable IN EXCLUSIVE MODE;

UPDATE testtable
SET somedata = newvals.somedata
FROM newvals
WHERE newvals.id = testtable.id;

INSERT INTO testtable
SELECT newvals.id, newvals.somedata
FROM newvals
LEFT OUTER JOIN testtable ON (testtable.id = newvals.id)
WHERE testtable.id IS NULL;

COMMIT;

Leitura relacionada

  • UPSERT página wiki
  • UPSERTismos no Postgres
  • Inserir, na atualização duplicada no PostgreSQL?
  • http://petereisentraut.blogspot.com/2010/05/merge-syntax.html
  • Upsert com uma transação
  • O SELECT ou INSERT está em uma função propensa a condições de corrida?
  • SQL MERGE no wiki do PostgreSQL
  • A maneira mais idiomática de implementar UPSERT no Postgresql atualmente

E quanto a MERGE ?


Padrão SQL MERGE na verdade, tem semântica de simultaneidade mal definida e não é adequado para upserting sem bloquear uma tabela primeiro.

É uma instrução OLAP realmente útil para mesclagem de dados, mas na verdade não é uma solução útil para upsert seguro de simultaneidade. Há muitos conselhos para pessoas que usam outros DBMSs para usar MERGE para upserts, mas na verdade está errado.

Outros bancos de dados:

  • INSERT ... ON DUPLICATE KEY UPDATE em MySQL
  • MERGE do MS SQL Server (mas veja acima sobre MERGE problemas)
  • MERGE da Oracle (mas veja acima sobre MERGE problemas)