当前位置:首页 > 娱乐

sqlserver去重语句

昨天帮同事调一个报表查询,重语他写了句 SELECT * FROM Orders,重语结果出来一万多条,重语其中同一个客户下了三次同样的重语单,时间戳只差几秒。重语他问我怎么把这堆重复的重语干掉。其实 SQL Server 里去重,重语很多人第一反应就是重语 DISTINCT,但它只能整行比对,重语稍微有点差异就失效了。重语

先明确一点:DISTINCT 是重语对 SELECT 出来的所有列做联合去重。比如 SELECT DISTINCT CustomerID,重语 OrderDate FROM Orders,只有 CustomerID 和 OrderDate 都相同才会被合并。重语如果表里还有 OrderID 这种唯一列,重语那 DISTINCT 基本没用——每行都不同。重语

真正要按某个字段去重、保留其他列信息,得用窗口函数。ROW_NUMBER() 是常用手段。假设要按 CustomerID 去重,保留最新一条记录:

WITH Ranked AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY OrderDate DESC) AS rn FROM Orders ) SELECT * FROM Ranked WHERE rn = 1;

这段代码里 PARTITION BY 就是分组,ORDER BY 决定每组里谁排第一。rn=1 就是每组只留一条。我一般建议 ORDER BY 后面加个唯一列(比如 OrderID),否则时间戳一样时结果不稳定。

还有一种情况:只想看哪些客户有重复,不需要具体数据。那就用 GROUP BY + HAVING:

SELECT CustomerID, COUNT(*) AS cnt FROM Orders GROUP BY CustomerID HAVING COUNT(*) > 1;

这个查法很快,但拿不到重复行的其他字段。想同时看到重复行的完整信息,可以 JOIN 回原表:

SELECT o.* FROM Orders o JOIN ( SELECT CustomerID FROM Orders GROUP BY CustomerID HAVING COUNT(*) > 1 ) d ON o.CustomerID = d.CustomerID ORDER BY o.CustomerID, o.OrderDate;

注意这里没有去重,只是把重复客户的所有行都列出来。真要删掉多余的,得先确定保留规则。比如保留每个客户最早的一单:

WITH ToDelete AS ( SELECT *, ROW_NUMBER() OVER (PARTITION BY CustomerID ORDER BY OrderDate ASC) AS rn FROM Orders ) DELETE FROM ToDelete WHERE rn > 1;

这个写法在 SQL Server 里能直接跑,因为 CTE 可以作用在 DELETE 上。但生产环境我建议先 SELECT 确认一遍,别直接 DELETE。有一次我手快,没加 WHERE 条件,差点把整张表清空——幸好只是测试库。

另外,如果表里有完全重复的行(所有列值都一样),可以用 SELECT DISTINCT * INTO #temp FROM Orders 再导回去,或者用 GROUP BY 所有列。但后者写起来太啰嗦,列一多就头疼。

还有个偏门方法:UNION 自带去重。SELECT * FROM Orders UNION SELECT * FROM Orders 会返回去重后的结果,但性能通常不如 DISTINCT,而且只能用于查询,不能直接删数据。

实际工作中,去重语句怎么写取决于你要什么结果。只看不删,ROW_NUMBER() 最灵活;只找重复项,GROUP BY 最快;要删数据,CTE + ROW_NUMBER() 最直接。别迷信一种写法,看场景换着用。

分享到: