DBAtlas · Атлас эксплуатации баз данных

MERGE / UPSERT (обновить, если есть, иначе вставить)

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);

MERGE (upsert) сопоставляет источник с целью, обновляя совпадающие строки и вставляя несовпадающие; идеален для массовой синхронизации данных. В Oracle сильная поддержка MERGE: можно писать также WHEN MATCHED ... DELETE и условные обновления. Для источника из одной строки используется таблица 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;

Классический upsert в PostgreSQL — INSERT ... ON CONFLICT (уникальный столбец) DO UPDATE / DO NOTHING; новые значения конфликтующей строки доступны через псевдоним EXCLUDED. С PostgreSQL 15 поддерживается и стандартный оператор MERGE. ON CONFLICT атомарен на одной таблице и широко используется.

MySQL

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

MySQL/MariaDB используют для upsert INSERT ... ON DUPLICATE KEY UPDATE; при конфликте уникального или первичного ключа строка обновляется. Новые значения доступны через VALUES(my_column) (или псевдоним строки 'new.my_column' в 8.0.19+). INSERT IGNORE молча пропускает строку при конфликте. Стандартного MERGE в MySQL нет.

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);

SQL Server поддерживает оператор MERGE с 2008; он предлагает дополнительные ветви, например WHEN NOT MATCHED BY SOURCE для удаления лишних строк в цели. Оператор MERGE должен завершаться точкой с запятой. Примечание: из-за некоторых ошибок в граничных случаях для простых upsert могут быть предпочтительны отдельные операторы INSERT/UPDATE.

MongoDB

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

В MongoDB upsert — нативная опция: если добавить { upsert: true } к updateOne/updateMany/findOneAndUpdate, совпадающий документ обновляется, иначе создаётся новый. Для массовой синхронизации ближе к SQL MERGE этап агрегации $merge (запись/обновление в целевую коллекцию).

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

В Redis команды записи по своей природе являются upsert: SET/HSET обновляют ключ или поле, если они есть, иначе создают. Опция SET ... NX — запись, только если ключа нет, XX — только если он есть. Различие «обновить, если есть, иначе вставить» уже является поведением большинства команд по умолчанию.

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 }

В Elasticsearch вызов index (PUT) с тем же _id полностью заменяет документ, если он есть, иначе создаёт его (аналог upsert). Для частичных обновлений со вставкой при отсутствии используйте 'doc_as_upsert': true или блок 'upsert' в API _update. Возможны и скриптовые upsert.

Открыть в DBAtlas →