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;

PostgreSQL의 고전적인 upsert는 INSERT ... ON CONFLICT (unique column) 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를 사용한다. unique 키나 primary 키가 충돌하면 행이 갱신된다. 새 값은 VALUES(my_column)(8.0.19+에서는 'new.my_column' 행 별칭)으로 접근한다. INSERT IGNORE는 충돌 시 행을 조용히 건너뛴다. MySQL에는 표준 MERGE가 없다.

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는 2008부터 MERGE 문을 지원하며, 대상의 남는 행을 삭제하는 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는 네이티브 옵션이다. updateOne/updateMany/findOneAndUpdate에 { upsert: true }를 추가하면 일치하는 문서는 갱신되고, 없으면 새 문서가 생성된다. 대량 동기화에는 aggregation $merge 단계(대상 컬렉션에 쓰기/갱신)가 SQL 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에서 같은 _id로 index(PUT)를 호출하면 문서가 있으면 전체를 교체하고, 없으면 생성한다(upsert와 유사). 없으면 삽입하는 부분 갱신에는 _update API에서 'doc_as_upsert': true나 'upsert' 블록을 사용한다. 스크립트 기반 upsert도 가능하다.

DBAtlas에서 열기 →