PostgreSQL 15: 支持 MERGE 命令

John Doe 七月 25, 2025

你有在 Oracle 中使用过 MERGE 语句吗?你需要将它们迁移到 PostgreSQL 吗?

与 SQL 共舞的大象

特性提交日志

新增对 MERGE SQL 命令的支持。

MERGE 命令可利用源表或查询来修改目标表中的行。它通过单条 SQL 语句,就能有条件地执行 INSERT/UPDATE/DELETE 操作,而这项任务原本需要多条存储过程的语句才能完成。例如:

MERGE INTO target AS t
USING source AS s
ON t.tid = s.sid
WHEN MATCHED AND t.balance > s.delta THEN
  UPDATE SET balance = t.balance - s.delta
WHEN MATCHED THEN
  DELETE
WHEN NOT MATCHED AND s.delta > 0 THEN
  INSERT VALUES (s.sid, s.delta)
WHEN NOT MATCHED THEN
  DO NOTHING;

MERGE 适用于普通表、分区表和继承关系表,支持列级和行级安全策略的实施,也支持行级触发器、语句级触发器以及其中的转换表。

MERGE 针对 OLTP 场景进行了优化,且支持参数化,同时也适用于大规模的 ETL/ELT 操作。但由于存在一定的额外开销,在仅需执行 INSERT、UPDATE 或 DELETE 等单一操作时,不建议优先使用 MERGE。MERGE 可在 PL/pgSQL 中使用。

MERGE 不支持以可更新视图或外部表为目标,也不允许使用 RETURNING 子句。这些限制有望通过进一步的努力解决。此外,MERGE 也不支持重写规则,不过目前尚不清楚是否有必要支持这一功能。

讨论:https://postgr.es/m/CANP8+jKitBSrB7oTgT9CY2i1ObfOt36z0XMraQc+Xrz8QB0nXA@mail.gmail.com

示例

MERGE 让你有机会通过一条 SQL 语句,对普通表、分区表等中的行执行 INSERT/UPDATE/DELETE 操作。如果将其用作单一 SQL 命令,会存在一些额外开销,因为需要用到大量的 WHEN/THEN 表达式。

我们通过一个简单示例来了解这项新功能,示例中使用两个结构相似但其中一个包含更多条目的表。

select * from category;
 category_id |    name     |     last_update
-------------+-------------+---------------------
           1 | Action      | 2006-02-15 09:46:27
           2 | Animation   | 2006-02-15 09:46:27
           3 | Children    | 2006-02-15 09:46:27
           4 | Classics    | 2006-02-15 09:46:27
           5 | Comedy      | 2006-02-15 09:46:27
           6 | Documentary | 2006-02-15 09:46:27
           7 | Drama       | 2006-02-15 09:46:27
           8 | Family      | 2006-02-15 09:46:27
           9 | Foreign     | 2006-02-15 09:46:27
          10 | Games       | 2006-02-15 09:46:27
          11 | Horror      | 2006-02-15 09:46:27
          12 | Music       | 2006-02-15 09:46:27
          13 | New         | 2006-02-15 09:46:27
          14 | Sci-Fi      | 2006-02-15 09:46:27
          15 | Sports      | 2006-02-15 09:46:27
          16 | Travel      | 2006-02-15 09:46:27
(16 rows)
 
select * from category_new;
 category_id |    name     |        last_update
-------------+-------------+----------------------------
           1 | Action      | 2006-02-15 09:46:27
           2 | Animation   | 2006-02-15 09:46:27
           3 | Children    | 2006-02-15 09:46:27
           4 | Classics    | 2006-02-15 09:46:27
           5 | Comedy      | 2006-02-15 09:46:27
           6 | Documentary | 2006-02-15 09:46:27
           7 | Drama       | 2006-02-15 09:46:27
           8 | Family      | 2006-02-15 09:46:27
           9 | Foreign     | 2006-02-15 09:46:27
          10 | Games       | 2006-02-15 09:46:27
          11 | Horror      | 2006-02-15 09:46:27
          12 | Music       | 2006-02-15 09:46:27 
          13 | Biography   | 2022-04-12 11:53:34.986878
          14 | Sci-Fi      | 2006-02-15 09:46:27
          15 | Sports      | 2006-02-15 09:46:27
          16 | Travel      | 2006-02-15 09:46:27
          17 | Dramedy     | 2022-04-12 11:48:49.559058
          18 | Love        | 2022-04-12 11:49:32.072536
(17 rows)
 
MERGE INTO category AS c
USING category_new AS n
ON c.category_id = n.category_id
WHEN MATCHED AND c.name = n.name  THEN
  DO NOTHING
WHEN MATCHED AND c.name  n.name THEN
  UPDATE SET name=n.name
WHEN NOT MATCHED THEN
  INSERT VALUES (n.category_id, n.name, n.last_update);

MERGE 命令执行完成后,再次查询原始表,查看是否按计划完成了所有添加和更新操作:

select * from category order by 1;
 category_id |    name     |        last_update
-------------+-------------+----------------------------
           1 | Action      | 2006-02-15 09:46:27
           2 | Animation   | 2006-02-15 09:46:27
           3 | Children    | 2006-02-15 09:46:27
           4 | Classics    | 2006-02-15 09:46:27
           5 | Comedy      | 2006-02-15 09:46:27
           6 | Documentary | 2006-02-15 09:46:27
           7 | Drama       | 2006-02-15 09:46:27
           8 | Family      | 2006-02-15 09:46:27
           9 | Foreign     | 2006-02-15 09:46:27
          10 | Games       | 2006-02-15 09:46:27
          11 | Horror      | 2006-02-15 09:46:27
          12 | Music       | 2006-02-15 09:46:27
          13 | Biography   | 2022-04-12 13:42:26.187381
          14 | Sci-Fi      | 2006-02-15 09:46:27
          15 | Sports      | 2006-02-15 09:46:27
          16 | Travel      | 2006-02-15 09:46:27
          17 | Dramedy     | 2022-04-12 11:48:49.559058
          18 | Love        | 2022-04-12 11:49:32.072536
(18 rows)

MERGE 不支持外部表或可更新视图。如果有需求,这一功能或许会在后续推出,但目前暂无相关计划。

这样一来,PostgreSQL 的 SQL 命令更加兼容 Oracle 了。非常不错的体验,感谢所有参与的社区人员。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/7103ebb7aae8ab8076b7e85f335ceb8fe799097c