SQLite INSERT - ON DUPLICATE KEY UPDATE (UPSERT)

98

O MySQL tem algo assim:

INSERT INTO visits (ip, hits)
VALUES ('127.0.0.1', 1)
ON DUPLICATE KEY UPDATE hits = hits + 1;

Pelo que eu sei, esse recurso não existe no SQLite, o que eu quero saber é se há alguma maneira de obter o mesmo efeito sem ter que executar duas consultas. Além disso, se isso não for possível, o que você prefere:

  1. SELECT + (INSERT ou UPDATE) ou
  2. UPDATE (+ INSERT se UPDATE falhar )
Alix Axel
fonte

Respostas:

31

Desde 3.24.0 SQLite também suporta upsert , então agora você pode simplesmente escrever o seguinte

INSERT INTO visits (ip, hits)
VALUES ('127.0.0.1', 1)
ON CONFLICT(ip) DO UPDATE SET hits = hits + 1;
szmate1618
fonte
3
Eu queria saber se você pode fazer vários upsertcomo este em uma transação, ou seja, com a executemany()função Python ?
Rádio Controlado em
117
INSERT OR IGNORE INTO visits VALUES ($ip, 0);
UPDATE visits SET hits = hits + 1 WHERE ip LIKE $ip;

Isso requer que a coluna "ip" tenha uma restrição UNIQUE (ou PRIMARY KEY).


EDIT: Outra ótima solução: https://stackoverflow.com/a/4330694/89771 .

dan04
fonte
2
Apenas para registro, REPLACEnão é uma opção.
Alix Axel
1
Com relação ao link "outra grande solução", eu também consideraria uma resposta diferente para a mesma pergunta: stackoverflow.com/a/418988/3650835
KayakinKoder
19

Eu prefiro UPDATE (+ INSERT if UPDATE fails). Menos código = menos bugs.

codeholic
fonte
1
Obrigado! @Sam ( stackoverflow.com/questions/418898/… ) parece concordar com você. Eu também prefiro essa abordagem.
Alix Axel
@Smith, eu quis dizer usar instruções UPDATE e INSERT simples e verificar o valor de retorno.
codeholic de
Isso não tem atomicidade, é possível que o INSERT falhe se algum outro processo for inserido no meio.
Robin Lavallée
7

A resposta atual só funcionará em sqlite OR mysql (dependendo se você usa OR ou não). Então, se você quiser compatibilidade cross dbms, o seguinte servirá ...

REPLACE INTO `visits` (ip, value) VALUES ($ip, 0);
Jacob Thomason
fonte
3
A resposta aceita funciona no SQLite (esse era o meu objetivo). REPLACEfuncionará no SQLite também, mas no MySQL sempre zera o contador para 0 - embora a consulta seja portátil, o resultado final será muito diferente.
Alix Axel
Você está certo, pensei que o OP estava procurando por algo que fosse portátil. Sei que REPLACE INTO não funcionará com todos os casos, especialmente onde a preservação de PK é necessária, mas funcionará em muitos casos.
Jacob Thomason
Falha limpa em vez de descartar dados é um recurso, não um bug.
Tobu,
-4

Você deve usar memcached para isso, pois é uma única chave (o endereço IP) que armazena um único valor (o número de visitas). Você pode usar a função de incremento atômico para garantir que não haja condições de "corrida".

É mais rápido que o MySQL e economiza carga para que o MySQL possa se concentrar em outras coisas.

Xeoncross
fonte
Se os dados não forem tão importantes, sim. No entanto, se isso estiver sendo usado em um site ocupado onde muitos IPs estão acessando o serviço, as instâncias do memcached podem ficar cheias e fazer com que algum conteúdo seja descartado. Fazer backup do conteúdo do memcached também seria interessante (se necessário).
Elliot Foster
@ElliotFoster Memcached pode lidar com tantos dados quanto a RAM que você joga nele (se você quiser persitência, use redis ou membase). Se você estiver recebendo mais de 1 milhão de visitantes por dia, provavelmente poderá dar à sua instância do memcache mais de 30 MB de RAM (que é o padrão, eu acho). No entanto, ele certamente pode lidar com uma carga muito maior do que o SQLite e o MySQL para a quantidade de memória que você fornece - simplesmente não há comparação.
Xeoncross
Não confunda meu comentário com um voto contra o memcache, pois acho que é uma ferramenta fantástica. As is redis (não posso falar em nome do membase, pois não o usei). No entanto, memcache / redis não são os armazenamentos mais confiáveis. Sim, o redis tem persistência, mas os dados são persistidos no disco em um intervalo (última consulta) e o memcache não. Como eu disse, se os dados não forem importantes (ou puderem ser reproduzidos facilmente), o memcache e a empresa são ótimos. A postagem original também perguntava sobre sqlite, que é muito diferente do MySQL e provavelmente significa que eles são limitados de outras maneiras.
Elliot Foster,
Você pode configurar o Redis para persistir os dados tão rápido quanto você deseja (no número X de segundos ou no número Y de alterações).
Buffalo