由 John Doe 七月 25, 2025
你有在 Oracle 中使用过 MERGE 语句吗?你需要将它们迁移到 PostgreSQL 吗?
特性提交日志
新增对 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