SQL去重查询是数据库操作中非常常见的需求,当表中存在重复记录时,我们需要通过特定的语法将重复数据剔除,得到唯一的结果集,同时还需要考虑查询的性能,避免在处理大量数据时产生过高的资源消耗。

常见的SQL去重实现方式
1. 使用distinct关键字
distinct是最基础也最常用的去重方式,它可以对查询结果中的指定字段进行去重,返回所有字段组合唯一的记录。需要注意的是,distinct会对后面跟随的所有字段组合进行去重,而不是单独对某一个字段去重。
示例:查询用户表中所有不重复的城市
-- 查询用户表中所有不重复的城市 SELECT DISTINCT city FROM user_info;
如果要同时对多个字段去重,比如查询不重复的城市和年龄段组合:
-- 查询不重复的城市和年龄段组合 SELECT DISTINCT city, age_group FROM user_info;
2. 使用group by子句
group by子句本身是用于分组的,结合聚合函数使用可以完成去重需求,它的去重逻辑是将相同字段值的记录分为一组,每组只返回一条记录。这种方式还可以配合聚合函数获取分组后的其他字段信息。
示例:查询每个城市的最早注册用户时间,同时实现城市去重
-- 按城市分组,获取每个城市的最早注册时间 SELECT city, MIN(register_time) AS first_register_time FROM user_info GROUP BY city;
3. 使用窗口函数
窗口函数适合需要保留重复数据中某一条特定记录的场景,比如保留每个用户最新的订单记录,剔除其他旧订单。常用的窗口函数是row_number(),它会为每一组内的记录生成序号,我们可以通过筛选序号为1的记录实现去重。
示例:查询每个用户的最新一条订单记录
-- 按用户ID分区,按订单时间倒序排序,取每个分区的第一条记录
SELECT * FROM (
SELECT
order_id,
user_id,
order_time,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) AS rn
FROM order_info
) t WHERE t.rn = 1;
SQL去重查询的优化思路
1. 合理使用索引
对去重涉及的字段建立合适的索引,可以大幅提升查询效率。比如使用distinct对city字段去重时,给city字段建立普通索引,数据库可以直接通过索引获取不重复的值,避免全表扫描。如果是group by场景,建立包含分组字段和查询字段的联合索引,可以实现索引覆盖查询,减少回表操作。
2. 减少查询的字段数量
去重查询时尽量只查询需要的字段,不要使用SELECT *。查询的字段越多,数据库需要处理的数据量越大,尤其是在使用distinct时,需要对所有查询字段的组合进行去重判断,字段过多会显著增加计算开销。
3. 避免不必要的嵌套查询
多层嵌套的子查询会增加数据库的执行成本,尽量将去重逻辑简化。比如上面的窗口函数去重示例,如果不需要其他复杂逻辑,可以直接用group by实现的话,优先选择group by,减少嵌套层级。
4. 根据数据量选择合适的去重方式
如果数据量较小,三种去重方式的性能差异不明显,可以根据使用习惯选择。如果数据量较大,优先选择group by或者窗口函数配合索引的方式,distinct在大数据量下对全字段组合去重的开销会比较高。如果是只需要对单个字段去重,且不需要其他字段信息,distinct是更简洁的选择。
不同去重方式的适用场景对比
我们可以通过下面的表格快速判断不同场景该选择哪种去重方式:
| 去重方式 | 适用场景 | 优势 | 劣势 |
|---|---|---|---|
| distinct | 简单单字段或多字段组合去重,不需要其他聚合信息 | 语法简单,易理解 | 只能返回去重字段,无法获取其他字段信息,大数据量下开销较高 |
| group by | 去重同时需要获取其他字段的聚合信息,比如最大值、最小值 | 功能灵活,可配合聚合函数使用 | 语法相对复杂,需要对分组逻辑有清晰理解 |
| 窗口函数 | 需要保留重复组中某一条特定记录,比如最新、最早记录 | 可以保留完整记录,排序逻辑灵活 | 部分低版本数据库不支持,大数据量下需要配合索引优化 |
注意:在编写去重查询时,要先明确自己的需求,是需要完全剔除重复记录,还是保留重复组中的某一条,再选择对应的实现方式,避免得到不符合预期的结果。