postgresql 用 insert ... on conflict 替代 merge,仅支持冲突时更新,不支持三路逻辑;sql server merge 易因非唯一 on 条件引发重复更新错误;oracle merge 支持 delete 但需严格保证源数据稳定性。

PostgreSQL 没有 MERGE,得用 INSERT ... ON CONFLICT 替代
PostgreSQL 从 9.5 开始支持 INSERT ... ON CONFLICT,它能覆盖大部分 MERGE 场景,但不是语法等价。核心区别在于:它只处理“插入冲突时更新”,不支持“匹配不到就插入、匹配到就更新、还额外支持不匹配就删除”这种三路逻辑。
常见错误是硬套 SQL Server 的 MERGE 结构,比如写 MERGE INTO ... USING ... ON ... WHEN MATCHED THEN UPDATE ... WHEN NOT MATCHED THEN INSERT ...,直接报错 syntax error at or near "MERGE"。
- 只在主键或唯一约束冲突时触发
ON CONFLICT,所以目标表必须有明确的冲突判定依据(比如ON CONFLICT (id)或ON CONFLICT ON CONSTRAINT uk_name) -
ON CONFLICT DO UPDATE SET col = EXCLUDED.col中的EXCLUDED是 PostgreSQL 特有关键字,代表本次想插入但被拦下的那行数据,别写成VALUES()或NEW - 不能在
DO UPDATE里引用源表别名(因为没USING子句),所有右值得来自EXCLUDED或子查询
INSERT INTO users (id, name, email) VALUES (123, 'Alice', 'a@example.com') ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, email = EXCLUDED.email;
SQL Server 的 MERGE 要防“重复更新”和事务隔离问题
SQL Server 的 MERGE 看似简洁,但实际运行中容易因并发或谓词设计不当,导致同一行被多次匹配、触发多次 UPDATE,甚至抛出 The MERGE statement attempted to UPDATE or DELETE the same row more than once 错误。
根本原因是:SQL Server 在执行 MERGE 前会先做匹配扫描,若 ON 条件不唯一(比如只靠非唯一字段匹配),就可能把源行映射到多个目标行上。
-
ON子句必须基于目标表的唯一键或主键,否则运行时报错;如果业务上确实无法保证唯一性,得先用 CTE 或临时表预聚合源数据 -
MERGE是单语句,但内部隐含读+写两阶段,受事务隔离级别影响明显;在READ COMMITTED下可能漏匹配刚提交的新行,建议显式加WITH (HOLDLOCK)提升锁粒度 - 不要在
WHEN MATCHED THEN UPDATE里调用非确定性函数(如GETDATE()多次出现),结果不可预期
MERGE users WITH (HOLDLOCK) AS tgt USING (SELECT 123 AS id, 'Alice' AS name) AS src ON tgt.id = src.id WHEN MATCHED THEN UPDATE SET name = src.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (src.id, src.name);
Oracle 的 MERGE 支持 DELETE,但条件写法和触发时机很关键
Oracle 的 MERGE 是三者中最接近标准的,支持 WHEN MATCHED THEN UPDATE ... DELETE WHERE,但它删的是“本次匹配成功、且满足 DELETE 条件”的行,不是独立的删除操作。很多人误以为它能清理历史冗余数据,结果发现删不掉。
典型现象是:DELETE WHERE 条件始终不生效,或者整条 MERGE 报 ORA-30926: unable to get a stable set of rows in the source tables —— 这说明源数据对目标键存在一对多关系,Oracle 拒绝执行以保一致性。
-
ON子句里的字段必须是目标表的唯一键,否则报ORA-30926;如果源表本身有重复,得先用GROUP BY或ROW_NUMBER()去重 -
DELETE WHERE只在WHEN MATCHED分支内有效,且只作用于当前匹配上的那一行,不能跨行删;想删其他行,得另起DELETE语句 - Oracle 12c+ 支持
MERGE ... RETURNING,但只返回被插入/更新/删除的行数,不返回具体值,调试时别指望它回传 ID
MERGE INTO users tgt USING (SELECT 123 id, 'Alice' name FROM dual) src ON (tgt.id = src.id) WHEN MATCHED THEN UPDATE SET name = src.name DELETE WHERE src.name IS NULL WHEN NOT MATCHED THEN INSERT (id, name) VALUES (src.id, src.name);
跨数据库写“类 MERGE”逻辑时,别依赖语法糖,优先拆成原子操作
想写一次 SQL 跑三个库?基本不可行。各厂对 MERGE 的实现差异不只是关键字不同,而是语义分层、锁行为、错误策略全都不一样。强行抽象一个“通用 MERGE 函数”,最后往往变成一堆 if-else + 数据库类型判断,维护成本远高于直接写三段清晰逻辑。
真正省事的做法,是接受差异,按需选择最稳的路径:对小数据量、低并发场景,用 SELECT + INSERT/UPDATE 两步走;对大数据量,PostgreSQL 用 ON CONFLICT,SQL Server 用带 HOLDLOCK 的 MERGE,Oracle 用原生 MERGE —— 各自发挥所长。
- 别为了“看起来统一”在 PostgreSQL 里模拟
MERGE的三路分支,用INSERT ... ON CONFLICT+ 单独DELETE更直观可靠 - SQL Server 的
MERGE在大表上性能未必比UPDATE+INSERT好,尤其当ON字段没索引时,执行计划容易走嵌套循环+全表扫描 - Oracle 的
MERGE在并行 DML 模式下才真正高效,但需要单独开启ALTER SESSION ENABLE PARALLEL DML,默认不生效
兼容性不是拼语法,是看住数据边界、锁范围和错误反馈点。这三个地方对齐了,比死磕一个“通用语句”实在得多。










