INSERIR SE NÃO EXISTE MAIS ATUALIZAÇÃO?

275

Encontrei algumas soluções "seria" para o clássico "Como insiro um novo registro ou atualizo um, se ele já existe", mas não consigo fazer com que nenhum deles funcione no SQLite.

Eu tenho uma tabela definida da seguinte maneira:

CREATE TABLE Book 
ID     INTEGER PRIMARY KEY AUTOINCREMENT,
Name   VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level  INTEGER,
Seen   INTEGER

O que eu quero fazer é adicionar um registro com um nome exclusivo. Se o nome já existir, desejo modificar os campos.

Alguém pode me dizer como fazer isso, por favor?

SparkyNZ
fonte
3
"inserir ou substituir" é totalmente diferente de "inserção ou atualização"
Fattie

Respostas:

320

Dê uma olhada em http://sqlite.org/lang_conflict.html .

Você quer algo como:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);

Observe que qualquer campo que não esteja na lista de inserção será definido como NULL se a linha já existir na tabela. É por isso que existe uma subseleção para a IDcoluna: no caso de substituição, a instrução o definiria como NULL e, em seguida, um novo ID seria alocado.

Essa abordagem também pode ser usada se você desejar deixar valores específicos de campo em paz se a linha no caso de substituição, mas definir o campo como NULL no caso de inserção.

Por exemplo, supondo que você queira sair Seensozinho:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
   (select ID from Book where Name = "SearchName"),
   "SearchName",
    5,
    6,
    (select Seen from Book where Name = "SearchName"));
janm
fonte
109
"Inserir ou substituir" incorreto é diferente de "inserir ou atualizar". Para obter uma resposta válida, consulte stackoverflow.com/questions/418898/…
rds
12
@rds Não, não está errado, porque esta pergunta diz "modificar os campos" e a chave primária não faz parte da lista de colunas, mas todos os outros campos. Se você vai ter casos de canto em que não está substituindo todos os valores de campo, ou se está mexendo com a chave primária, deve estar fazendo algo diferente. Se você possui um conjunto completo de novos campos, essa abordagem é ótima. Você tem um problema específico que não consigo ver?
JANM
9
É válido se você conhecer todo o novo valor para todos os campos. Se o usuário atualizar apenas, digamos, o Level, essa abordagem não poderá ser seguida.
Rd #
4
Correto . A outra resposta é relevante, mas essa abordagem é válida para atualizar todos os campos (excluindo a chave).
224142 42 о
2
Sim Isso está completamente errado. O que acontecerá se eu quiser atualizar o valor da coluna única em Confliction a partir de uma nova. No caso acima, todos os outros dados serão substituídos por novos que não estiverem corretos.
Mrug
85

Você deve usar o INSERT OR IGNOREcomando seguido por um UPDATEcomando: No exemplo a seguir nameé uma chave primária:

INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'

O primeiro comando irá inserir o registro. Se o registro existir, ele ignorará o erro causado pelo conflito com uma chave primária existente.

O segundo comando atualizará o registro (que agora existe definitivamente)

moshik
fonte
6
em que momento ele irá ignorar? quando o nome e a idade são iguais?
mou
Essa deve ser a solução ... se você estiver usando qualquer gatilho na inserção, a resposta aceita será acionada sempre. Isso não faz e executa uma atualização única
PodTech.io
1
Ele ignora com base apenas no nome. Lembre-se de que apenas a coluna "nome" é uma chave primária.
Gabriel Ferrer
3
Quando o registro é novo, a atualização não é necessária, mas será executada de qualquer maneira, levando a um desempenho ruim?
#
Como executar uma declaração preparada para isso?
Prashant
65

Você precisa definir uma restrição na tabela para acionar um " conflito " que será resolvido fazendo uma substituição:

CREATE TABLE data   (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);

Então você pode emitir:

INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);

O "SELECT * FROM data" fornece:

2|2|2|3.0
3|1|2|5.0

Observe que o data.id é "3" e não "1" porque REPLACE faz um DELETE e INSERT, não um UPDATE. Isso também significa que você deve garantir que define todas as colunas necessárias ou obterá valores NULL inesperados.

gaspard
fonte
33

Primeiro, atualize-o. Se a contagem de linhas afetadas = 0, insira-a. É o mais fácil e adequado para todos os RDBMS .

Burçin
fonte
11
Duas operações não devem ter problema com uma transação no nível de isolamento correto, independentemente do banco de dados.
janm
5
Insert or Replaceé realmente mais preferível.
MPelletier
1
Eu realmente gostaria que a documentação de TI contivesse mais exemplos. Eu tentei isso abaixo e não funciona (minha sintaxe está obviamente errada). Alguma idéia sobre o que deveria ser? INSERIR NO LIVRO (Nome, TypeID, Nível, Visto) VALORES ('Super-homem', '2', '14', '0') SOBRE SUBSTITUIÇÃO DE CONFLITO Livro (Nome, TypeID, Nível, Visto) VALORES ('Super-homem', ' 2 ',' 14 ',' 0 ')
SparkyNZ
6
Além disso, se os valores da linha forem exatamente iguais, a contagem de linhas afetadas será zero e uma nova linha duplicada será criada.
Barkside
2
+1, porque colocar ou substituir irá excluir a linha original no conflito e se você não está definindo todas as colunas, você vai perder valores originais
Pavel Machyniak
20

INSERT OR REPLACE substituirá os outros campos para o valor padrão.

sqlite> CREATE TABLE Book (
  ID     INTEGER PRIMARY KEY AUTOINCREMENT,
  Name   TEXT,
  TypeID INTEGER,
  Level  INTEGER,
  Seen   INTEGER
);

sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');

sqlite> SELECT * FROM Book;
1001|SQLite|||

Se você deseja preservar o outro campo

sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;

sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0

ou usando UPSERT (a sintaxe foi adicionada ao SQLite com a versão 3.24.0 (04/06/2018))

INSERT INTO Book (ID, Name)
  VALUES (1001, 'SQLite')
  ON CONFLICT (ID) DO
  UPDATE SET Name=excluded.Name;

O excluded.prefixo igual ao valor em VALUES.

Asa de aço
fonte
16

Upsert é o que você quer. UPSERTA sintaxe foi adicionada ao SQLite com a versão 3.24.0 (04/06/2018).

CREATE TABLE phonebook2(
  name TEXT PRIMARY KEY,
  phonenumber TEXT,
  validDate DATE
);

INSERT INTO phonebook2(name,phonenumber,validDate)
  VALUES('Alice','704-555-1212','2018-05-08')
  ON CONFLICT(name) DO UPDATE SET
    phonenumber=excluded.phonenumber,
    validDate=excluded.validDate
  WHERE excluded.validDate>phonebook2.validDate;

Esteja avisado de que, neste momento, a palavra real "UPSERT" não faz parte da sintaxe de upsert.

A sintaxe correta é

INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...

e se você estiver fazendo INSERT INTO SELECT ...suas necessidades selecionadas, pelo menos, WHERE truepara resolver a ambiguidade do analisador sobre o tokenON com a sintaxe de junção.

Esteja avisado de que INSERT OR REPLACE...excluirá o registro antes de inserir um novo, caso seja necessário substituí-lo, o que pode ser ruim se houver cascatas de chaves estrangeiras ou outros gatilhos de exclusão.

CamaradaJoecool
fonte
Ela existe na documentação e eu tenho a última versão do SQLite que é 3.25.1, mas ele não funciona para mim
Bentaiba Miled Basma
você tentou essas duas consultas acima literalmente?
precisa saber é o seguinte
Observe que o Ubuntu 18.04 vem com o SQLite3 v3.22 , e não com o 3.25, portanto, ele não suporta a UPSERTsintaxe.
Starbeamrainbowlabs 17/08/19
Obrigado pela versão de referência no recurso!
Matteo
6

Se você não tiver uma chave primária, poderá inserir, se não existir, e faça uma atualização. A tabela deve conter pelo menos uma entrada antes de usá-la.

INSERT INTO Test 
   (id, name)
   SELECT 
      101 as id, 
      'Bob' as name
   FROM Test
       WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;

Update Test SET id='101' WHERE name='Bob';
mate
fonte
Esta é a única solução que funcionou para mim sem criar entradas duplicadas. Provavelmente porque a tabela que estou usando não possui chaves primárias e possui 2 colunas sem valores padrão. Embora seja uma solução um pouco demorada, ele faz o trabalho corretamente e funciona conforme o esperado.
Inbar Rose
4

Eu acredito que você quer UPSERT .

"INSERIR OU SUBSTITUIR" sem os truques adicionais nessa resposta redefinirá todos os campos que você não especificar para NULL ou outro valor padrão. (Esse comportamento de INSERT OR REPLACE é diferente de UPDATE; é exatamente como INSERT, porque na verdade é INSERT; no entanto, se o que você deseja é UPDATE-se-existe, provavelmente deseja a semântica UPDATE e ficará desagradável com o resultado real.)

O truque da implementação sugerida do UPSERT é basicamente usar INSERT OR REPLACE, mas especifique todos os campos, usando cláusulas SELECT incorporadas para recuperar o valor atual dos campos que você não deseja alterar.

metamatt
fonte
1

Eu acho que vale ressaltar que pode haver algum comportamento inesperado aqui, se você não entender completamente como PRIMARY KEY e UNIQUE interagem.

Como exemplo, se você deseja inserir um registro apenas se o campo NAME não estiver atualmente ocupado, e se desejar, uma exceção de restrição será acionada para informar, INSERT OR REPLACE não lançará uma exceção e, em vez disso, será acionado. resolver o UNIQUE restrição substituindo o registro conflitante (o registro existente com o mesmo NAME ). Gaspard's demonstra isso muito bem em sua resposta acima.

Se você deseja que uma exceção de restrição seja acionada, use uma instrução INSERT e conte com um comando UPDATE separado para atualizar o registro quando souber que o nome não foi usado.

Matt Matthias
fonte