MySQL用ON DUPLICATE KEY UPDATE、PostgreSQL用ON CONFLICT DO UPDATE、SQL Server用MERGE实现UPSERT,三者语法互不兼容;MySQL依赖PRIMARY KEY或UNIQUE索引触发更新,PostgreSQL需显式指定冲突字段或约束名并用EXCLUDED引用新值,SQL Server MERGE须严格按USING-ON-WHEN结构编写,且NULL在唯一约束中不触发冲突。

MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE,PostgreSQL 用 INSERT ... ON CONFLICT DO UPDATE,SQL Server 用 MERGE;三者语法差异大,不能混用,选错会直接报错。
MySQL:靠唯一索引触发 ON DUPLICATE KEY UPDATE
这个语句不是“查一遍再决定”,而是靠插入时违反唯一约束(PRIMARY KEY 或 UNIQUE 索引)来触发更新。没建对应索引,它就永远只插入,不会更新。
- 必须确保表中存在
PRIMARY KEY或UNIQUE索引,且INSERT的值会命中该索引冲突 -
VALUES后的字段顺序要和UPDATE中的col = VALUES(col)严格对应,否则可能更新错列 - 注意
VALUES(col)是指本次INSERT语句中该列的值,不是表里原来的值
INSERT INTO users (id, name, score) VALUES (123, 'Alice', 85) ON DUPLICATE KEY UPDATE name = VALUES(name), score = score + VALUES(score);
PostgreSQL:依赖 ON CONFLICT 指定冲突目标
PostgreSQL 要求显式写出冲突依据——通常是 ON CONFLICT (id) 或 ON CONFLICT ON CONSTRAINT users_pkey。漏写或写错约束名,语句会报 there is no unique or exclusion constraint。
- 不能只写
ON CONFLICT,必须跟括号或约束名 -
DO UPDATE SET后不能引用VALUES(),得用EXCLUDED.col表示本次想插入但被拦截的值 - 如果想跳过更新(即“存在就不管”),用
DO NOTHING,不是省略整个DO UPDATE
INSERT INTO users (id, name, score) VALUES (123, 'Alice', 85) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, score = users.score + EXCLUDED.score;
SQL Server:MERGE 语句结构固定,条件写在 WHEN MATCHED 和 WHEN NOT MATCHED
MERGE 不是单条语句,而是一个块,必须配对 USING、ON、WHEN MATCHED、WHEN NOT MATCHED。少一个关键字或顺序错,直接语法错误。
-
USING后不能直接写值,得包装成子查询,比如(VALUES (123, 'Alice', 85)) AS new(id, name, score) -
ON条件必须是等值匹配,不支持>或LIKE - 每条
WHEN分支后必须接THEN,且UPDATE或INSERT的列名和值要与USING子句别名一致
MERGE users AS t USING (VALUES (123, 'Alice', 85)) AS s(id, name, score) ON t.id = s.id WHEN MATCHED THEN UPDATE SET name = s.name, score = t.score + s.score WHEN NOT MATCHED THEN INSERT (id, name, score) VALUES (s.id, s.name, s.score);
跨数据库移植时最容易忽略的一点
各数据库对“空值是否算重复”的处理不一致:NULL != NULL 在唯一索引中通常不触发冲突,所以 ON DUPLICATE KEY 或 ON CONFLICT 对含 NULL 的唯一组合可能完全失效。上线前务必用真实含 NULL 的数据测一遍逻辑是否符合预期。

















