sql查询去掉重复的值

SELECT DISTINCT column_name FROM table_name; 这行代码,询去是掉重很多SQL初学者的第一道坎。我见过太多人卡在这里——明明数据里就是询去有重复,怎么去掉之后反而少了?掉重或者,怎么去了半天,询去重复的掉重还在? 先讲个真事。上周帮朋友排查一个报表问题,询去他写的掉重是: ```sql SELECT name, email FROM users WHERE signup_date > '2024-01-01'; ``` 结果名单里同一封邮件出现了三次。他问我:“我加了WHERE条件啊,询去为什么还有重复?掉重”我说,WHERE管的询去是行筛选,不管去重。掉重你要去重,询去得用DISTINCT或者GROUP BY。掉重 **去重这件事,询去本质上分两种场景。** 一种是“整行完全重复”,另一种是“某一列重复,但其他列不同”。前者好办,一个DISTINCT全搞定。后者就麻烦了,你得想清楚到底要保留哪一行。 拿刚才那个例子说,如果只需要唯一的email列表: ```sql SELECT DISTINCT email FROM users WHERE signup_date > '2024-01-01'; ``` 这样出来的就是干净的邮件清单。但问题来了——如果你还想要对应的用户ID呢?DISTINCT会把所有选中的列作为一个整体判断,如果ID不同,那email即使重复也照样显示。这时候DISTINCT就失灵了。 我见过最典型的错误写法是这样的: ```sql SELECT DISTINCT email, name FROM users; ``` 写的人以为“我只要email不重复”,但实际效果是“email+name的组合不重复”。除非你确保name在相同email下永远一致,否则结果里照样有重复email。这个坑,踩过的人应该都有印象。 **那如果要去掉某一列的重复,同时保留其他字段,该怎么办?** 常规做法是用窗口函数ROW_NUMBER()。假设要取每个用户最新的登录记录: ```sql SELECT user_id, login_time, device FROM ( SELECT user_id, login_time, device, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_time DESC) AS rn FROM login_log ) t WHERE rn = 1; ``` PARTITION BY user_id的意思是“按用户分组”,每组内部按登录时间倒序排,然后只取每组的第一行。这个写法很灵活,能精确控制保留哪条记录。 另一种思路是用GROUP BY加聚合函数。比如想知道每个用户登录过几种设备: ```sql SELECT user_id, COUNT(DISTINCT device) AS device_count FROM login_log GROUP BY user_id; ``` 这里COUNT(DISTINCT device)是“统计每个用户使用过的不同设备数”,跟SELECT DISTINCT完全是两码事。很多人混淆这两者,其实一个负责“显示时去重”,一个负责“计算时去重”。 **最后说个容易忽略的点。** DISTINCT不止能用在SELECT后面,还能用在聚合函数里,甚至配合CASE WHEN用。比如统计每天有多少个不同的付费用户: ```sql SELECT DATE(pay_time) AS day, COUNT(DISTINCT user_id) AS pay_users FROM orders GROUP BY DATE(pay_time); ``` 这个写法在电商后台很常用。但要注意,如果数据量上了百万级,DISTINCT的性能并不算好,尤其在大表上做多列DISTINCT,有时候会慢得让人怀疑人生。这时候可以考虑用GROUP BY替代,或者先对子查询去重再关联,效率会好很多。 SQL的去重逻辑,说白了就是搞清楚“重复”的定义维度。是行级别,还是字段级别?是保留任意一条,还是保留最新一条?想清楚这三个问题,写出来的查询基本不会跑偏。别一上去就无脑DISTINCT,先看看你的业务到底需要哪种“唯一性”。