Vários meses atrás, aprendi com uma resposta no Stack Overflow como executar várias atualizações de uma vez no MySQL usando a seguinte sintaxe:
INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);
Agora mudei para o PostgreSQL e, aparentemente, isso não está correto. Está se referindo a todas as tabelas corretas, portanto, presumo que sejam usadas palavras-chave diferentes, mas não tenho certeza de onde isso está coberto na documentação do PostgreSQL.
Para esclarecer, quero inserir várias coisas e, se elas já existirem, atualizá-las.
sql
postgresql
upsert
sql-merge
Teifion
fonte
fonte
Respostas:
O PostgreSQL desde a versão 9.5 possui sintaxe UPSERT , com a cláusula ON CONFLICT . com a seguinte sintaxe (semelhante ao MySQL)
A busca por "upsert" nos arquivos do grupo de e-mail do postgresql leva a encontrar um exemplo de como você possivelmente quer fazer, no manual :
Existe possivelmente um exemplo de como fazer isso em massa, usando CTEs na 9.1 e acima, na lista de e-mails dos hackers :
Veja a resposta de a_horse_with_no_name para um exemplo mais claro.
fonte
excluded
refere a primeira solução aqui?excluded
tabela especial fornece acesso aos valores que você estava tentando inserir em primeiro lugar.Aviso: isso não é seguro se executado a partir de várias sessões ao mesmo tempo (veja advertências abaixo).
Outra maneira inteligente de executar um "UPSERT" no postgresql é executar duas instruções UPDATE / INSERT sequenciais, cada uma projetada para ter sucesso ou não ter efeito.
O UPDATE será bem-sucedido se uma linha com "id = 3" já existir, caso contrário não terá efeito.
O INSERT terá êxito apenas se a linha com "id = 3" ainda não existir.
Você pode combinar esses dois em uma única sequência e executá-los com uma única instrução SQL executada no seu aplicativo. Executá-los juntos em uma única transação é altamente recomendado.
Isso funciona muito bem quando executado isoladamente ou em uma tabela bloqueada, mas está sujeito a condições de corrida, o que significa que ainda poderá falhar com erro de chave duplicada se uma linha for inserida simultaneamente ou pode terminar sem nenhuma linha inserida quando uma linha for excluída simultaneamente. . Uma
SERIALIZABLE
transação no PostgreSQL 9.1 ou superior irá lidar com isso de maneira confiável ao custo de uma taxa de falha de serialização muito alta, o que significa que você precisará tentar muito. Vejo por que o upsert é tão complicado , que discute esse caso com mais detalhes.Esta abordagem também é sujeito a atualizações perdidas em
read committed
isolamento, a menos que a aplicação verifica a contagem de linhas afetadas e verifica que tanto oinsert
ou oupdate
afetou uma linha .fonte
... where not exists (select 1 from table where id = 3);
read committed
isoladamente, a menos que seu aplicativo verifique se o número de linhas é diferente de zeroinsert
ouupdate
não. Veja dba.stackexchange.com/q/78510/7788Com o PostgreSQL 9.1, isso pode ser alcançado usando um CTE gravável ( expressão de tabela comum ):
Veja estas entradas do blog:
Observe que esta solução não evita uma violação exclusiva da chave, mas não é vulnerável a atualizações perdidas.
Veja o acompanhamento de Craig Ringer em dba.stackexchange.com
fonte
UPDATE
linhas afetadas sejam afetadas.No PostgreSQL 9.5 e mais recente, você pode usar
INSERT ... ON CONFLICT UPDATE
.Veja a documentação .
Um MySQL
INSERT ... ON DUPLICATE KEY UPDATE
pode ser reformulado diretamente para aON CONFLICT UPDATE
. A sintaxe padrão do SQL também não é, ambas são extensões específicas do banco de dados. Existem boas razões paraMERGE
não ter sido usado para isso , uma nova sintaxe não foi criada apenas por diversão. (A sintaxe do MySQL também tem problemas que significam que não foi adotado diretamente).por exemplo, dada configuração:
a consulta do MySQL:
torna-se:
Diferenças:
Você deve especificar o nome da coluna (ou nome exclusivo da restrição) a ser usado para a verificação de exclusividade. Essa é a
ON CONFLICT (columnname) DO
A palavra-chave
SET
deve ser usada, como se fosse umaUPDATE
declaração normalTambém possui alguns recursos interessantes:
Você pode ter uma
WHERE
cláusulaUPDATE
(permitindo efetivamente transformar-seON CONFLICT UPDATE
emON CONFLICT IGNORE
certos valores)Os valores propostos para inserção estão disponíveis como a variável de linha
EXCLUDED
, que possui a mesma estrutura da tabela de destino. Você pode obter os valores originais na tabela usando o nome da tabela. Então, neste casoEXCLUDED.c
, será10
(porque foi o que tentamos inserir) e"table".c
será3
porque esse é o valor atual na tabela. Você pode usar uma ou ambas asSET
expressões eWHERE
cláusula.Para obter mais informações sobre o upsert, consulte Como UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) no PostgreSQL?
fonte
ON DUPLICATE KEY UPDATE
. Fiz o download do Postgres 9.5 e implementei seu código, mas estranhamente ocorre o mesmo problema no Postgres: o campo serial da chave primária não é consecutivo (existem lacunas entre as inserções e atualizações). Alguma idéia do que está acontecendo aqui? Isso é normal? Alguma idéia de como evitar esse comportamento? Obrigado.SERIAL
/SEQUENCE
ouAUTO_INCREMENT
não ter lacunas. Se você precisa de sequências sem intervalos, elas são mais complexas; você precisa usar uma mesa de contador normalmente. O Google lhe dirá mais. Mas esteja ciente de que seqüências sem intervalos impedem toda a simultaneidade da inserção.BEGIN ... EXCEPTION ...
execuções em uma subtransação são revertidas por erro, seu incremento de sequência seria revertido seINSERT
falhasse.Eu estava procurando a mesma coisa quando cheguei aqui, mas a falta de uma função genérica "upsert" me incomodou um pouco, então pensei que você poderia simplesmente passar a atualização e inserir sql como argumentos nessa função do manual
que ficaria assim:
e, talvez, para fazer o que você inicialmente queria, em lotes "upsert", você poderia usar o Tcl para dividir o sql_update e fazer o loop das atualizações individuais, o resultado da pré-execução será muito pequeno, consulte http://archives.postgresql.org/pgsql- performance / 2006-04 / msg00557.php
o custo mais alto é a execução da consulta a partir do seu código, no lado do banco de dados o custo de execução é muito menor
fonte
DELETE
menos que você bloqueie a tabela ou esteja emSERIALIZABLE
isolamento de transação no PostgreSQL 9.1 ou superior.Não há um comando simples para fazê-lo.
A abordagem mais correta é usar a função, como a dos documentos .
Outra solução (embora não tão segura) é atualizar com o retorno, verificar quais linhas foram atualizadas e inserir o restante delas.
Algo ao longo das linhas de:
assumindo id: 2 foi retornado:
É claro que ele será resgatado mais cedo ou mais tarde (em ambiente concorrente), pois há uma clara condição de corrida aqui, mas geralmente funcionará.
Aqui está um artigo mais longo e abrangente sobre o assunto .
fonte
Pessoalmente, configurei uma "regra" anexada à instrução insert. Digamos que você tenha uma tabela "dns" que registre hits de DNS por cliente em uma base de tempo:
Você queria poder reinserir linhas com valores atualizados ou criá-las se elas já não existissem. Introduziu o customer_id e a hora. Algo assim:
Atualização: isso pode falhar se inserções simultâneas estiverem acontecendo, pois gerará exceções de violação única. No entanto, a transação não finalizada continuará e terá êxito, e você só precisará repetir a transação finalizada.
No entanto, se houver muitas inserções acontecendo o tempo todo, você desejará colocar um bloqueio de tabela em torno das instruções de inserção: O bloqueio SHARE ROW EXCLUSIVE impedirá qualquer operação que possa inserir, excluir ou atualizar linhas na tabela de destino. No entanto, as atualizações que não atualizam a chave exclusiva são seguras; portanto, se nenhuma operação fizer isso, use bloqueios de aviso.
Além disso, o comando COPY não usa REGRAS, portanto, se você estiver inserindo com COPY, precisará usar gatilhos.
fonte
Eu uso essa função mesclar
fonte
update
primeiro e depois verificar o número de linhas atualizadas. (Veja a resposta de Ahmad)Customizei a função "upsert" acima, se você deseja INSERIR E SUBSTITUIR:
`
E depois de executar, faça algo como isto:
É importante colocar uma vírgula dupla para evitar erros do compilador
fonte
Semelhante à resposta mais curtida, mas funciona um pouco mais rápido:
(fonte: http://www.the-art-of-web.com/sql/upsert/ )
fonte
Tenho o mesmo problema para gerenciar as configurações da conta que os pares de valor e nome. O critério de design é que clientes diferentes possam ter conjuntos de configurações diferentes.
Minha solução, semelhante ao JWP, é apagar e substituir em massa, gerando o registro de mesclagem no seu aplicativo.
Isso é bastante à prova de balas, independente de plataforma e, como nunca há mais de 20 configurações por cliente, são apenas três chamadas db de carga bastante baixa - provavelmente o método mais rápido.
A alternativa de atualizar linhas individuais - verificar se há exceções e depois inserir - ou alguma combinação de código hediondo, é lento e frequentemente quebra porque (como mencionado acima) o tratamento de exceção SQL fora do padrão, alterando de db para db - ou mesmo de liberação para liberação.
fonte
REPLACE INTO
que issoINSERT INTO ... ON DUPLICATE KEY UPDATE
, o que pode causar um problema se você usar gatilhos. Você acabará executando excluir e inserir gatilhos / regras, em vez de atualizar.De acordo com a documentação da
INSERT
declaração do PostgreSQL , o tratamento doON DUPLICATE KEY
caso não é suportado. Essa parte da sintaxe é uma extensão proprietária do MySQL.fonte
MERGE
também é realmente mais uma operação OLAP; consulte stackoverflow.com/q/17267417/398670 para obter explicação. Ele não define a semântica de simultaneidade e a maioria das pessoas que a usa para upsert está apenas criando bugs.fonte
Para mesclar conjuntos pequenos, usar a função acima é adequado. No entanto, se você estiver mesclando grandes quantidades de dados, sugiro consultar http://mbk.projects.postgresql.org
A melhor prática atual que eu conheço é:
fonte
UPDATE retornará o número de linhas modificadas. Se você usar JDBC (Java), poderá verificar esse valor em relação a 0 e, se nenhuma linha tiver sido afetada, acionar INSERT. Se você usar alguma outra linguagem de programação, talvez o número de linhas modificadas ainda possa ser obtido, consulte a documentação.
Isso pode não ser tão elegante, mas você tem um SQL muito mais simples que é mais trivial para usar no código de chamada. Diferentemente, se você escrever o script de dez linhas no PL / PSQL, provavelmente deverá ter um teste de unidade de um ou outro tipo apenas para ele.
fonte
Editar: Isso não funciona conforme o esperado. Diferentemente da resposta aceita, isso gera violações de chave exclusivas quando dois processos chamam repetidamente
upsert_foo
simultaneamente.Eureka! Eu descobri uma maneira de fazer isso em uma consulta: use
UPDATE ... RETURNING
para testar se alguma linha foi afetada:O
UPDATE
que deve ser feito em um procedimento separado, porque, infelizmente, este é um erro de sintaxe:Agora funciona como desejado:
fonte