MERGE had been discussed on the PostgreSQL mailing list for years before it landed in core with PostgreSQL 15. The command combines insert, update and delete logic into a single statement whose behavior is governed by explicit match rules.
Preparing a target table
A target table is built from two columns, the second holding ten times the value of the first.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
db15=# CREATE TABLE t_test ( id serial PRIMARY KEY, val int ); CREATE TABLE db15=# INSERT INTO t_test (val) SELECT x * 10 FROM generate_series(1, 10) AS x; INSERT 0 10 db15=# SELECT * FROM t_test; id | val ---+----- 1 | 10 2 | 20 3 | 30 4 | 40 5 | 50 6 | 60 7 | 70 8 | 80 9 | 90 10 | 100 (10 rows) |
This table receives the changes applied by the operations that follow.
Merging a source into the target
The USING clause produces a source relation, y, which is merged into t_test as the target. The WHEN clauses define what happens on a match and on its absence: matching rows are overwritten, missing rows are inserted.
|
1 2 3 4 5 6 7 8 9 10 |
db15=# MERGE INTO t_test USING (SELECT x, random() * 1000 AS z FROM generate_series(1, 16, 2) AS x ) AS y ON t_test.id = y.x WHEN MATCHED THEN UPDATE SET val = z WHEN NOT MATCHED THEN INSERT (val) VALUES (z); MERGE 8 |
Two details stand out. The UPDATE statement no longer needs the target table to be named, and the same holds for the trailing INSERT — the table is not repeated there either.
The output reflects those rules: all odd numbers were updated, the remaining rows were left untouched, and the missing rows were added.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
db15=# SELECT * FROM t_test ORDER BY id; id | val ---+----- 1 | 101 2 | 20 3 | 656 4 | 40 5 | 309 6 | 60 7 | 897 8 | 80 9 | 195 10 | 100 11 | 634 12 | 625 13 | 50 (13 rows) |
Deleting through MERGE
DELETE is also permitted inside the statement.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
db15=# MERGE INTO t_test USING (SELECT x, random() * 1000 AS z FROM generate_series(1, 16, 2) AS x) AS y ON t_test.id = y.x WHEN MATCHED THEN DELETE WHEN NOT MATCHED THEN INSERT (val) VALUES (z); MERGE 8 db15=# SELECT * FROM t_test ORDER BY id; id | val ---+----- 2 | 20 4 | 40 6 | 60 8 | 80 10 | 100 12 | 625 14 | 648 (7 rows) |
Here every matching row is removed. The DELETE takes no further arguments — which rows are affected is unambiguous, so no column information is required.
For PostgreSQL 15, MERGE has been a long-awaited addition, and it simplifies a substantial amount of surrounding code.



