惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
V
V2EX
量子位
Last Week in AI
Last Week in AI
Jina AI
Jina AI
博客园 - 【当耐特】
爱范儿
爱范儿
宝玉的分享
宝玉的分享
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
博客园 - 三生石上(FineUI控件)
有赞技术团队
有赞技术团队
小众软件
小众软件
IT之家
IT之家
博客园_首页
博客园 - 聂微东
S
SegmentFault 最新的问题
阮一峰的网络日志
阮一峰的网络日志
博客园 - 叶小钗

博客园 - 李文学

Windows Server 2016 64位 ASP+Access(.mdb) 80004005 最简解决方案 Oracle查询表中指定库名,表约束名的上下表依赖关系 linux简版启动,关闭jar命令 Oracle11.2实例重启 Centos7扩容2 wps,两个页签比较常用函数 Oracle常见SQL示例 VUE-手机号验证 Centos7安装mysql8 Centos7安装ffmpeg 不同数据库标识长度检查 Centos7重置网络 Centos7磁盘空间自动处理 Oracle11新建业务库账号信息 Java启动JAR,.sh版 linux常用命令 centos7防火墙IP白名单维护 centos7安装lamp,php5.6,mysql5.7,xdebug MSSQL最大内存设置
主流数据库 “匹配更新、不匹配插入” 语法详解(Oracle/MSSQL/...
李文学 · 2025-12-30 · via 博客园 - 李文学

在数据库数据同步场景中,“匹配则更新、不匹配则插入”(即 “UPSERT”)是高频需求。Oracle 原生支持 MERGE 语句,SQL Server(MSSQL)后续跟进了类似语法,而 MySQL 则通过专属语法实现等效功能。本文将详细对比三大主流数据库的实现方案、语法细节及使用注意事项,附可直接运行的实战示例。

Oracle 是最早原生支持 MERGE INTO 语句的数据库之一,语法简洁直观,无需依赖额外约束,仅通过 ON 子句定义匹配条件即可。

MERGE INTO 目标表 别名
USING 源表/子查询 别名
ON (匹配条件)
WHEN MATCHED THEN  -- 匹配成功:执行更新
  UPDATE SET 字段=WHEN NOT MATCHED THEN  -- 匹配失败:执行插入
  INSERT (字段列表) VALUES (值列表);
-- 同步待同步标识为1、指定租户的组织数据到目标表
MERGE INTO sec_org o
USING (
    SELECT 
        org_id, parent_org_id, org_short_name, org_name,
        org_type, org_wbs_code, org_wbs_level, orderby,
        status, remark, update_time, tenant_code
    FROM sync_org 
    WHERE is_sync = 1 
      AND tenant_code = 'TENANT001'  -- 替换为实际租户编码
) so
ON (o.org_id = so.org_id)  -- 以组织ID作为唯一匹配标识
WHEN MATCHED THEN
    UPDATE SET 
        o.parent_org_id = so.parent_org_id,
        o.org_name = NVL(so.org_short_name, so.org_name),  -- 优先用简称,无则用全名
        o.org_type = so.org_type,
        o.org_wbs_code = so.org_wbs_code,
        o.org_wbs_level = so.org_wbs_level,
        o.orderby = so.orderby,
        o.status = so.status,
        o.update_time = SYSDATE  -- 用当前时间更新,确保时效性
WHEN NOT MATCHED THEN
    INSERT (
        org_id, parent_org_id, org_name, org_type,
        org_wbs_code, org_wbs_level, orderby, status,
        remark, create_time, update_time, tenant_code
    ) VALUES (
        so.org_id, so.parent_org_id, NVL(so.org_short_name, so.org_name),
        so.org_type, so.org_wbs_code, so.org_wbs_level, so.orderby,
        so.status, so.remark, SYSDATE, SYSDATE, so.tenant_code
    );

COMMIT;  -- Oracle 11.2 需手动提交(非事务环境下)
  • 空值处理:使用 NVL(字段, 默认值) 函数,当源字段为空时取默认值,等价于其他数据库的 ISNULL/IFNULL
  • 原子性:MERGE 语句为单原子操作,更新和插入逻辑要么同时成功,要么同时回滚,避免数据不一致;
  • 版本兼容:Oracle 10g+ 已支持基础 MERGE 功能,11.2+ 无语法兼容问题;
  • 性能优化:建议为目标表 org_id 和源表 org_id 建立索引,减少匹配时的全表扫描。

SQL Server 2008 引入 MERGE 语句,语法与 Oracle 高度相似,同时增加了 OUTPUT 子句等特有功能,灵活性更强。

MERGE [ INTO ] 目标表 [ 目标别名 ]
USING 源表/子查询 [ 源别名 ]
ON (匹配条件)
[ WHEN MATCHED [ AND 额外条件 ] THEN UPDATE SET 字段=值 ]
[ WHEN NOT MATCHED [ AND 额外条件 ] THEN INSERT (字段列表) VALUES (值列表) ]
[ OUTPUT 输出字段 ];  -- MSSQL 独有,可返回操作结果
-- 声明租户编码参数(支持NULL取消过滤)
DECLARE @tenantCode VARCHAR(50) = 'TENANT001';

MERGE INTO sec_org o
USING (
    SELECT 
        org_id, parent_org_id, org_short_name, org_name,
        org_type, org_wbs_code, org_wbs_level, orderby,
        status, remark, update_time, tenant_code
    FROM sync_org
    WHERE is_sync = 1
      AND (@tenantCode IS NULL OR tenant_code = @tenantCode)  -- 动态租户过滤
) so
ON (o.org_id = so.org_id)  -- 唯一键匹配
WHEN MATCHED THEN
    UPDATE SET 
        o.parent_org_id = so.parent_org_id,
        o.org_name = ISNULL(so.org_short_name, so.org_name),  -- 替换Oracle的NVL函数
        o.org_type = so.org_type,
        o.org_wbs_code = so.org_wbs_code,
        o.org_wbs_level = so.org_wbs_level,
        o.orderby = so.orderby,
        o.status = so.status,
        o.update_time = GETDATE()  -- MSSQL获取当前时间
WHEN NOT MATCHED THEN
    INSERT (
        org_id, parent_org_id, org_name, org_type,
        org_wbs_code, org_wbs_level, orderby, status,
        remark, create_time, update_time, tenant_code
    )
    VALUES (
        so.org_id, so.parent_org_id, ISNULL(so.org_short_name, so.org_name),
        so.org_type, so.org_wbs_code, so.org_wbs_level, so.orderby,
        so.status, so.remark, GETDATE(), GETDATE(), so.tenant_code
    )
-- 输出操作结果(INSERT/UPDATE),便于业务校验
OUTPUT inserted.org_id, $action INTO #temp_result;

-- 查看同步结果($action值为'INSERT'或'UPDATE')
SELECT * FROM #temp_result;
DROP TABLE #temp_result;  -- 清理临时表
COMMIT;
  • 函数替换:Oracle 的 NVL 对应 MSSQL 的 ISNULL,功能完全一致;也支持跨库通用的 COALESCE 函数;
  • 动态条件:通过 @变量 接收参数,结合 OR 变量 IS NULL 实现 “可选过滤”,无需框架拼接 SQL;
  • 独有功能:OUTPUT 子句可返回插入 / 更新的记录及操作类型,方便后续业务处理(如日志记录、告警触发);
  • 版本兼容:SQL Server 2008+ 支持完整 MERGE 功能,2005 及以下版本需拆分为 UPDATE + INSERT 语句;
  • 锁机制:默认对目标表加行级锁,高并发场景需注意锁等待问题,可通过 WITH (NOLOCK) 优化(需评估业务风险)。

MySQL 无原生 MERGE 关键字,提供两种等效方案,其中 INSERT ... ON DUPLICATE KEY UPDATE 最贴合 “匹配更新、不匹配插入” 语义,是推荐方案。

INSERT INTO 目标表 (字段列表)
SELECT 源字段列表 FROM 源表 WHERE 过滤条件
ON DUPLICATE KEY UPDATE 字段1=值1, 字段2=值2, ...;

目标表 sec_org 的 org_id 字段必须是主键或唯一索引,否则 ON DUPLICATE KEY 不生效,会直接报主键冲突错误。

-- 同步组织数据,匹配则更新,不匹配则插入
INSERT INTO sec_org (
    org_id, parent_org_id, org_name, org_type,
    org_wbs_code, org_wbs_level, orderby, status,
    remark, create_time, update_time, tenant_code
)
SELECT 
    org_id, parent_org_id, IFNULL(org_short_name, org_name),  -- 替换Oracle的NVL
    org_type, org_wbs_code, org_wbs_level, orderby, status,
    remark, NOW(), NOW(), tenant_code  -- MySQL获取当前时间
FROM sync_org
WHERE is_sync = 1
  AND (tenant_code = #{tenantCode} OR #{tenantCode} IS NULL)  -- 动态条件(MyBatis适配)
ON DUPLICATE KEY UPDATE  -- 匹配org_id唯一键时执行更新
    parent_org_id = VALUES(parent_org_id),  -- VALUES(字段)引用插入的源值
    org_name = VALUES(org_name),
    org_type = VALUES(org_type),
    org_wbs_code = VALUES(org_wbs_code),
    org_wbs_level = VALUES(org_wbs_level),
    orderby = VALUES(orderby),
    status = VALUES(status),
    update_time = NOW();  -- 更新时间刷新为当前时间

COMMIT;  -- InnoDB引擎需手动提交
REPLACE INTO sec_org (
    org_id, parent_org_id, org_name, org_type,
    org_wbs_code, org_wbs_level, orderby, status,
    remark, create_time, update_time, tenant_code
)
SELECT 
    org_id, parent_org_id, IFNULL(org_short_name, org_name),
    org_type, org_wbs_code, org_wbs_level, orderby, status,
    remark, NOW(), NOW(), tenant_code
FROM sync_org
WHERE is_sync = 1
  AND (tenant_code = #{tenantCode} OR #{tenantCode} IS NULL);
  • 核心逻辑:匹配唯一键时先删除旧行,再插入新行,与 Oracle/MSSQL 的 “更新” 逻辑不同;
  • 数据丢失风险:若目标表存在未在插入列表中的字段(如 creator 创建人),旧行的该字段值会被置空;
  • 适用场景:仅适用于 “全字段覆盖” 场景,不推荐替代方案 1。
  • 空值处理:使用 IFNULL(字段, 默认值) 或跨库通用的 COALESCE 函数;
  • 唯一键依赖:两种方案均依赖 org_id 为主键 / 唯一索引,需提前确认表结构;
  • 版本兼容:INSERT ... ON DUPLICATE KEY UPDATE 从 MySQL 4.1 开始支持,无版本兼容风险;
  • 批量优化:可通过 LIMIT 限制同步行数(如 LIMIT 1000),避免一次性操作过多数据导致锁表。

image