SQL Server用MERGE实现多源去重入库,需ON匹配条件+唯一约束;MySQL用INSERT...ON DUPLICATE KEY UPDATE;PostgreSQL用INSERT...ON CONFLICT DO UPDATE;三者均须前置数据清洗与业务主键准确定义。

SQL Server 里用 MERGE 实现多源数据去重入库
MERGE 是最贴近“合并+去重+入库”需求的原生方案,不是先 DELETE 再 INSERT,也不是靠应用层兜底。它原子性地判断存在与否,避免并发写入时的重复或丢失。
常见错误是把 MERGE 当成单纯 Upsert:没加 WHEN NOT MATCHED BY SOURCE THEN DELETE 就乱用,结果旧数据越积越多;或者 WHERE 条件漏写,导致全表被误删。
- 必须显式指定
ON匹配条件,且该字段组合在目标表上要有唯一约束(如UNIQUE INDEX),否则MERGE可能报错The MERGE statement attempted to UPDATE or DELETE the same row more than once - 源数据建议走临时表(
#staging)或表变量(@staging),别直接SELECT FROM ...多层子查询——执行计划容易崩,且无法对源数据预去重 -
MATCHED分支里慎用UPDATE SET col = ISNULL(source.col, target.col):空值覆盖逻辑会破坏已有非空值,应明确用CASE WHEN source.col IS NOT NULL THEN source.col ELSE target.col END
MySQL 8.0+ 怎么做类似 MERGE 的操作
MySQL 没有 MERGE,但 INSERT ... ON DUPLICATE KEY UPDATE 能覆盖大部分场景。前提是目标表主键或唯一键能准确标识“同一行”——比如用业务单号 + 数据源标识联合唯一。
容易踩的坑是忽略 DUPLICATE KEY 的触发范围:它只响应 PRIMARY KEY 和 UNIQUE 约束冲突,普通索引无效;另外,如果 INSERT 本身因其他约束(如 NOT NULL、CHECK)失败,ON DUPLICATE KEY UPDATE 根本不执行。
- 批量插入时,
VALUES ROW(), ROW(), ...比循环单条快得多,但总行数别超max_allowed_packet,否则报错Packets larger than max_allowed_packet are not allowed - 更新字段若涉及表达式(如
counter = counter + 1),要确认是否真需要累加——多数去重场景只需覆盖,用col = VALUES(col)更安全 - 如果源数据含时间戳字段(如
updated_at),建议在ON DUPLICATE KEY UPDATE里加条件判断:updated_at = GREATEST(VALUES(updated_at), updated_at),避免旧数据覆盖新数据
PostgreSQL 用 INSERT ON CONFLICT 处理冲突
INSERT ... ON CONFLICT DO UPDATE 是 PostgreSQL 的标准解法,比 MySQL 更灵活:支持任意唯一约束(包括部分索引)、可写 WHERE 条件过滤更新动作。
典型翻车点是没理解 EXCLUDED 表别名——它代表“本次想插入但冲突了的那行数据”,不是源表别名;另一个是 ON CONFLICT 后漏写约束名,导致报错 there is no unique or exclusion constraint matching the ON CONFLICT specification。
- 如果目标表用
serial或IDENTITY主键,且希望冲突时保留原id,DO UPDATE里就别碰id字段;否则可能引发主键变更,下游外键断裂 - 多个字段需按优先级合并(如 A 源数据可信度高于 B),可在
DO UPDATE SET里用CASE WHEN EXCLUDED.source = 'A' THEN EXCLUDED.val ELSE target.val END - 不要在
ON CONFLICT里调用函数(如now())做复杂判断——它会在冲突路径里反复执行,可能造成时间戳异常或序列错乱
跨库/异构数据源合并前必须做的三件事
存储过程再稳,也救不了源头数据质量差。多源头合并最常崩在“看起来一样,其实不一样”的数据上:空格、大小写、时区、编码、NULL 和空字符串混用。
别指望 SQL 层全自动清洗——得在进 staging 表之前切一刀。
- 统一清洗字段:用
TRIM()、UPPER()、REPLACE(col, CHAR(160), ' ')(处理不间断空格)、CONVERT_TZ()或AT TIME ZONE对齐时区 - 显式转换 NULL:比如把所有空字符串转成
NULL(NULLIF(trim(col), '')),避免'' = ''和NULL = NULL语义不一致 - 加校验列:在 staging 表里存
source_id、raw_hash(如MD5(concat_ws('|', col1, col2, ...))),方便事后追查哪条记录被谁覆盖、是否误删
真正难的从来不是语法,而是确认“哪些字段组合才算同一笔业务数据”。这个定义一旦错,后面所有 MERGE、ON DUPLICATE KEY、ON CONFLICT 都只是高效地错下去。

















