执行INSERT INTO/SELECT或MERGE时主键重复错误的排查与解决
主键重复错误原因及解决方法
问题原因
目标表DESTINATION_TABLE的复合主键是DocNumber+TenantId,且执行插入时表为空,所以冲突根本不是来自目标表——是你的SOURCE_TABLE本身存在多组相同的(DocNumber, TenantId)记录。
因为SOURCE_TABLE的主键是SourceTableId(单字段唯一标识),这个主键只保证每条记录的SourceTableId不重复,但完全允许DocNumber和TenantId的组合重复。当你批量插入时,这些重复的组合就会触发目标表的主键约束,导致报错。
你可以用下面的SQL验证这一点,它会列出源表中所有(DocNumber, TenantId)重复的记录:
SELECT DocNumber, TenantId, COUNT(*) AS 重复记录数 FROM dbo.SOURCE_TABLE WHERE DeletedById IS NULL GROUP BY DocNumber, TenantId HAVING COUNT(*) > 1;
执行后,报错信息里的(DOC-99, some-tenant)肯定会出现在结果里。
解决方法
根据你的业务需求,选以下一种方式处理:
1. 插入时自动去重,只保留每组重复数据的一条
比如保留每组重复记录中SourceTableId最大的那条(也就是最新插入的记录),用窗口函数实现:
INSERT INTO dbo.DESTINATION_TABLE ( DocNumber, TenantId, UserId, OtherField ) SELECT po.DocNumber, po.TenantId, po.CreatedById, po.OtherField FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY DocNumber, TenantId ORDER BY SourceTableId DESC) AS 行号 FROM dbo.SOURCE_TABLE WHERE DeletedById IS NULL ) po WHERE po.行号 = 1;
如果业务需要保留最早的记录,把ORDER BY SourceTableId DESC改成ASC即可。
2. 先清理源表的重复数据
如果业务上不允许源表存在这类重复记录,可以先删除多余的重复项:
WITH 重复记录 AS ( SELECT SourceTableId, ROW_NUMBER() OVER (PARTITION BY DocNumber, TenantId ORDER BY SourceTableId DESC) AS 行号 FROM dbo.SOURCE_TABLE WHERE DeletedById IS NULL ) DELETE FROM 重复记录 WHERE 行号 > 1;
清理完成后,再用你原来的INSERT或MERGE语句插入数据就不会报错了。
3. 修改目标表约束(不推荐)
除非你确认业务上不需要(DocNumber, TenantId)的唯一性,否则不建议这么做——直接去掉目标表的复合主键,或者换成非唯一约束。但这通常会引发后续的业务逻辑问题,谨慎选择。
内容的提问来源于stack exchange,提问作者sheamus
相关产品推荐
相关产品推荐

