Loading

ROUTE

ルートゼロの
アクティビティ

SQL MERGEとUPSERTの違い|DB別に整理

2

INSERTしたい行がすでに存在したらUPDATEしたい――この処理はよく「UPSERT」と呼ばれます。ただし、UPSERTは1つの共通SQLキーワードではなく、DBごとに構文が異なります。さらにMERGEは、sourceとtargetを照合して複数の条件分岐を行えるため、単純なunique conflict処理とは役割が少し違います。

まず比較:MERGEとUPSERT系構文は何が違う?

観点 MERGE UPSERT系構文
主な目的 sourceとtargetを照合し、条件ごとにINSERT/UPDATE/DELETE INSERT時の重複・競合を起点にINSERTまたはUPDATE
判定基準 JOIN条件とWHEN句 UNIQUE/PRIMARY KEYなどの競合
PostgreSQL例 MERGE INSERT … ON CONFLICT
MySQL例 製品/version仕様を確認 INSERT … ON DUPLICATE KEY UPDATE
向く処理 同期・一括取込・複数分岐 一意キー単位の登録/更新

PostgreSQLの公式資料でも、MERGEとINSERT … ON CONFLICTには違いと制約があり、相互に置き換え可能ではないと説明されています。まず「sourceとの照合が必要か」「unique conflictだけ処理したいか」で分けると選びやすくなります。


UPSERTは『INSERTまたはUPDATE』という処理パターン

用語解説:UPSERT
INSERTし、既存行との競合があればUPDATEする処理パターンの通称です。具体的な構文はDB製品ごとに異なります。

たとえばユーザーIDや商品コードのような一意キーで、存在しなければ追加、存在すれば更新したいケースが典型です。アプリ側でSELECT→INSERT/UPDATEと分岐させる代わりに、DB側の競合処理構文を使えます。


PostgreSQL:単純なUPSERTならON CONFLICT

PostgreSQLでは`INSERT … ON CONFLICT`を使い、unique constraintやunique indexの競合時に`DO NOTHING`または`DO UPDATE`を指定できます。

INSERT INTO users (id, name, updated_at)
VALUES (101, 'Sato', CURRENT_TIMESTAMP)
ON CONFLICT (id)
DO UPDATE SET
  name = EXCLUDED.name,
  updated_at = EXCLUDED.updated_at;

`ON CONFLICT DO UPDATE`は、提案された各行についてINSERTするか、指定した競合対象にぶつかった既存行をUPDATEします。unique conflictが判断軸なら、意図を読み取りやすい選択肢です。


PostgreSQL:sourceとtargetを照合するならMERGE

MERGEは`USING`のdata sourceとtarget tableをJOIN条件で照合し、`WHEN MATCHED`や`WHEN NOT MATCHED`ごとに処理を定義します。PostgreSQL 18ではINSERT、UPDATE、DELETE、DO NOTHINGを条件付きで組み合わせられます。

MERGE INTO products AS target
USING import_products AS src
ON target.id = src.id
WHEN MATCHED THEN
  UPDATE SET price = src.price
WHEN NOT MATCHED THEN
  INSERT (id, name, price)
  VALUES (src.id, src.name, src.price);

CSV取込後のstaging tableと本番tableを同期するように、source集合を基準に複数行を処理したい場合はMERGEの形が自然です。複数のWHEN条件を使う場合は、上から最初にtrueになったactionが実行されます。

MySQL:ON DUPLICATE KEY UPDATEを使う

MySQL 8.4では`INSERT … ON DUPLICATE KEY UPDATE`を使えます。INSERTしようとした行がPRIMARY KEYまたはUNIQUE indexの重複を起こす場合、既存行をUPDATEします。

INSERT INTO users (id, name, updated_at)
VALUES (101, 'Sato', CURRENT_TIMESTAMP) AS new
ON DUPLICATE KEY UPDATE
  name = new.name,
  updated_at = new.updated_at;

MySQL公式ドキュメントでは、複数のunique indexがあるtableでの利用には注意が必要とされています。また、旧来の`VALUES(column)`参照は非推奨のため、現行8.4ではrow aliasを使う書き方も確認しておきます。

どちらを選ぶ?実務の判断基準

やりたいこと 候補
一意キーが重複したらその行を更新 PostgreSQL ON CONFLICT / MySQL ON DUPLICATE KEY UPDATE
source tableとtarget tableを照合して同期 MERGE
matched時にも条件を分けたい MERGE
競合時は何もしない PostgreSQL ON CONFLICT DO NOTHINGなどDB固有構文
複数DBで同じSQLを使いたい 各DBの対応構文・versionを先に確認

「1文で書けるか」だけで選ぶと、競合条件や同時実行時の挙動を見落とします。unique key、sourceの重複、transaction isolation、対象DBのversionを含めてテストします。

注意点:DB固有の挙動まで同じではない

  • UPSERTという名前だけで構文を決めず、対象DBの公式構文を確認する
  • どのUNIQUE/PRIMARY KEYが競合判定に使われるか確認する
  • MERGEのsourceが同じtarget rowへ複数回対応しないよう確認する
  • 複数unique keyがある場合のDB固有挙動を確認する
  • 同時実行時の挙動はtransaction/isolationを含めて検証する

構文が似ていても、競合判定・権限・trigger・concurrencyの詳細はDB製品やversionで異なります。migration時はSQLの見た目だけで置き換えないようにします。

まとめ:unique conflictかsource同期かで選ぶ

  • UPSERTはINSERTまたはUPDATEの処理パターンで、構文はDBごとに異なる
  • PostgreSQLのON CONFLICTはunique conflict処理に向く
  • MERGEはsourceとtargetを照合した複数分岐に向く
  • MySQLではON DUPLICATE KEY UPDATEが代表的
  • MERGEとUPSERT系構文を同一視せず、判定条件から選ぶ

まず対象処理が「一意キー競合の処理」なのか「sourceとtargetの同期」なのかを書き出し、その後で利用DBの現行構文を選んでみてください。

カジュアル面談はこちら

RANKINGranking-icon

すべてを開示している会社です

ポジティブな面もネガティブな面も、包み隠さず誠実にお答えします。
気になることがあれば、面談の際にすべてオープンにお話します。