http://en.wikipedia.org/wiki/Upsert
Inserir processo armazenado de atualização no SQL Server
Existe alguma maneira inteligente de fazer isso no SQLite que eu não tenha pensado?
Basicamente, quero atualizar três das quatro colunas se o registro existir. Se ele não existir, insira o registro com o valor padrão (NUL) da quarta coluna.
O ID é uma chave primária, portanto, sempre haverá um registro para a UPSERT.
(Estou tentando evitar a sobrecarga de SELECT para determinar se preciso atualizar ou inserir obviamente)
Sugestões?
Não posso confirmar essa sintaxe no site SQLite para TABLE CREATE. Eu não construí uma demonstração para testá-la, mas ela não parece ser suportada.
Se fosse, eu tenho três colunas, então seria assim:
CREATE TABLE table1(
id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
Blob1 BLOB ON CONFLICT REPLACE,
Blob2 BLOB ON CONFLICT REPLACE,
Blob3 BLOB
);
mas os dois primeiros blobs não causam conflito, apenas o ID causaria. Então, eu usava o Blob1 e o Blob2 não seriam substituídos (conforme desejado)
ATUALIZAÇÕES no SQLite quando os dados de ligação são uma transação completa, o que significa que cada linha enviada a ser atualizada requer: Instruções de Preparação / Ligação / Etapa / Finalização ao contrário do INSERT, que permite o uso da função de redefinição
A vida de um objeto de instrução é mais ou menos assim:
- Crie o objeto usando sqlite3_prepare_v2 ()
- Vincule valores aos parâmetros do host usando interfaces sqlite3_bind_.
- Execute o SQL chamando sqlite3_step ()
- Redefina a instrução usando sqlite3_reset (), volte para a etapa 2 e repita.
- Destrua o objeto de instrução usando sqlite3_finalize ().
ATUALIZAÇÃO Acho que é lento comparado ao INSERT, mas como ele se compara ao SELECT usando a chave Primária?
Talvez eu deva usar o select para ler a 4ª coluna (Blob3) e, em seguida, usar REPLACE para escrever um novo registro misturando a 4ª coluna original com os novos dados para as 3 primeiras colunas?
Respostas:
Supondo três colunas na tabela: ID, NAME, ROLE
RUIM: Isso irá inserir ou substituir todas as colunas por novos valores para ID = 1:
RUIM: Isso irá inserir ou substituir 2 das colunas ... a coluna NAME será definida como NULL ou o valor padrão:
BOM : Use a cláusula de conflito SQLite On no suporte ao UPSERT no SQLite! A sintaxe UPSERT foi adicionada ao SQLite com a versão 3.24.0!
UPSERT é uma adição de sintaxe especial ao INSERT que faz com que o INSERT se comporte como UPDATE ou no-op se o INSERT violar uma restrição de exclusividade. UPSERT não é SQL padrão. O UPSERT no SQLite segue a sintaxe estabelecida pelo PostgreSQL.
BOM, mas tendencioso: isso atualizará duas das colunas. Quando o ID = 1 existir, o NAME não será afetado. Quando o ID = 1 não existir, o nome será o padrão (NULL).
Isso atualizará duas das colunas. Quando ID = 1 existir, o ROLE não será afetado. Quando o ID = 1 não existe, a função será definida como 'Benchwarmer' em vez do valor padrão.
fonte
INSERT OR REPLACE
enquanto especifica valores para todos colunas.INSERIR OU SUBSTITUIR NÃO é equivalente a "UPSERT".
Digamos que eu tenha a tabela Employee com os campos id, name and role:
Crescimento, você perdeu o nome do funcionário número 1. O SQLite o substituiu por um valor padrão.
A saída esperada de um UPSERT seria alterar a função e manter o nome.
fonte
coalesce((select ..), 'new value')
cláusula incorporada . A resposta de Eric precisa de mais votos aqui, eu acho.Resposta de Eric B é boa se você deseja preservar apenas uma ou talvez duas colunas da linha existente. Se você deseja preservar muitas colunas, fica muito rápido e complicado.
Aqui está uma abordagem que será bem dimensionada para qualquer quantidade de colunas nos dois lados. Para ilustrá-lo, assumirei o seguinte esquema:
Observe, em particular, que
name
é a chave natural da linha -id
é usada apenas para chaves estrangeiras; portanto, o ponto é que o SQLite selecione o valor do ID ao inserir uma nova linha. Mas ao atualizar uma linha existente com base em suaname
, quero que ela continue com o antigo valor de ID (obviamente!).Consigo um verdadeiro
UPSERT
com a seguinte construção:A forma exata desta consulta pode variar um pouco. A chave é o uso de
INSERT SELECT
uma junção externa esquerda, para unir uma linha existente aos novos valores.Aqui, se uma linha não existir anteriormente,
old.id
haveráNULL
e o SQLite atribuirá um ID automaticamente, mas se já houver uma linha,old.id
terá um valor real e isso será reutilizado. Qual é exatamente o que eu queria.De fato, isso é muito flexível. Observe como a
ts
coluna está completamente ausente por todos os lados - porque tem umDEFAULT
valor, o SQLite fará a coisa certa em qualquer caso, então eu não preciso cuidar disso sozinha.Você também pode incluir uma coluna em ambos os
new
eold
os lados e, em seguida, usar por exemploCOALESCE(new.content, old.content)
no exteriorSELECT
para dizer “insert o novo conteúdo se havia alguma, caso contrário, manter o conteúdo antigo” - por exemplo, se você estiver usando uma consulta fixo e são obrigatórias a nova valores com espaços reservados.fonte
WHERE name = "about"
restriçãoSELECT ... AS old
para acelerar as coisas. Se você tiver mais de 1m de linhas, isso é muito lento.WHERE
cláusula requer exatamente o tipo de redundância na consulta que eu estava tentando evitar em primeiro lugar quando me deparei com essa abordagem. Como sempre: quando você precisar de desempenho, desnormalize - a estrutura da consulta, neste caso.INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
ON DELETE
quando ele executa uma substituição (ou seja, uma atualização)?ON DELETE
gatilhos. Não sei desnecessariamente. Para a maioria dos usuários, provavelmente seria desnecessário, até indesejado, mas talvez não para todos os usuários. Da mesma forma, também exclui em cascata todas as linhas com chaves estrangeiras na linha em questão - provavelmente um problema para muitos usuários. Infelizmente, o SQLite não tem nada mais perto de um UPSERT real. (Save para fingir com umINSTEAD OF UPDATE
gatilho, eu acho.)Se você geralmente está fazendo atualizações, eu faria ..
Se você geralmente faz inserções, eu
Dessa forma, você evita a seleção e está transacionalmente sólido no Sqlite.
fonte
Esta resposta foi atualizada e, portanto, os comentários abaixo não se aplicam mais.
2018-05-18 STOP PRESS.
Suporte UPSERT no SQLite!A sintaxe UPSERT foi adicionada ao SQLite com a versão 3.24.0 (pendente)!
UPSERT é uma adição de sintaxe especial ao INSERT que faz com que o INSERT se comporte como UPDATE ou no-op se o INSERT violar uma restrição de exclusividade. UPSERT não é SQL padrão. O UPSERT no SQLite segue a sintaxe estabelecida pelo PostgreSQL.
alternativamente:
Outra maneira completamente diferente de fazer isso é: No meu aplicativo, defino meu ID da linha na memória como long.MaxValue quando crio a linha na memória. (MaxValue nunca será usado como um ID, você não viverá o suficiente ... Então, se rowID não for esse valor, ele já deverá estar no banco de dados, portanto, precisará de um UPDATE se for MaxValue e precisará de uma inserção. Isso só é útil se você puder rastrear os IDs de linha no seu aplicativo.
fonte
WHERE
cláusula não pode ser anexada a umaINSERT
instrução: sqlite-insert ...Sei que esse é um encadeamento antigo, mas tenho trabalhado no sqlite3 ultimamente e criei esse método que melhor se adequava às minhas necessidades de gerar consultas parametrizadas dinamicamente:
Ainda são 2 consultas com uma cláusula where na atualização, mas parece fazer o truque. Eu também tenho essa visão de que o sqlite pode otimizar completamente a instrução update se a chamada para changes () for maior que zero. Se realmente faz ou não, isso está além do meu conhecimento, mas um homem pode sonhar, não pode? ;)
Para pontos de bônus, você pode acrescentar esta linha que retorna o ID da linha, seja uma linha recém-inserida ou uma linha existente.
fonte
Update <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
Aqui está uma solução que realmente é um UPSERT (UPDATE ou INSERT) em vez de um INSERT OR REPLACE (que funciona de maneira diferente em muitas situações).
Funciona assim:
1. Tente atualizar se existir um registro com o mesmo ID.
2. Se a atualização não alterou nenhuma linha (
NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)
), insira o registro.Portanto, um registro existente foi atualizado ou uma inserção será executada.
O detalhe importante é usar a função SQL changes () para verificar se a instrução update atingiu algum registro existente e somente executar a instrução insert se não atingiu nenhum registro.
Uma coisa a mencionar é que a função changes () não retorna as alterações realizadas pelos gatilhos de nível inferior (consulte http://sqlite.org/lang_corefunc.html#changes ), portanto, leve isso em consideração.
Aqui está o SQL ...
Atualização de teste:
Inserto de teste:
fonte
INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;
, também deve funcionar.A partir da versão 3.24.0, o UPSERT é suportado pelo SQLite.
A partir da documentação :
Fonte da imagem: https://www.sqlite.org/images/syntax/upsert-clause.gif
fonte
Você pode realmente fazer um upsert no SQLite, apenas parece um pouco diferente do que você está acostumado. Seria algo como:
fonte
Expandindo a resposta de Aristóteles, você pode SELECIONAR a partir de uma tabela fictícia 'singleton' (uma tabela de sua própria criação com uma única linha). Isso evita alguma duplicação.
Eu também mantive o exemplo portátil em MySQL e SQLite e usei uma coluna 'date_added' como um exemplo de como você pode definir uma coluna somente na primeira vez.
fonte
A melhor abordagem que conheço é fazer uma atualização, seguida por uma inserção. A "sobrecarga de uma seleção" é necessária, mas não é um fardo terrível, pois você está pesquisando na chave primária, que é rápida.
Você deve poder modificar as instruções abaixo com os nomes de tabela e campo para fazer o que quiser.
fonte
Se alguém quiser ler minha solução para SQLite em Cordova, obtive este método js genérico graças à resposta @david acima.
Portanto, escolha primeiro os nomes das colunas com esta função:
Em seguida, crie as transações programaticamente.
"Valores" é uma matriz que você deve criar antes e representa as linhas que deseja inserir ou atualizar na tabela.
"remoteid" é o ID que eu usei como referência, pois estou sincronizando com meu servidor remoto.
Para o uso do plug-in SQLite Cordova, consulte o link oficial
fonte
Esse método corrige alguns dos outros métodos da resposta para esta pergunta e incorpora o uso de CTE (Common Table Expressions). Vou apresentar a consulta e explicar por que fiz o que fiz.
Gostaria de alterar o sobrenome do funcionário 300 para DAVIS se houver um funcionário 300. Caso contrário, adicionarei um novo funcionário.
Nome da tabela: funcionários Colunas: id, first_name, last_name
A consulta é:
Basicamente, usei o CTE para reduzir o número de vezes que a instrução select deve ser usada para determinar os valores padrão. Como se trata de um CTE, basta selecionar as colunas que queremos da tabela e a instrução INSERT usa isso.
Agora você pode decidir quais padrões deseja usar substituindo os valores nulos, na função COALESCE pelos valores que devem ser.
fonte
Seguindo Aristóteles Pagaltzis e a ideia da resposta
COALESCE
de Eric B , aqui está uma opção melhor: atualizar apenas algumas colunas ou inserir linha completa, se ela não existir.Nesse caso, imagine que o título e o conteúdo devem ser atualizados, mantendo os outros valores antigos ao existir e inserindo os valores fornecidos quando o nome não for encontrado:
OBSERVAÇÃO
id
é forçado a ser NULL quando,INSERT
como deveria ser o incremento automático. Se for apenas uma chave primária gerada,COALESCE
também poderá ser usada (consulte o comentário de Aristóteles Pagaltzis ).Portanto, a regra geral seria: se você deseja manter valores antigos, use
COALESCE
, quando desejar atualizar valores, usenew.fieldname
fonte
COALESCE(old.id, new.id)
está definitivamente errado com uma chave de incremento automático. E embora “mantenha a maior parte da linha inalterada, exceto onde os valores estão ausentes” parece um caso de uso que alguém possa ter, não acho que é isso que as pessoas procuram quando estão procurando um UPSERT.old
tabela foram atribuídas aNULL
, e não aos valores fornecidosnew
. Esta é a razão para usarCOALESCE
. Eu não sou um especialista em SQLite, eu tenho vindo a testar esta consulta e parece funcionar para o caso, eu seria muito obrigado se você poderia me aponte para a solução com autoincrementsNULL
como a chave, porque isso instrui o SQLite a inserir o próximo valor disponível.Eu acho que isso pode ser o que você está procurando: cláusula ON CONFLICT .
Se você definir sua tabela assim:
Agora, se você fizer um INSERT com um ID que já exista, o SQLite fará automaticamente UPDATE em vez de INSERT.
Hth ...
fonte
REPLACE
declaração.Se você não se importa em fazer isso em duas operações.
Passos:
1) Adicione novos itens com "INSERIR OU IGNORAR"
2) Atualize itens existentes com "UPDATE"
A entrada para as duas etapas é a mesma coleção de itens novos ou com capacidade de atualização. Funciona bem com itens existentes que não precisam de alterações. Eles serão atualizados, mas com os mesmos dados e, portanto, o resultado líquido não será alterado.
Claro, mais lento, etc. Ineficiente. Sim.
Fácil de escrever o sql, manter e entender? Definitivamente.
É uma troca a considerar. Funciona muito bem para pequenas upserts. Funciona muito bem para aqueles que não se importam em sacrificar a eficiência pela manutenção do código.
fonte
Tendo acabado de ler este tópico e desapontado por não ter sido fácil chegar a este "UPSERT", investiguei mais ...
Você pode fazer isso diretamente e facilmente no SQLITE.
Ao invés de usar:
INSERT INTO
Usar:
INSERT OR REPLACE INTO
Isso faz exatamente o que você quer que ele faça!
fonte
INSERT OR REPLACE
não é umUPSERT
. Veja a "resposta" de gregschlom pelo motivo. A solução de Eric B realmente funciona e precisa de alguns votos positivos.E se
COUNT(*) = 0
senão se
COUNT(*) > 0
fonte