Eu preciso realizar UPSERT / INSERT OR UPDATE em um banco de dados SQLite.
Existe o comando INSERT OR REPLACE que em muitos casos pode ser útil. Mas se você quiser manter seus ids com incremento automático no lugar por causa de chaves estrangeiras, isso não funciona, pois exclui a linha, cria uma nova e, conseqüentemente, esta nova linha tem um novo ID.
Esta seria a mesa:
jogadores - (chave primária no id, user_name exclusivo)
| id | user_name | age |
------------------------------
| 1982 | johnny | 23 |
| 1983 | steven | 29 |
| 1984 | pepee | 40 |
db.execSQL("insert into bla(id,name) values (?,?) on conflict(id) do update set name=?")
. Dá-me um erro de sintaxe na palavra "on"Estilo de perguntas e respostas
Bem, depois de pesquisar e lutar com o problema por horas, descobri que existem duas maneiras de fazer isso, dependendo da estrutura da sua tabela e se você tem restrições de chaves estrangeiras ativadas para manter a integridade. Eu gostaria de compartilhar isso em um formato limpo para economizar algum tempo para as pessoas que podem estar na minha situação.
Opção 1: você pode excluir a linha
Em outras palavras, você não tem uma chave estrangeira ou, se as tiver, seu mecanismo SQLite está configurado para que não haja exceções de integridade. O caminho a seguir é INSERT OR REPLACE . Se você está tentando inserir / atualizar um jogador cujo ID já existe, o mecanismo SQLite irá deletar aquela linha e inserir os dados que você está fornecendo. Agora surge a pergunta: o que fazer para manter o antigo ID associado?
Digamos que queremos UPSERT com os dados user_name = 'steven' e idade = 32.
Olhe para este código:
O truque está em coalescer. Ele retorna o id do usuário 'steven' se houver, e caso contrário, ele retorna um novo id novo.
Opção 2: você não pode excluir a linha
Depois de mexer na solução anterior, percebi que no meu caso isso poderia acabar destruindo dados, já que esse ID funciona como uma chave estrangeira para outra tabela. Além disso, criei a tabela com a cláusula ON DELETE CASCADE , o que significaria que ela apagaria os dados silenciosamente. Perigoso.
Então, primeiro pensei em uma cláusula IF, mas o SQLite só tem CASE . E este CASE não pode ser usado (ou pelo menos eu não consegui) para realizar uma consulta UPDATE se EXISTS (selecione id de jogadores onde user_name = 'steven'), e INSERT se não. Não vá.
E então, finalmente, usei a força bruta, com sucesso. A lógica é, para cada UPSERT que você deseja realizar, primeiro execute um INSERT OU IGNORE para ter certeza de que há uma linha com nosso usuário e, em seguida, execute uma consulta UPDATE com exatamente os mesmos dados que você tentou inserir.
Os mesmos dados de antes: user_name = 'steven' e idade = 32.
E isso é tudo!
EDITAR
Como Andy comentou, tentar inserir primeiro e depois atualizar pode levar ao disparo de gatilhos com mais frequência do que o esperado. Isso não é, em minha opinião, um problema de segurança de dados, mas é verdade que disparar eventos desnecessários faz pouco sentido. Portanto, uma solução melhorada seria:
fonte
Aqui está uma abordagem que não requer a força bruta 'ignorar', que só funcionaria se houvesse uma violação de chave. Desta forma, funciona com base em qualquer condições que você especificar na atualização.
Experimente isso ...
Como funciona
O 'molho mágico' aqui está usando
Changes()
naWhere
cláusula.Changes()
representa o número de linhas afetadas pela última operação, que neste caso é a atualização.No exemplo acima, se não houver mudanças desde a atualização (ou seja, o registro não existe), então
Changes()
= 0, então aWhere
cláusula naInsert
instrução é avaliada como verdadeira e uma nova linha é inserida com os dados especificados.Se o
Update
fez atualização uma linha existente, entãoChanges()
= 1 (ou mais precisamente, não zero se mais do que uma linha foi atualizado), de modo que o 'onde' cláusula noInsert
agora avaliada como falsa e, portanto, nenhuma inserção terá lugar.A beleza disso é que não há necessidade de força bruta, nem exclusão desnecessária e, em seguida, reinserção de dados, o que pode resultar na bagunça de chaves downstream em relacionamentos de chave estrangeira.
Além disso, como é apenas uma
Where
cláusula padrão , pode se basear em qualquer coisa que você definir, não apenas em violações de chave. Da mesma forma, você pode usarChanges()
em combinação com qualquer coisa que quiser / precisar em qualquer lugar que as expressões sejam permitidas.fonte
Changes() = 0
, retornará falso e duas linhas farão INSERT OU REPLACEUPSERT
em primeiro lugar? Mas mesmo assim, é uma boa coisa que a atualização aconteça, configurandoChanges=1
ou então aINSERT
instrução seria disparada incorretamente, o que você não quer.O problema com todas as respostas apresentadas é a completa falta de levar os gatilhos (e provavelmente outros efeitos colaterais) em consideração. Solução como
leva a ambos os gatilhos executados (para inserir e depois para atualizar) quando a linha não existe.
A solução adequada é
nesse caso, apenas uma instrução é executada (quando a linha existe ou não).
fonte
UPDATE OR IGNORE
é necessário, já que a atualização não irá travar se nenhuma linha for encontrada.Para ter um UPSERT puro sem orifícios (para programadores) que não retransmitem em chaves únicas e outras:
SELECT changes () retornará o número de atualizações feitas na última consulta. Em seguida, verifique se o valor de retorno de changes () é 0, se for o caso, execute:
fonte
Você também pode simplesmente adicionar uma cláusula ON CONFLICT REPLACE à sua restrição única user_name e então apenas inserir INSERT, deixando para SQLite descobrir o que fazer em caso de conflito. Veja: https://sqlite.org/lang_conflict.html .
Observe também a frase sobre os gatilhos de exclusão: Quando a estratégia de resolução de conflito REPLACE exclui linhas para satisfazer uma restrição, os gatilhos de exclusão são acionados se e somente se os gatilhos recursivos estiverem habilitados.
fonte
Opção 1: Inserir -> Atualizar
Se você gosta de evitar ambos
changes()=0
eINSERT OR IGNORE
mesmo se não puder excluir a linha - você pode usar essa lógica;Primeiro, insira (se não existir) e depois atualize filtrando com a chave exclusiva.
Exemplo
Em relação aos gatilhos
Aviso: não testei para ver quais gatilhos estão sendo chamados, mas presumo o seguinte:
se a linha não existe
se existe linha
Opção 2: inserir ou substituir - manter seu próprio ID
desta forma, você pode ter um único comando SQL
Editar: opção 2 adicionada.
fonte