Oracle 19c 中 MERGE INTO 实现 upsert 需严格遵循语法:ON 子句仅支持主键/唯一列等值匹配,不可含待更新字段;USING 必须为 SELECT 子查询(单行也需 FROM DUAL);UPDATE/INSERT 字段须与源列顺序、别名、类型严格对齐;NULL 键需用 NVL/COALESCE 标准化或确保 NOT NULL + UNIQUE 约束。

MERGE INTO 在 Oracle 19c 中完全支持 upsert(插入或更新),但必须严格满足语法约束,否则直接报错 ORA-38104 或 ORA-00932。
ON 子句只能用主键/唯一列做等值判断,不能含待更新字段
这是最常踩的坑。比如想根据 id 匹配、同时又按 status 是否为 'PENDING' 决定是否更新,写成 ON (t.id = s.id AND t.status = 'PENDING') 是非法的——Oracle 会报 ORA-38104: Columns referenced in the ON Clause cannot be updated。
正确做法是把业务过滤下推到 USING 子查询里:
USING (SELECT id, name, status FROM source WHERE status = 'PENDING') s-
ON (t.id = s.id)—— 这里只保留连接键 - 匹配后在
UPDATE SET中赋值,不参与匹配逻辑
源数据必须是 SELECT 子查询,单行也要用 DUAL
不能写 USING VALUES (1, 'abc'),Oracle 不支持这种语法。哪怕只插/更一行,也得套一层 SELECT ... FROM DUAL:
MERGE INTO target t USING (SELECT 1 AS id, 'abc' AS name FROM DUAL) s ON (t.id = s.id) WHEN MATCHED THEN UPDATE SET t.name = s.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);
常见错误是漏掉 FROM DUAL 或拼错表名,导致 ORA-00942: table or view does not exist。
UPDATE 和 INSERT 的字段必须与源查询列严格对齐
UPDATE SET 后面的字段列表、INSERT 的字段列表,都要和 USING 子查询返回的列顺序、别名、类型一致。例如:
- 源查了
UPPER(name) AS name,目标列是VARCHAR2(20),但UPPER()可能触发隐式长度计算,某些版本会报ORA-00932 - 源查了
NVL(status, 'ACTIVE') AS status,目标列是CHAR(10),而NVL返回的是VARCHAR2,类型不兼容 - INSERT 字段数与 VALUES 数量不等,或顺序错位,直接报错
NULL 值处理必须显式标准化
Oracle 对 NULL 比较敏感,ON (t.code = s.code) 在任一端为 NULL 时永远不匹配(因为 NULL = NULL 为 FALSE)。如果业务允许用 NULL 当作有效键,必须用 NVL 或 COALESCE 统一转换:
ON (NVL(t.code, '##NULL##') = NVL(s.code, '##NULL##'))
但更稳妥的做法是:确保连接键本身有 NOT NULL + UNIQUE 约束,避免 NULL 参与匹配。否则 MERGE 行为不可预测,容易漏更新或重复插入。
真正难的不是写出来,而是确认 ON 条件是否真的能精确区分“存在”和“不存在”——尤其当表没建主键、或业务键含 NULL 时,MERGE 会静默失效,而不是报错。


















