Eu tenho o seguinte UPSERT no PostgreSQL 9.5:
INSERT INTO chats ("user", "contact", "name")
VALUES ($1, $2, $3),
($2, $1, NULL)
ON CONFLICT("user", "contact") DO NOTHING
RETURNING id;
Se não houver conflitos, ele retornará algo como isto:
----------
| id |
----------
1 | 50 |
----------
2 | 51 |
----------
Mas se houver conflitos, ele não retornará nenhuma linha:
----------
| id |
----------
Desejo retornar as novas id
colunas se não houver conflitos ou retornar as id
colunas existentes das colunas conflitantes.
Isso pode ser feito? Se sim, como?
ON CONFLICT UPDATE
para que haja uma alteração na linha. EntãoRETURNING
irá capturá-lo.Respostas:
Eu tinha exatamente o mesmo problema e resolvi-o usando 'atualizar' em vez de 'não fazer nada', apesar de não ter nada para atualizar. No seu caso, seria algo como isto:
Essa consulta retornará todas as linhas, independentemente de terem sido inseridas ou já existirem antes.
fonte
DO NOTHING
aspecto da pergunta original - para mim parece atualizar o campo sem conflito (aqui, "nome") para todas as linhas.A resposta atualmente aceita parece adequada para um único destino de conflito, poucos conflitos, pequenas tuplas e sem gatilhos. Evita o problema 1 de simultaneidade (veja abaixo) com força bruta. A solução simples tem seu apelo, os efeitos colaterais podem ser menos importantes.
Para todos os outros casos, no entanto, não atualize linhas idênticas sem necessidade. Mesmo que você não veja nenhuma diferença na superfície, existem vários efeitos colaterais :
Pode disparar gatilhos que não devem ser disparados.
Ele bloqueia linhas "inocentes", possivelmente incorrendo em custos para transações simultâneas.
Pode fazer com que a linha pareça nova, embora seja antiga (registro de data e hora da transação).
Mais importante , com o modelo MVCC do PostgreSQL, uma nova versão de linha é gravada para todos
UPDATE
, independentemente de os dados da linha serem alterados. Isso incorre em uma penalidade de desempenho para o próprio UPSERT, inchaço da mesa, inchaço do índice, penalidade de desempenho para operações subsequentes na mesa,VACUUM
custo. Um efeito menor para poucas duplicatas, mas massivo para a maioria dos enganados.Além disso , às vezes não é prático ou mesmo possível de usar
ON CONFLICT DO UPDATE
. O manual:Um único "destino de conflito" não é possível se vários índices / restrições estiverem envolvidos.
Você pode conseguir (quase) o mesmo sem atualizações vazias e efeitos colaterais. Algumas das soluções a seguir também funcionam
ON CONFLICT DO NOTHING
(sem "destino de conflito"), para capturar todos os conflitos possíveis que possam surgir - o que pode ou não ser desejável.Sem carga de gravação simultânea
A
source
coluna é uma adição opcional para demonstrar como isso funciona. Você pode realmente precisar dizer a diferença entre os dois casos (outra vantagem sobre gravações vazias).O final
JOIN chats
funciona porque as linhas recém-inseridas de um CTE modificador de dados anexado ainda não estão visíveis na tabela subjacente. (Todas as partes da mesma instrução SQL veem os mesmos instantâneos das tabelas subjacentes.)Como a
VALUES
expressão é independente (não está diretamente anexada a umINSERT
), o Postgres não pode derivar tipos de dados das colunas de destino e pode ser necessário adicionar conversões de tipo explícitas. O manual:A consulta em si (sem contar os efeitos colaterais) pode ser um pouco mais cara para alguns burros, devido à sobrecarga da CTE e à adicional
SELECT
(que deve ser barata, pois o índice perfeito existe por definição - uma restrição exclusiva é implementada com Um índice).Pode ser (muito) mais rápido para muitas duplicatas. O custo efetivo de gravações adicionais depende de muitos fatores.
Mas há menos efeitos colaterais e custos ocultos em qualquer caso. Provavelmente é mais barato no geral.
As sequências anexadas ainda são avançadas, pois os valores padrão são preenchidos antes do teste de conflitos.
Sobre CTEs:
Com carga de gravação simultânea
Assumindo
READ COMMITTED
isolamento de transação padrão . Palavras-chave:A melhor estratégia para se defender contra as condições de corrida depende dos requisitos exatos, do número e tamanho das linhas na tabela e nas UPSERTs, no número de transações simultâneas, na probabilidade de conflitos, nos recursos disponíveis e em outros fatores ...
Problema de simultaneidade 1
Se uma transação simultânea tiver sido gravada em uma linha que sua transação agora tenta UPSERT, sua transação deverá aguardar a conclusão da outra.
Se as outras extremidades de transação com
ROLLBACK
(ou qualquer erro, ou seja, automáticaROLLBACK
), a transação pode prosseguir normalmente. Menor efeito colateral possível: lacunas nos números seqüenciais. Mas não há linhas ausentes.Se a outra transação terminar normalmente (implícita ou explícita
COMMIT
), vocêINSERT
detectará um conflito (oUNIQUE
índice / restrição é absoluto) eDO NOTHING
, portanto, também não retornará a linha. (Também não é possível bloquear a linha, conforme demonstrado no problema de concorrência 2 abaixo, pois não é visível .) EleSELECT
vê o mesmo instantâneo desde o início da consulta e também não pode retornar a linha ainda invisível.Essas linhas estão ausentes no conjunto de resultados (mesmo que existam na tabela subjacente)!
Isso pode estar ok como está . Especialmente se você não está retornando linhas como no exemplo e está satisfeito sabendo que a linha está lá. Se isso não for bom o suficiente, existem várias maneiras de contornar isso.
Você pode verificar a contagem de linhas da saída e repetir a instrução se ela não corresponder à contagem de linhas da entrada. Pode ser bom o suficiente para o caso raro. O ponto é iniciar uma nova consulta (pode estar na mesma transação), que verá as linhas recém confirmadas.
Ou verifique se há linhas de resultados ausentes na mesma consulta e substitua aquelas com o truque da força bruta demonstrado na resposta de Alextoni .
É como a consulta acima, mas adicionamos mais uma etapa ao CTE
ups
, antes de retornar o conjunto completo de resultados. Esse último CTE não fará nada na maioria das vezes. Somente se faltarem linhas no resultado retornado, usaremos força bruta.Mais despesas, ainda. Quanto mais conflitos com linhas pré-existentes, maior a probabilidade de isso superar a abordagem simples.
Um efeito colateral: o segundo UPSERT grava linhas fora de ordem e reintroduz a possibilidade de conflitos (veja abaixo) se três ou mais transações gravadas nas mesmas linhas se sobrepuserem. Se isso for um problema, você precisará de uma solução diferente - como repetir toda a declaração, como mencionado acima.
Problema de concorrência 2
Se transações simultâneas puderem gravar em colunas envolvidas de linhas afetadas, e você precisar garantir que as linhas encontradas ainda estejam lá em um estágio posterior da mesma transação, poderá bloquear as linhas existentes mais baratas no CTE
ins
(que, de outra forma, seriam desbloqueadas) com:E adicione uma cláusula de bloqueio
SELECT
tambémFOR UPDATE
.Isso faz com que as operações de gravação concorrentes esperem até o final da transação, quando todos os bloqueios forem liberados. Então seja breve.
Mais detalhes e explicação:
Deadlocks?
Defenda-se contra conflitos , inserindo linhas em ordem consistente . Vejo:
Tipos de dados e transmissões
Tabela existente como modelo para tipos de dados ...
Conversões explícitas de tipo para a primeira linha de dados na
VALUES
expressão independente podem ser inconvenientes. Existem maneiras de contornar isso. Você pode usar qualquer relação existente (tabela, exibição, ...) como modelo de linha. A tabela de destino é a escolha óbvia para o caso de uso. Os dados de entrada são coagidos a tipos apropriados automaticamente, como naVALUES
cláusula de umINSERT
:Isso não funciona para alguns tipos de dados. Vejo:
... e nomes
Isso também funciona para todos os tipos de dados.
Ao inserir em todas as colunas (iniciais) da tabela, você pode omitir os nomes das colunas. Supondo que a tabela
chats
no exemplo consista apenas nas 3 colunas usadas no UPSERT:Aparte: não use palavras reservadas como
"user"
como identificador. Essa é uma espingarda carregada. Use identificadores legais, em minúsculas e sem aspas. Eu substituí porusr
.fonte
ON CONFLICT SELECT...
que uma coisa embora :)Upsert, sendo uma extensão da
INSERT
consulta, pode ser definido com dois comportamentos diferentes em caso de conflito de restrição:DO NOTHING
ouDO UPDATE
.Observe também que
RETURNING
nada retorna, porque nenhuma tupla foi inserida . Agora comDO UPDATE
, é possível executar operações na tupla com as quais há um conflito. Primeiro, observe que é importante definir uma restrição que será usada para definir que há um conflito.fonte
Para inserções de um único item, eu provavelmente usaria uma coalescência ao retornar o ID:
fonte
O principal objetivo do uso
ON CONFLICT DO NOTHING
é evitar gerar erros, mas não causará retornos de linha. Então, precisamos de outroSELECT
para obter o ID existente.Nesse SQL, se falhar em conflitos, ele não retornará nada, o segundo
SELECT
obterá a linha existente; se ele for inserido com êxito, haverá dois registros iguais e precisamosUNION
mesclar o resultado.fonte
Modifiquei a resposta incrível de Erwin Brandstetter, que não incrementa a sequência e também não bloqueia nenhuma linha. Eu sou relativamente novo no PostgreSQL, portanto, sinta-se à vontade para me informar se você encontrar algum inconveniente nesse método:
Isso pressupõe que a tabela
chats
tenha uma restrição exclusiva nas colunas(usr, contact)
.Atualização: adicionadas as revisões sugeridas do spatar (abaixo). Obrigado!
fonte
CASE WHEN r.id IS NULL THEN FALSE ELSE TRUE END AS row_exists
apenas escreverr.id IS NOT NULL as row_exists
. Em vez deWHERE row_exists=FALSE
apenas escreverWHERE NOT row_exists
.