在日常开发中,数据表里经常会出现重复记录。比如用户注册信息不小心被提交了两次,或者订单系统因为网络抖动生成了重复流水。这时候,怎么从数据库里把干净的数据捞出来,就成了关键问题。
DISTINCT:最直接的去重方式
当你只想查出不重复的值时,DISTINCT 是最常用的关键词。比如一张用户登录日志表,同一个用户可能多次登录,你想知道有多少不同的用户登录过:
SELECT DISTINCT user_id FROM login_log;
这样就能拿到唯一的用户ID列表。如果要按多个字段组合去重,比如查出不同用户在不同日期的登录记录:
SELECT DISTINCT user_id, DATE(login_time) FROM login_log;
GROUP BY:更灵活的去重控制
当需要在去重的同时做一些统计,比如统计每个用户的登录次数,GROUP BY 就比 DISTINCT 更合适:
SELECT user_id, COUNT(*) AS login_count FROM login_log GROUP BY user_id;
它不仅能去重,还能附带聚合信息。有时候你发现某个订单表里有完全相同的行,可以用 GROUP BY 配合 HAVING 找出重复次数大于1的记录:
SELECT order_no, COUNT(*) as cnt FROM orders GROUP BY order_no HAVING cnt > 1;
处理部分字段重复的情况
实际业务中,很少整行都一样。常见的是关键字段重复,但时间戳或状态不同。比如同一笔订单被插入了两条,只有创建时间不一样。这时候可以用窗口函数 ROW_NUMBER() 来标记重复项:
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY order_no ORDER BY create_time DESC) as rn
FROM orders
) t WHERE rn = 1;
这段语句的意思是:按订单号分组,每组内按创建时间倒序排,取第一条。这样就保留了最新的那条记录,其他重复的就被过滤掉了。
去重也要考虑性能
数据量一大,DISTINCT 和 GROUP BY 都可能变慢。尤其是对大文本字段去重,数据库得做大量比对。这时候可以考虑加索引,比如在 user_id 或 order_no 上建索引,能明显提升去重速度。
另外,如果只是想检查是否存在重复,没必要查出所有数据,用下面这种写法更轻量:
SELECT order_no FROM orders GROUP BY order_no HAVING COUNT(*) > 1 LIMIT 10;
只看前10个重复的订单号,快速定位问题。
避免重复从源头做起
与其事后去重,不如一开始就防止重复插入。比如在数据库层面设置唯一索引:
ALTER TABLE orders ADD UNIQUE INDEX uk_order_no (order_no);
这样再插入相同订单号时,数据库会直接报错,强制程序处理异常,避免脏数据入库。
去重不是一次性任务,而是贯穿数据生命周期的操作。掌握几种常用写法,结合业务场景灵活使用,才能让查询又准又快。