SQL Server多列分区插入:跳过唯一约束并取分组首行
解决方案:SQL Server 按唯一键分组取首行并插入目标表
针对你的需求——把source表中按Nam+Gender分组后,取Score最高的行插入dest表,同时避免触发唯一键冲突,这里给你提供几个替代之前两种尝试的方法:
方法1:用ROW_NUMBER()窗口函数精准筛选目标行
这种方法先通过窗口函数给每组(Nam, Gender)内的行按Score降序编号,只留每组的第一行(也就是Score最高的那条),再插入dest表,从根源上杜绝唯一键冲突的问题。
WITH RankedSource AS ( SELECT RollNo, Nam, Gender, Score, -- 按Nam和Gender分组,Score降序排,给每行分配序号 ROW_NUMBER() OVER (PARTITION BY Nam, Gender ORDER BY Score DESC, RollNo DESC) AS rn FROM source -- 如果不需要Score为NULL的行,就加上这个过滤条件 WHERE Score IS NOT NULL ) INSERT INTO dest (RollNo, Nam, Gender, Score) SELECT RollNo, Nam, Gender, Score FROM RankedSource WHERE rn = 1; -- 只取每组的第一行
小提示:
- 我这里额外加了
RollNo DESC作为第二排序条件,要是同一组里有相同Score的行,就取RollNo更大的那条,你可以根据实际需求调整这个规则哈。 - 要是允许Score为NULL的行参与筛选,直接删掉
WHERE Score IS NOT NULL就行,SQL Server里NULL会被当成比所有非NULL值都小,所以不会被选为第一行。
方法2:用MERGE语句处理已存在的唯一键
如果dest表已经有部分Nam+Gender的记录,需要跳过这些重复项,同时插入新的分组首行,MERGE语句就很合适:
WITH RankedSource AS ( SELECT RollNo, Nam, Gender, Score, ROW_NUMBER() OVER (PARTITION BY Nam, Gender ORDER BY Score DESC, RollNo DESC) AS rn FROM source ) MERGE INTO dest AS d USING RankedSource AS s ON d.Nam = s.Nam AND d.Gender = s.Gender WHEN NOT MATCHED THEN -- 只有dest里没有这个唯一键组合的时候才插入 INSERT (RollNo, Nam, Gender, Score) VALUES (s.RollNo, s.Nam, s.Gender, s.Score) WHERE s.rn = 1;
方法3:用TRY_CATCH块捕获并跳过插入错误
要是你需要保留原始插入逻辑(比如不确定会不会有漏网的重复数据),可以用TRY_CATCH块把插入语句包起来,遇到唯一键冲突就跳过该行:
DECLARE @RollNo INTEGER, @Nam VARCHAR(6), @Gender VARCHAR(1), @Score INTEGER; DECLARE source_cursor CURSOR FOR SELECT RollNo, Nam, Gender, Score FROM ( SELECT RollNo, Nam, Gender, Score, ROW_NUMBER() OVER (PARTITION BY Nam, Gender ORDER BY Score DESC, RollNo DESC) AS rn FROM source ) AS ranked WHERE rn = 1; OPEN source_cursor; FETCH NEXT FROM source_cursor INTO @RollNo, @Nam, @Gender, @Score; WHILE @@FETCH_STATUS = 0 BEGIN BEGIN TRY INSERT INTO dest (RollNo, Nam, Gender, Score) VALUES (@RollNo, @Nam, @Gender, @Score); END TRY BEGIN CATCH -- 捕获唯一键冲突的错误(错误号是2627),直接跳过该行 IF ERROR_NUMBER() = 2627 BEGIN PRINT '跳过重复的唯一键组合:Nam=' + @Nam + ', Gender=' + @Gender; END ELSE BEGIN -- 其他类型的错误就正常抛出来 THROW; END END CATCH FETCH NEXT FROM source_cursor INTO @RollNo, @Nam, @Gender, @Score; END CLOSE source_cursor; DEALLOCATE source_cursor;
说明:
- 这个方法用游标逐行处理数据,遇到唯一键冲突时会打印提示信息并跳过,其他错误则正常抛出,适合需要记录跳过情况的场景。
内容的提问来源于stack exchange,提问作者Harshit Mahajan
相关产品推荐
相关产品推荐

