在SQL复杂查询的实际使用中,多表关联、子查询、聚合计算等场景很容易产生重复记录,这些重复数据会影响统计结果的准确性,甚至导致业务逻辑出错,因此需要掌握对应的处理方法。

重复记录产生的常见原因
复杂查询中出现重复记录通常有以下几种情况:
- 多表关联时,关联条件设计不合理,导致一对多关系下主表记录被重复匹配
- 子查询返回的结果集本身存在重复数据,外层查询未做去重处理
- 使用UNION合并多个结果集时,不同子集存在相同记录
- 聚合查询中未正确分组,导致相同分组下的记录被重复展示
基础去重方法
使用DISTINCT关键字
DISTINCT是最基础的去重方式,会对查询结果的所有列组合进行去重,只要所有列的值完全相同才会被判定为重复记录。
示例:查询所有不重复的用户所在城市,关联用户表和订单表做复杂查询:
SELECT DISTINCT u.city FROM user u INNER JOIN order o ON u.id = o.user_id WHERE o.amount > 100
注意DISTINCT会对所有选中列生效,如果只需要对部分列去重,这种方式就不适用。
使用GROUP BY子句
GROUP BY可以按照指定列分组,配合聚合函数实现去重,还能同时完成统计计算,适合需要分组统计的复杂查询场景。
示例:查询每个用户的首次下单时间,同时去重重复的用户记录:
SELECT u.id, u.name, MIN(o.create_time) AS first_order_time FROM user u INNER JOIN order o ON u.id = o.user_id GROUP BY u.id, u.name
复杂场景下的去重方案
窗口函数去重
当需要在保留完整记录的前提下去重,或者需要按规则保留重复记录中的某一条时,窗口函数是更灵活的选择,常用的有ROW_NUMBER()、RANK()、DENSE_RANK()。
示例:查询每个用户金额最高的订单记录,去除同一个用户的多条订单重复数据:
SELECT *
FROM (
SELECT
o.id,
o.user_id,
o.amount,
o.create_time,
ROW_NUMBER() OVER (PARTITION BY o.user_id ORDER BY o.amount DESC) AS rn
FROM order o
INNER JOIN user u ON o.user_id = u.id
WHERE u.status = 1
) t
WHERE t.rn = 1
这里PARTITION BY按照用户ID分组,ORDER BY按照订单金额降序排序,ROW_NUMBER()会给每个分组内的记录生成唯一序号,取序号为1的就是每个用户金额最高的订单。
子查询结合EXISTS去重
对于需要关联多张表且去重逻辑较复杂的场景,可以使用EXISTS子查询过滤重复记录,这种方式性能通常优于多层嵌套的GROUP BY。
示例:查询有下单记录且不重复的用户信息,排除重复的用户数据:
SELECT DISTINCT u.*
FROM user u
WHERE EXISTS (
SELECT 1
FROM order o
WHERE o.user_id = u.id
AND o.status = 2
)
不同去重方案的选择建议
| 去重方案 | 适用场景 | 优势 | 不足 |
|---|---|---|---|
| DISTINCT | 简单查询,需要对所有选中列去重 | 语法简单,易于理解 | 无法做分组统计,对部分列去重不灵活 |
| GROUP BY | 需要分组统计的复杂查询 | 可同时完成去重和聚合计算 | 只能展示分组列和聚合结果,无法保留完整原始记录 |
| 窗口函数 | 需要保留完整记录,按规则筛选重复记录 | 灵活度高,可自定义去重规则 | 部分低版本数据库不支持 |
| EXISTS子查询 | 多表关联复杂去重场景 | 性能较好,逻辑清晰 | 语法相对复杂,需要编写子查询 |
注意事项
在处理重复记录时,首先要明确重复的定义,是全部列相同才算重复,还是部分关键列相同就算重复,避免误删有效数据。另外,去重操作会增加查询的计算开销,对于数据量极大的表,建议先通过索引优化查询效率,再选择合适的去重方案。