Uma pergunta muito frequente aqui é como fazer um upsert, que é o que o MySQL chama INSERT ... ON DUPLICATE UPDATE
e o padrão suporta como parte da MERGE
operação.
Dado que o PostgreSQL não o suporta diretamente (antes da página 9.5), como você faz isso? Considere o seguinte:
CREATE TABLE testtable (
id integer PRIMARY KEY,
somedata text NOT NULL
);
INSERT INTO testtable (id, somedata) VALUES
(1, 'fred'),
(2, 'bob');
Agora imagine que você quer "Upsert" as tuplas (2, 'Joe')
, (3, 'Alan')
, de modo que os novos conteúdos de tabela seria:
(1, 'fred'),
(2, 'Joe'), -- Changed value of existing tuple
(3, 'Alan') -- Added new tuple
É disso que as pessoas estão falando quando discutem um upsert
. Fundamentalmente, qualquer abordagem deve ser segura na presença de várias transações trabalhando na mesma tabela - usando bloqueio explícito ou defendendo-se contra as condições de corrida resultantes.
Este tópico é discutido extensivamente no Insert, na atualização duplicada no PostgreSQL? , mas trata-se de alternativas à sintaxe do MySQL, e aumentou um pouco de detalhes não relacionados ao longo do tempo. Estou trabalhando em respostas definitivas.
Essas técnicas também são úteis para "inserir se não existir, caso contrário, não faça nada", ou seja, "inserir ... ao ignorar chave duplicada".
fonte
Respostas:
9.5 e mais recente:
PostgreSQL 9.5 e suporte mais recente
INSERT ... ON CONFLICT UPDATE
(eON CONFLICT DO NOTHING
), ou seja, upsert.Comparação com
ON DUPLICATE KEY UPDATE
.Explicação rápida .
Para uso, consulte o manual - especificamente a cláusula conflito_ação no diagrama de sintaxe e o texto explicativo .
Ao contrário das soluções para a 9.4 e anteriores fornecidas abaixo, esse recurso funciona com várias linhas conflitantes e não requer bloqueio exclusivo ou loop de repetição.
O commit adicionando o recurso está aqui e a discussão sobre seu desenvolvimento está aqui .
Se você está na versão 9.5 e não precisa ser compatível com versões anteriores, pode parar de ler agora .
9.4 e mais velhos:
O PostgreSQL não tem nenhum built-in
UPSERT
MERGE
recurso interno (ou ) e é muito difícil fazê-lo de maneira eficiente em face do uso simultâneo.Este artigo discute o problema em detalhes úteis .
Em geral, você deve escolher entre duas opções:
Loop de repetição de linha individual
O uso de upserts de linha individuais em um loop de repetição é a opção razoável se você desejar várias conexões simultaneamente tentando executar inserções.
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 insere corridas, ao contrário das soluções mais ingênuas. Funcionará apenas em
READ COMMITTED
modo e só será seguro se for a única coisa que você fizer na transação. A função não funcionará corretamente se acionadores 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 uma upsert em massa, conforme descrito abaixo.
Muitas soluções tentadas para esse problema não consideram reversões, portanto resultam em atualizações incompletas. Duas transações competem entre si; um deles com sucesso
INSERT
s; o outro recebe um erro de chave duplicada e, emUPDATE
vez disso, executa um . OsUPDATE
blocos aguardandoINSERT
a reversão ou confirmação. Quando é revertida, aUPDATE
nova verificação da condição corresponde a zero linhas, mesmo que asUPDATE
confirmações não tenham feito o upsert esperado. Você deve verificar as contagens da linha 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:
então, quando dois executam ao mesmo tempo, existem vários modos de falha. Uma é a questão já discutida com uma nova verificação de atualização. Outro é onde ambos
UPDATE
ao mesmo tempo, combinando zero linhas e continuando. Em seguida, ambos fazem oEXISTS
teste, o que acontece antes doINSERT
. Ambos têm zero linhas, e ambos fazem oINSERT
. 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 o SQL inteligente, mas não pode. Você precisa verificar a contagem de linhas ou manipular erros de chave duplicados (dependendo da abordagem escolhida) e tentar novamente.
Por favor, não role sua própria solução para isso. Como na fila de mensagens, provavelmente está errado.
Upsert a granel com trava
Às vezes, você deseja fazer uma upsert em massa, em que possui um novo conjunto de dados que deseja mesclar em um conjunto de dados existente mais antigo. Isso é muito mais eficiente do que upserts individuais de linha e deve ser preferido sempre que possível.
Nesse caso, você normalmente segue o seguinte processo:
CREATE
umaTEMPORARY
mesaCOPY
ou insira em massa os novos dados na tabela temporáriaLOCK
a tabela de destinoIN EXCLUSIVE MODE
. Isso permite que outras transaçõesSELECT
, mas não façam alterações na tabela.Faça um
UPDATE ... FROM
dos registros existentes usando os valores na tabela temporária;Faça uma
INSERT
das linhas que ainda não existem na tabela de destino;COMMIT
, liberando a trava.Por exemplo, para o exemplo fornecido na pergunta, usando valores múltiplos
INSERT
para preencher a tabela temporária:Leitura relacionada
MERGE
no wiki do PostgreSQLA respeito
MERGE
?Padrão SQL
MERGE
Na verdade, o possui semânticas de concorrência mal definidas 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 com segurança de simultaneidade. Há muitos conselhos para as pessoas que usam outros DBMSes para usar
MERGE
DBMSes para upserts, mas na verdade está errado.Outros bancos de dados:
INSERT ... ON DUPLICATE KEY UPDATE
no MySQLMERGE
do MS SQL Server (mas veja acima sobreMERGE
problemas)MERGE
da Oracle (mas veja acima sobreMERGE
problemas)fonte
MERGE
para SQL Server e Oracle são incorretas e propensas a condições de corrida, conforme observado acima. Você precisará examinar cada DBMS especificamente para descobrir como lidar com eles; na verdade, só posso oferecer conselhos sobre o PostgreSQL. A única maneira de fazer um upsert seguro com várias linhas no PostgreSQL será se o suporte ao upsert nativo for adicionado ao servidor núcleo.Estou tentando contribuir com outra solução para o problema de inserção única nas versões anteriores ao 9.5 do PostgreSQL. A idéia é simplesmente tentar executar primeiro a inserção e, caso o registro já esteja presente, atualizá-lo:
Observe que esta solução pode ser aplicada apenas se não houver exclusões de linhas da tabela .
Eu não sei sobre a eficiência desta solução, mas me parece razoável o suficiente.
fonte
insert on update
Aqui estão alguns exemplos para
insert ... on conflict ...
(página 9.5+ ):fonte
SQLAlchemy upsert para Postgres> = 9.5
Como a postagem grande acima cobre muitas abordagens SQL diferentes para as versões do Postgres (não apenas as 9.5 como na pergunta), gostaria de adicionar como fazê-lo no SQLAlchemy se você estiver usando o Postgres 9.5. Em vez de implementar seu próprio upsert, você também pode usar as funções do SQLAlchemy (que foram adicionadas no SQLAlchemy 1.1). Pessoalmente, eu recomendaria usá-los, se possível. Não apenas por conveniência, mas também porque permite ao PostgreSQL lidar com quaisquer condições de corrida que possam ocorrer.
Postagem cruzada de outra resposta que eu dei ontem ( https://stackoverflow.com/a/44395983/2156909 )
O SQLAlchemy suporta
ON CONFLICT
agora com dois métodoson_conflict_do_update()
eon_conflict_do_nothing()
:Copiando da documentação:
http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=conflict#insert-on-conflict-upsert
fonte
Testado no Postgresql 9.3
fonte
SERIALIZABLE
isolamento, abortaria com uma falha de serialização; caso contrário, provavelmente obteria uma violação única. Não reinvente o upsert, a reinvenção estará errada. UseINSERT ... ON CONFLICT ...
. Se o seu PostgreSQL é muito antigo, atualize-o.INSERT ... ON CLONFLICT ...
não se destina ao carregamento em massa. Na sua postagem, oLOCK TABLE testtable IN EXCLUSIVE MODE;
CTE é uma solução alternativa para obter coisas atômicas. Não ?insert ... where not exists ...
ou similar, é claro.Como essa pergunta foi encerrada, estou postando aqui como você faz isso usando SQLAlchemy. Por recursão, ele tenta novamente uma inserção ou atualização em massa para combater condições de corrida e erros de validação.
Primeiro as importações
Agora, algumas funções auxiliares
E, finalmente, a função upsert
Veja como você o usa
A vantagem disso
bulk_save_objects
é que ele pode lidar com relacionamentos, verificação de erros etc. na inserção (ao contrário das operações em massa ).fonte
SERIALIZABLE
transações e lidar com falhas de serialização, mas é lento. Você precisa de tratamento de erros e um loop de repetição. Veja minha resposta e a seção "leitura relacionada".