由 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