LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

SQL语句高效去重写法

admin
2025年7月31日 9:21 本文热度 88

今天咱聊聊SQL里那些去重的招儿。平时查数据总遇到重复值,统计结果跑偏、存了一堆没用的,头疼得很。我整理了6个常用的去重关键字和操作,结合实际场景给你掰扯明白,保准你看完就知道该用哪个!

一、DISTINCT:单表去重的"快刀"

这玩意儿最简单,直接把重复的行干掉。但记住一点:它是看所有选中列的组合,不是单看某一列。

举个例子,员工表employees里有这些数据:

id  name    department
1   Alice   HR
2   Bob     Engineering
3   Alice   HR  -- 这行和1行name+department重复
4   Charlie Marketing

DISTINCT查姓名:

SELECT DISTINCT name FROM employees;

结果就只剩Alice、Bob、Charlie,重复的Alice被合并了。

要是查name+department

SELECT DISTINCT name, department FROM employees;

因为第3行和第1行的name+department完全一样,所以只留一行。

优点:简单直接,一行代码搞定单表去重。
坑点:会隐式排序,数据量大了可能变慢;而且作用于所有选中的列,想只去重某一列但保留其他列?不行!

二、GROUP BY:带统计功能的"去重+计算器"

GROUP BYDISTINCT灵活,不光能去重,还能顺带做统计(比如计数、求和)。

还是用上面的员工表,想统计每个部门有多少人:

SELECT department, COUNT(*) AS 人数 
FROM employees 
GROUP BY department;

结果会是:

HR            2
Engineering   2
Marketing     1

这其实就是按department分组,每组只留一条(去重),再算每组的数量。

和DISTINCT的区别

  • DISTINCT只去重,不统计;GROUP BY能分组+计算,适合需要分析数据的场景。
  • 大数据量时GROUP BY性能可能更好(尤其加了索引的话),因为它能按分组字段有序处理。

三、UNION vs UNION ALL:多表合并时的"过滤器"

这俩是用来合并多个查询结果的,比如查两个部门的员工,再合并到一起。

先建两个表:

-- 人力资源部
hr_dept: id=1(Alice), id=2(David)
-- 工程部
eng_dept: id=2(David), id=3(Bob), id=1(Alice)

UNION合并:

SELECT name FROM hr_dept
UNION
SELECT name FROM eng_dept;

结果是Alice、David、Bob——重复的AliceDavid被自动去重了。

换成UNION ALL

SELECT name FROM hr_dept
UNION ALL
SELECT name FROM eng_dept;

结果会是Alice、David、David、Bob、Alice——原样保留所有重复项,不做去重。

用法口诀

  • 确定没重复,或者不需要去重?用UNION ALL,速度快(少了去重步骤)。
  • 怕有重复,必须去重?用UNION,但性能会差一点(要排序去重)。

四、EXCEPT / MINUS:找"差异"的利器

这俩是求差集:返回"第一个表有,第二个表没有"的记录,而且自动去重。

比如有两个表:

all_products(所有产品): id=1(Laptop), 2(Phone), 3(Tablet)
sold_products(已售产品): id=1(Laptop), 3(Tablet)

想查"没卖出去的产品",用EXCEPT(SQL Server/PostgreSQL):

SELECT * FROM all_products
EXCEPT
SELECT * FROM sold_products;

结果就一个id=2(Phone)

注意:Oracle用MINUS替代EXCEPT;MySQL不支持这俩,得用LEFT JOIN模拟:

-- MySQL替代方案
SELECT ap.* 
FROM all_products ap
LEFT JOIN sold_products sp ON ap.id = sp.id
WHERE sp.id IS NULL;  -- 只留没匹配上的

五、INTERSECT:找"交集"的工具

返回两个表都有的记录,自动去重。比如查"所有会员"和"活跃会员"的重叠部分:

all_members(所有会员): id=1(Tom), 2(Jerry), 3(Spike)
active_members(活跃会员): id=1(Tom), 3(Spike), 4(Tyke)

INTERSECT(Oracle/SQL Server支持):

SELECT * FROM all_members
INTERSECT
SELECT * FROM active_members;

结果是id=1(Tom)、3(Spike)——这俩在两个表都出现了。

MySQL同样不支持,用INNER JOIN模拟:

SELECT am.* 
FROM all_members am
INNER JOIN active_members ac ON am.id = ac.id;

六、窗口函数:复杂去重的"手术刀"

上面的方法对付简单场景够了,但遇到"保留每组最新一条"这种需求,就得用ROW_NUMBER()这类窗口函数了。

比如订单表orders,想按用户分组,只留每个用户最新的订单:

id  user_id  order_time
1   100      2023-01-01
2   100      2023-01-05  -- 这个用户的最新订单
3   200      2023-01-03

ROW_NUMBER()给每组排序,再取第一条:

SELECT * FROM (
  SELECT *,
    ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESCAS rn
  FROM orders
) t 
WHERE rn = 1;  -- 只留每组排序第一的(最新的)

结果会留下id=2(user_id=100的最新订单)和id=3(user_id=200的唯一订单)。

不同数据库的"脾气"得注意

有些方法不是所有数据库都支持,比如:

  • MySQL没有EXCEPTINTERSECT,得用JOIN模拟;
  • Oracle用MINUS代替EXCEPT
  • ROW_NUMBER()在MySQL 8.0+才支持,老版本不行。

场景速查表:啥时候用啥招?

场景
推荐方法
例子片段
单表去重看结果
DISTINCT
SELECT DISTINCT dept FROM emp
单表去重+统计(如计数)
GROUP BY
SELECT dept, COUNT(*) FROM emp GROUP BY dept
多表合并后去重
UNION
SELECT name FROM A UNION SELECT name FROM B
多表合并不需要去重
UNION ALL
同上,把UNION换成UNION ALL
找A有但B没有的数据
EXCEPT/MINUS(或模拟)
SELECT * FROM A EXCEPT SELECT * FROM B
找A和B都有的数据
INTERSECT(或模拟)
SELECT * FROM A INTERSECT SELECT * FROM B
保留每组最新/最早的记录
ROW_NUMBER()
PARTITION BY user_id ORDER BY time DESC

最后说句大实话

去重不是越复杂越好,得看数据量和需求:

  • 小数据量、简单去重:DISTINCT足够;
  • 要统计分析:GROUP BY更合适;
  • 多表合并:优先UNION ALL(快),需要去重再用UNION
  • 复杂逻辑(如保留最新记录):窗口函数ROW_NUMBER()是王道。

记住这些,下次遇到重复数据,你就知道该拔刀还是用手术刀了!


该文章在 2025/7/31 9:21:50 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved