DBAtlas · Atlas de operações de banco de dados

MERGE / UPSERT (atualizar se existir, senão inserir)

Oracle

MERGE INTO customer m
USING (SELECT 1 AS id, 'Ali' AS name, 500 AS balance FROM dual) k
ON (m.id = k.id)
WHEN MATCHED THEN
  UPDATE SET m.name = k.name, m.balance = k.balance
WHEN NOT MATCHED THEN
  INSERT (id, name, balance) VALUES (k.id, k.name, k.balance);

O MERGE (upsert) compara uma origem com um destino, atualizando as linhas correspondentes e inserindo as não correspondentes; ideal para sincronização de dados em massa. O Oracle tem um suporte robusto a MERGE: também é possível escrever WHEN MATCHED ... DELETE e atualizações condicionais. Para uma origem de linha única, usa-se a tabela dual.

PostgreSQL

-- The PostgreSQL way: INSERT ... ON CONFLICT
INSERT INTO customer (id, name, balance)
VALUES (1, 'Ali', 500)
ON CONFLICT (id) DO UPDATE
  SET name = EXCLUDED.name, balance = EXCLUDED.balance;

O upsert clássico do PostgreSQL é INSERT ... ON CONFLICT (coluna única) DO UPDATE / DO NOTHING; os novos valores da linha em conflito são acessados pelo alias EXCLUDED. Desde o PostgreSQL 15, a instrução MERGE padrão também é suportada. ON CONFLICT é atômico em uma única tabela e muito usado.

MySQL

INSERT INTO customer (id, name, balance)
VALUES (1, 'Ali', 500)
ON DUPLICATE KEY UPDATE
  name = VALUES(name), balance = VALUES(balance);

O MySQL/MariaDB usam INSERT ... ON DUPLICATE KEY UPDATE para upserts; se uma chave única ou primária entrar em conflito, a linha é atualizada. Os novos valores são acessados com VALUES(my_column) (ou o alias de linha 'new.my_column' no 8.0.19+). INSERT IGNORE pula a linha silenciosamente em caso de conflito. O MySQL não tem MERGE padrão.

SQL Server

MERGE INTO customer AS m
USING (VALUES (1, 'Ali', 500)) AS k(id, name, balance)
ON m.id = k.id
WHEN MATCHED THEN
  UPDATE SET m.name = k.name, m.balance = k.balance
WHEN NOT MATCHED THEN
  INSERT (id, name, balance) VALUES (k.id, k.name, k.balance);

O SQL Server suporta a instrução MERGE desde 2008; ela oferece ramos extras, como WHEN NOT MATCHED BY SOURCE, para excluir linhas excedentes no destino. Uma instrução MERGE deve terminar com ponto e vírgula. Observação: devido a alguns bugs em casos extremos, pode ser preferível escrever instruções INSERT/UPDATE separadas para upserts simples.

MongoDB

// Find-and-update or insert with upsert:true
db.customer.updateOne(
  { id: 1 },
  { $set: { name: "Ali", balance: 500 } },
  { upsert: true }
)

No MongoDB, o upsert é uma opção nativa: se { upsert: true } for adicionado a updateOne/updateMany/findOneAndUpdate, um documento correspondente é atualizado; caso contrário, um novo documento é criado. Para sincronização em massa, o estágio de agregação $merge (gravar/atualizar em uma coleção de destino) é mais próximo do MERGE do SQL.

Redis

# SET is a natural upsert: it overwrites the key if present, otherwise creates it.
SET customer:1 '{"name":"Ali","balance":500}'
# Update/add a hash field:
HSET customer:1 name Ali balance 500

No Redis, os comandos de escrita são intrinsecamente upserts: SET/HSET atualizam a chave ou o campo se existir; caso contrário, o criam. Use a opção SET ... NX para gravar apenas se não existir, e XX apenas se existir. A distinção 'atualizar se existir, senão inserir' já é o comportamento padrão da maioria dos comandos.

Elasticsearch

# An index call with a document id acts like an upsert (replaces if present):
PUT customer/_doc/1
{ "name": "Ali", "balance": 500 }
# Partial update + insert if absent:
POST customer/_update/1
{ "doc": { "balance": 500 }, "doc_as_upsert": true }

No Elasticsearch, uma chamada index (PUT) com o mesmo _id substitui totalmente o documento se ele existir; caso contrário, o cria (semelhante a upsert). Para atualizações parciais com inserção se não existir, use 'doc_as_upsert': true ou um bloco 'upsert' na API _update. Upserts com script também são possíveis.

Abrir no DBAtlas →