Como atualizo se existe, insira se não (AKA “upsert” ou “merge”) no MySQL?


152

Existe uma maneira fácil para INSERTuma linha quando ela não existe, ou UPDATEse existe, usando uma consulta MySQL?

Respostas:


191

Use INSERT ... ON DUPLICATE KEY UPDATE. Por exemplo:

INSERT INTO `usage`
(`thing_id`, `times_used`, `first_time_used`)
VALUES
(4815162342, 1, NOW())
ON DUPLICATE KEY UPDATE
`times_used` = `times_used` + 1

10
Sim, acredito que o equivalente do SQL Server é chamado MERGE. Em geral, o conceito é frequentemente chamado de "UPSERT".
caos

3
@blub: Se você criar uma chave exclusiva gebe topicela funcionará ( ALTER TABLE table ADD UNIQUE geb_by_topic (geb, topic)).
caos

1
@ Brian: O equivalente da Oracle também é chamado, MERGEmas não tenho certeza se sua sintaxe é idêntica à do SQL Server.
22411 Ken Keenan

1
@ Brooks: Se você passar um 0, ele realmente usará 0 como valor. Então não faça isso. Não passe nada, nem passe a NULL, para permitir que o auto_incrementcomportamento funcione (que, caso contrário, sim, funciona como você supõe ; consulte dev.mysql.com/doc/refman/5.5/en/example-auto-increment. html ).
caos

3
Obrigado, e você pode usar VALUES(col)para obter o valor do argumento de inserção em caso de duplicação. Exemplo:ON DUPLICATE UPDATE b = VALUES(b), c = VALUES(c)
gerrytan 20/05

5

Sei que essa é uma pergunta antiga, mas o Google me levou aqui recentemente, então imagino que outros venham aqui também.

@chaos está correto: existe a INSERT ... ON DUPLICATE KEY UPDATEsintaxe.

No entanto, a pergunta original feita sobre o MySQL especificamente, e no MySQL existe a REPLACE INTO ...sintaxe. IMHO, esse comando é mais fácil e direto de usar para upserts. Do manual:

REPLACE funciona exatamente como INSERT, exceto que, se uma linha antiga da tabela tiver o mesmo valor que uma nova linha para um índice PRIMARY KEY ou UNIQUE, a linha antiga será excluída antes da inserção da nova linha.

Observe que este não é o SQL padrão. Um exemplo do manual:

CREATE TABLE test (
  id INT UNSIGNED NOT NULL AUTO_INCREMENT,
  data VARCHAR(64) DEFAULT NULL,
  ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (id)
);

mysql> REPLACE INTO test VALUES (1, 'Old', '2014-08-20 18:47:00');
Query OK, 1 row affected (0.04 sec)

mysql> REPLACE INTO test VALUES (1, 'New', '2014-08-20 18:47:42');
Query OK, 2 rows affected (0.04 sec)

mysql> SELECT * FROM test;
+----+------+---------------------+
| id | data | ts                  |
+----+------+---------------------+
|  1 | New  | 2014-08-20 18:47:42 |
+----+------+---------------------+
1 row in set (0.00 sec)

Edit: Apenas um aviso justo que REPLACE INTOnão é como UPDATE. Como o manual diz, REPLACEexclui a linha, se existir, depois insere uma nova. (Observe as engraçadas "2 linhas afetadas" no exemplo acima.) Ou seja, ele substituirá os valores de todas as colunas de um registro existente (e não apenas atualizará algumas colunas.) O comportamento do MySQL REPLACE INTOé muito parecido com o do Sqlite INSERT OR REPLACE INTO. Consulte esta pergunta para algumas soluções alternativas, se você deseja atualizar apenas algumas colunas (e nem todas), se o registro já existir.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.