【PostgreSQL】MERGE文を使ってUpsertする方法
はじめに
前回の記事で、PostgreSQLでUpsertする方法のうち、ON CONFLICT句
を使った方法を見ていきました。
今回はそこでは触れられなかったMERGE文によるUpsertを見ていきます!
この記事でわかること
- MERGE文を使ってUpsertする方法
- RETURNING句を使ったDMLの確認方法
MERGE文を使ってUpsertする方法
PostgreSQLにおける MERGE文
は、2022年10月13日にリリースされたバージョン15.0で追加されました。ON CONFLICT句
同様、Upsertをするためのものとなります。
MERGE文
の構文は以下の通りです。
MERGE INTO <対象テーブル> AS <エイリアス>
USING <データソース> AS エイリアス
ON <結合条件>
WHEN MATCHED THEN
<更新または削除処理>
WHEN NOT MATCHED THEN
<登録処理>;
各要素には、以下のような意味や役割があります。
要素 | 意味 |
---|---|
MERGE INTO |
対象となるテーブルを指定 |
USING |
比較対象となるデータソース(テーブル・副問い合わせ・VALUES句)を指定 |
ON |
対象テーブルとデータソースを照合するための結合条件を指定 |
WHEN MATCHED THEN |
既存データがある場合の処理を指定(通常はUPDATEまたはDELETE) |
WHEN NOT MATCHED THEN |
新規データの場合の処理を指定(通常はINSERT) |
それでは、実際にMERGE文
を使ってUpsertをしてみましょう!
前提
ここでは、ユーザーを管理するusersテーブル
と、一時的にユーザー情報を保持するためのusers_sourceテーブル
が存在するものとします。
これらのテーブルの関係性イメージは、以下のようなものとします。
- 外部システムや別のDBから、夜間バッチでユーザーデータをusers_sourceテーブルに取り込む
- ユーザー情報の更新があったらusersテーブルに反映
- 新規ユーザーの場合はusersテーブルにもデータを反映
それぞれ、既に以下のようなデータが存在するものとします。
-- SELECT * FROM users;
id | name | birthday
----+--------+------------
1 | tomoya | 2025-10-15
(1 row)
-- SELECT * FROM users_source;
id | name | birthday
----+--------+------------
1 | tomoya | 2025-10-15
(1 row)
既にデータが存在する場合
users_sourceテーブルに存在するデータのうち、nameカラムに変更があった場合を想定します。
-- SELECT * FROM users_source;
id | name | birthday
----+----------------+------------
1 | tomoya updated | 2025-10-15
このデータを元に、MERGE文を用いてUpsertを行います。
MERGE INTO users AS targe
USING users_source AS source
ON targe.id = source.id
WHEN MATCHED THEN
UPDATE
SET name = source.name
WHEN NOT MATCHED THEN
INSERT (id, name, birthday)
VALUES (source.id, source.name, source.birthday);
実行すると、以下のような結果となります。
-- MERGE 1
-- SELECT * FROM users;
-- nameが更新されていることがわかる
id | name | birthday
----+----------------+------------
1 | tomoya updated | 2025-10-15
(1 row)
新規データの場合
users_sourceテーブルに、新たなデータが登録されたものとします。
-- SELECT * FROM users_source;
id | name | birthday
----+----------------+------------
1 | tomoya updated | 2025-10-15
2 | Alice | 2025-10-18
(2 rows)
このデータを元に、MERGE文を用いてUpsertを行います。
MERGE INTO users AS targe
USING users_source AS source
ON targe.id = source.id
WHEN MATCHED THEN
UPDATE
SET name = source.name
WHEN NOT MATCHED THEN
INSERT (id, name, birthday)
VALUES (source.id, source.name, source.birthday);
実行すると、以下のような結果となります。
-- MERGE 2
-- SELECT * FROM users;
id | name | birthday
----+----------------+------------
1 | tomoya updated | 2025-10-15
2 | Alice | 2025-10-18
(2 rows)
RETURNING句を使ってみる
2024年9月26日にリリースされたPostgreSQL 17.0では、MERGE文に RETURNING句
を使うことができるようになりました。merge_action()
という関数を使うことで、実行されたDML(INSERT/UPDATE/DELETE)を確認することができます。
今回は、users_sourceテーブルに、さらに新たなデータが登録されたものとします。
id | name | birthday
----+----------------+------------
1 | tomoya updated | 2025-10-15
2 | Alice | 2025-10-18
3 | Bob | 2025-10-19
(3 rows)
このデータを元に、MERGE文を用いてUpsertを行います。
MERGE INTO users AS target
USING users_source AS source
ON target.id = source.id
WHEN MATCHED THEN
UPDATE
SET name = source.name
WHEN NOT MATCHED THEN
INSERT (id, name, birthday)
VALUES (source.id, source.name, source.birthday)
RETURNING
merge_action() AS action,
target.*;
実行すると、以下のような結果となります。
action | id | name | birthday
--------+----+----------------+------------
UPDATE | 1 | tomoya updated | 2025-10-15 -- 既存データの更新
UPDATE | 2 | Alice | 2025-10-18 -- 既存データの更新
INSERT | 3 | Bob | 2025-10-19 -- 新規データの登録
(3 rows)
MERGE 3
まとめ
今回は、PostgreSQL 15.0で追加されたMERGE文を用いたUpsertの方法を見ていきました。
登録・更新するテーブルとソースとなるテーブルを結合し、データが存在する場合はUpdate、データが存在しない場合はInsertをするような仕組みとなっております。
また、PostgreSQL 17.0で追加されたRETURNING句を用いることで、どのようなDMLを実行したのかを明示的に確認することができます。
Discussion