在数据库日常查询操作中,重复数据是非常常见的问题,比如用户表中同一个用户因为多次注册产生重复记录,订单表中同一笔订单因为关联查询出现多条相同数据,这时候就需要用到SQL的去重能力,其中DISTINCT是最基础也最常用的去重关键字。

DISTINCT 基础用法
单字段去重
单字段去重是DISTINCT最基础的用法,只需要在查询字段前加上DISTINCT关键字,就可以对指定字段的所有重复值进行去重,返回该字段的唯一值集合。
例如用户表user中有多个相同city的记录,要获取所有不重复的城市列表,查询语句如下:
-- 查询用户表中所有不重复的城市 SELECT DISTINCT city FROM user;
多字段联合去重
当需要对多个字段的组合进行去重时,只需要在DISTINCT后跟上多个字段,数据库会对这些字段的组合值进行判断,只有当所有字段的值都相同时才会被判定为重复数据。
比如要获取用户表中不重复的省份和城市组合,查询语句如下:
-- 查询不重复的省份+城市组合 SELECT DISTINCT province, city FROM user;
实用去重技巧
结合聚合函数去重统计
很多时候我们需要统计去重后的数据量,这时候可以将DISTINCT和COUNT函数结合使用,直接得到去重后的记录数,不需要先查询再去重统计。
例如统计用户表中不重复的城市数量:
-- 统计不重复的城市数量 SELECT COUNT(DISTINCT city) AS unique_city_count FROM user;
子查询中去重后关联
在复杂的多表关联查询中,如果关联表存在重复数据,直接关联会导致结果重复,这时候可以先对关联表用DISTINCT去重,再作为子查询进行关联。
比如订单表order和商品表product关联,商品表同一个商品有多个分类记录,要获取每个订单对应的唯一商品信息:
-- 先对商品表去重再关联订单表
SELECT o.order_id, p.product_name
FROM `order` o
LEFT JOIN (
SELECT DISTINCT product_id, product_name FROM product
) p ON o.product_id = p.product_id;
结合窗口函数去重
如果需要保留去重后的完整记录,而不仅仅是去重字段,可以结合ROW_NUMBER窗口函数实现,通过给每个分组内的记录排序,取排序第一的记录实现去重。
例如要保留每个用户最新的一条注册记录:
-- 每个用户保留最新的一条注册记录
SELECT user_id, username, register_time
FROM (
SELECT
user_id,
username,
register_time,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY register_time DESC) AS rn
FROM user
) t
WHERE t.rn = 1;
不同去重方案对比
不同的去重方案适用场景不同,性能也有差异,以下是常见方案的对比:
| 去重方案 | 适用场景 | 性能特点 |
|---|---|---|
| DISTINCT 单/多字段去重 | 只需要去重后的字段值,不需要保留完整记录 | 逻辑简单,小数据量下性能较好,大数据量多字段去重时性能下降 |
| DISTINCT + 聚合函数 | 需要统计去重后的数量或聚合值 | 比先查询再去重统计更高效,减少数据传输 |
| 窗口函数去重 | 需要保留去重后的完整记录 | 大数据量下分区排序的性能开销较大,适合复杂去重逻辑 |
| GROUP BY 去重 | 去重同时需要对其他字段做聚合计算 | 和DISTINCT多字段去重逻辑类似,部分数据库下性能相当 |
注意事项
- DISTINCT 关键字会对所有查询字段的组合进行去重,不要误以为只对第一个字段去重
- 对NULL值进行去重时,所有NULL会被视为相同值,只会保留一个NULL记录
- 大数据量去重时,建议给去重字段加上索引,提升查询性能
- 如果只需要判断是否存在重复值,不需要返回具体数据,用EXISTS比DISTINCT更高效