SQL中的MERGE语句是一种高效的数据操作语句,它可以根据源表和目标表的匹配结果,自动执行插入、更新或删除操作,常被称为upsert操作,能够替代传统需要先判断数据是否存在再执行对应操作的冗余逻辑。

MERGE语句基础语法
MERGE语句的核心逻辑是先匹配源表和目标表的数据,再根据匹配结果执行不同的操作,通用的基础语法结构如下:
-- 通用MERGE语法结构
MERGE INTO 目标表 AS target
USING 源表 AS source
ON 匹配条件
WHEN MATCHED THEN
-- 匹配成功时执行的操作,通常是更新
UPDATE SET 目标表列 = 源表列
WHEN NOT MATCHED THEN
-- 匹配失败时执行的操作,通常是插入
INSERT (列1, 列2) VALUES (源表.列1, 源表.列2)
WHEN NOT MATCHED BY SOURCE THEN
-- 目标表存在但源表不存在时的操作,可选,通常是删除
DELETE;
语法各部分说明
- MERGE INTO:指定要操作的目标数据表,也就是最终数据要同步到的表。
- USING:指定源数据表,可以是实际的数据表,也可以是临时表或者查询出来的结果集。
- ON:设置源表和目标表的匹配条件,通常是主键或者唯一标识列的相等判断。
- WHEN MATCHED:当源表和目标表的数据满足ON后的匹配条件时,执行该分支的逻辑,一般用来更新已存在的数据。
- WHEN NOT MATCHED:当源表的数据在目标表中不存在时,执行该分支的逻辑,一般用来插入新数据。
- WHEN NOT MATCHED BY SOURCE:当目标表的数据在源表中不存在时,执行该分支的逻辑,一般用来删除目标表中多余的数据,该分支是可选的。
实际使用示例
示例场景准备
假设我们有两个数据表,一个是用户目标表user_target,存储全量用户数据,一个是用户增量表user_source,存储新增或者更新的用户数据,表结构如下:
-- 创建目标表
CREATE TABLE user_target (
user_id INT PRIMARY KEY,
user_name VARCHAR(50),
user_age INT,
update_time DATETIME
);
-- 创建源表
CREATE TABLE user_source (
user_id INT PRIMARY KEY,
user_name VARCHAR(50),
user_age INT,
update_time DATETIME
);
-- 插入测试数据到目标表
INSERT INTO user_target VALUES (1, '张三', 20, '2024-01-01 10:00:00');
INSERT INTO user_target VALUES (2, '李四', 22, '2024-01-01 10:00:00');
-- 插入测试数据到源表
INSERT INTO user_source VALUES (1, '张三', 21, '2024-01-02 10:00:00'); -- 已存在,需要更新
INSERT INTO user_source VALUES (3, '王五', 25, '2024-01-02 10:00:00'); -- 不存在,需要插入
执行MERGE合并操作
现在需要将user_source的数据同步到user_target中,已存在的用户更新年龄和更新时间,不存在的用户插入新数据,使用MERGE语句实现如下:
MERGE INTO user_target AS target
USING user_source AS source
ON target.user_id = source.user_id
WHEN MATCHED THEN
UPDATE SET
target.user_name = source.user_name,
target.user_age = source.user_age,
target.update_time = source.update_time
WHEN NOT MATCHED THEN
INSERT (user_id, user_name, user_age, update_time)
VALUES (source.user_id, source.user_name, source.user_age, source.update_time);
执行上述语句后,查询user_target表,结果如下:
SELECT * FROM user_target; -- 结果: -- user_id | user_name | user_age | update_time -- 1 | 张三 | 21 | 2024-01-02 10:00:00 -- 2 | 李四 | 22 | 2024-01-01 10:00:00 -- 3 | 王五 | 25 | 2024-01-02 10:00:00
包含删除操作的MERGE示例
如果还需要删除user_target中存在但user_source中不存在的数据,比如用户注销场景,只需要添加WHEN NOT MATCHED BY SOURCE分支即可:
MERGE INTO user_target AS target
USING user_source AS source
ON target.user_id = source.user_id
WHEN MATCHED THEN
UPDATE SET
target.user_name = source.user_name,
target.user_age = source.user_age,
target.update_time = source.update_time
WHEN NOT MATCHED THEN
INSERT (user_id, user_name, user_age, update_time)
VALUES (source.user_id, source.user_name, source.user_age, source.update_time)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
如果此时user_source中没有user_id为2的数据,执行上述语句后,user_id为2的用户数据会被从user_target中删除。
不同数据库的使用差异
虽然MERGE是SQL标准语句,但不同数据库的实现存在一定差异:
| 数据库类型 | 差异说明 |
|---|---|
| SQL Server | 完整支持上述标准语法,所有分支都可以正常使用,需要注意语句结尾要加分号。 |
| Oracle | 不支持WHEN NOT MATCHED BY SOURCE分支,无法直接通过MERGE实现删除操作,需要额外编写删除逻辑。 |
| MySQL | 8.0版本之前不支持MERGE语句,8.0及之后版本支持,语法和标准形式基本一致。 |
| PostgreSQL | 15版本之前不支持MERGE语句,15及之后版本支持,同时也可以使用INSERT ... ON CONFLICT语句实现类似的upsert功能。 |
使用注意事项
- 匹配条件
ON后面的列最好是主键或者有唯一索引的列,避免出现一对多的匹配情况,否则会导致语句执行报错。 - 不要在MERGE语句的同一分支中同时执行更新和插入操作,每个分支只能执行一种类型的操作。
- 如果源表是查询出来的结果集,要确保结果集的数据没有重复,否则可能出现重复匹配的问题。
- 执行MERGE语句前最好先备份目标表数据,避免误操作导致数据丢失。
- 当目标表有触发器时,MERGE操作的触发逻辑和单独的插入、更新、删除操作的触发逻辑一致,需要提前确认触发器的逻辑是否符合预期。
常见使用场景
- 数据同步:将增量数据表的数据同步到全量数据表中,比如每日的用户增量数据同步到用户全量表。
- 数据修复:根据正确的源数据修复目标表中错误的数据,匹配到的更新,没匹配到的插入。
- 缓存更新:将业务库的变更数据同步到缓存对应的数据表中,保持缓存数据和业务库数据一致。