PostgreSQL 17: 支持 MERGE … WHEN NOT MATCHED BY SOURCE

John Doe 五月 15, 2025

你希望批量加载数据进表时,识别出目标表中存在但数据源中不存在的行吗?现在,PostgreSQL 已经支持 MERGE … WHEN NOT MATCHED BY SOURCE 了。

在山坡漫步的大象

特性提交日志

支持 MERGE…WHEN NOT MATCHED BY SOURCE 语法。

这使得 MERGE 命令可以包含 WHEN NOT MATCHED BY SOURCE 操作,该操作针对目标表中存在但数据源中不存在的行。这些操作可以执行 UPDATE、DELETE 或 DO NOTHING 子命令。

这与已支持的 WHEN NOT MATCHED 操作形成对比,后者针对数据源中存在但目标表中不存在的行。为了更清晰地区分,此类操作现在可以写为 WHEN NOT MATCHED BY TARGET。

如果只写 WHEN NOT MATCHED 而不指定 BY SOURCE 或 BY TARGET,则等同于 WHEN NOT MATCHED BY TARGET。

讨论https://postgr.es/m/CAEZATCWqnKGc57Y_JanUBHQXNKcXd7r=0R4NEZUVwP+syRkWbA@mail.gmail.com

示例

这个特性非常棒。自 PostgreSQL 15 起,PostgreSQL 已经支持了 MERGE 命令,它用于根据某些条件以单个命令插入、删除或更新一组行。现在,我们又迎来了一个相当不错的新特性。

让我们来进行一些测试:

create table base_data (
    id int8 generated always as identity primary key,
    codename text not null unique,
    val int4 not null default 0
);

insert into base_data (codename, val)
  select x, length(x) * 10 from unnest('{yep,duel,young,manias}'::text[]) x;

select * from base_data;
 id | codename | val 
----+----------+-----
  1 | yep      |  30
  2 | duel     |  40
  3 | young    |  50
  4 | manias   |  60
(4 rows)

create table incr as
  select codename, floor(random() * 3) - 1 as diff from base_data;

insert into incr (codename, diff) values ('redrock', 15 );

select * from incr;
 codename | diff 
----------+------
 yep      |   -1
 duel     |    1
 young    |    0
 manias   |    0
 redrock  |   15
(5 rows)

现在,可以使用 MERGE 命令,将 incr 表中的所有数据合并到 base_data 表中,并更新 val 值:

MERGE INTO base_data as b
    using incr as i
    ON b.codename = i.codename
WHEN MATCHED THEN
    UPDATE SET val = b.val + i.diff
WHEN NOT MATCHED THEN
    INSERT (codename, val) VALUES (i.codename, i.diff)
;

SELECT * FROM base_data;
 id | codename | val 
----+----------+-----
  1 | yep      |  29
  2 | duel     |  41
  3 | young    |  50
  4 | manias   |  60
  7 | redrock  |  15
(5 rows)

如你所见,所有的差异值都被正确地应用了。不过,新特性才是真正的亮点。让我们从 incr 表中删除一行数据:

delete from incr where codename = 'duel';

现在,执行修改后的 MERGE 命令:

MERGE INTO base_data as b
    using incr as i
    ON b.codename = i.codename
WHEN MATCHED THEN
    UPDATE SET val = b.val + i.diff
WHEN NOT MATCHED THEN
    INSERT (codename, val) VALUES (i.codename, i.diff)
WHEN NOT MATCHED BY SOURCE THEN
    DELETE
;

select * from base_data;
 id | codename | val 
----+----------+-----
  1 | yep      |  28
  3 | young    |  50
  4 | manias   |  60
  7 | redrock  |  30
(4 rows)

请注意,我们从 incr 表中删除了 ‘duel’,而 MERGE 操作也从 base_data 表中删除了该记录。神奇之处在于其中的WHEN NOT MATCHED BY SOURCE子句,它意味着如果正在合并到的目标表中存在数据源(USING 子句指定的表)中不存在的行,我们就可以对目标表中的这些行执行所需的操作。

这个特性绝对是很有用的,它将使各种数据加载或更新的处理变得更加简单。

非常不错的体验,感谢所有参与的社区人员。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/0294df2f1f842dfb0eed79007b21016f486a3c6c