SQL“筛选重复数据”和“去重查询”是SQL中两个不同的操作,区别在目标不同:SQL“筛选重复数据”是找出重复的数据,SQL“去重查询”是保留唯一的数据。下面我们一起来详细拆解:
一、创建模拟数据表及插入模拟数据
为了涵盖所有的SQL去重方法,下面创建2张表并插入模拟数据:
1、employees表(员工信息表)
CREATE TABLE employees ( emp_id INT PRIMARY KEY, name VARCHAr(50), position VARCHAr(50), department VARCHAr(50), hire_date DATE, birth_date DATE, salary DECIMAL(10,2));-- 插入模拟数据(含重复项)INSERT INTO employees VALUES(1, '张三', '开发工程师', '技术部', '2020-01-15', '1990-05-20', 8000),(2, '李四', '产品经理', '产品部', '2019-03-20', '1988-11-05', 10000),(3, '张三', '高级开发', '技术部', '2021-05-10', '1990-05-20', 12000), -- 与1重复name(4, '王五', '测试工程师', '测试部', '2020-07-01', '1992-08-15', 7500),(5, '赵六', 'UI设计师', '设计部', '2018-10-25', '1993-03-30', 9000),(6, '李四', '高级产品经理', '产品部', '2022-01-05', '1988-11-05', 15000), -- 与2重复name(7, '孙七', '开发工程师', '技术部', '2020-01-15', '1991-07-12', 8500),(8, '张三', '架构师', '技术部', '2023-03-20', '1990-05-20', 18000); -- 与1、3重复name2、task表(任务信息表)
CREATE TABLE task ( task_id INT, task_name VARCHAr(100), start_time DATETIME, end_time DATETIME, status VARCHAr(20));-- 插入模拟数据(含重复项)INSERT INTO task VALUES(101, '用户登录模块开发', '2023-05-01 09:00', '2023-05-05 18:00', '已完成'),(101, '用户登录模块开发', '2023-05-01 10:30', '2023-05-06 12:00', '已取消'), -- 重复task_id(102, '订单支付接口', '2023-05-02 08:45', '2023-05-08 18:00', '进行中'),(103, '商品列表优化', '2023-05-03 09:15', '2023-05-10 18:00', '已完成'),(102, '订单支付接口', '2023-05-02 09:30', '2023-05-09 12:00', '已完成'), -- 重复task_id(104, '购物车逻辑开发', '2023-05-04 10:00', '2023-05-12 18:00', '进行中');二、SQL筛选重复数据(找出重复项)
SQL筛选重复数据是查询出表中存在重复的记录,通常用在识别哪些数据存在重复,以便后续处理(如:后续删除重复项)。
1、使用GROUP BY + HAVINg(统计重复次数,最常用)组合
用在查询指定字段的重复值及重复次数,适合快速识别哪些数据存在重复。
(1)筛选employees表中name重复的数据
SELECt name, COUNT(*) AS 重复次数FROM employeesGROUP BY nameHAVINg COUNT(*) > 1;模拟结果:
name | 重复次数 |
张三 | 3 |
李四 | 2 |
(2)筛选task表中task_id重复的数据
SELECt task_id, task_name, COUNT(*) AS 重复次数FROM taskGROUP BY task_id, task_nameHAVINg COUNT(*) > 1;模拟结果:
task_id | task_name | 重复次数 |
101 | 用户登录模块开发 | 2 |
102 | 订单支付接口 | 2 |
2、使用窗口函数(ROW_NUMBER())
可查询出所有重复记录(包括重复组内的每条数据),便于查看重复数据的具体内容。
(1)筛选employees表中name重复的所有记录
SELECt * FROM ( SELECt *, ROW_NUMBER() OVER (PARTITION BY name ORDER BY emp_id) AS rn FROM employees) AS subWHERe name IN ( SELECt name FROM employees GROUP BY name HAVINg COUNT(*) > 1);模拟结果:
emp_id | name | position | department | hire_date | birth_date | salary | rn |
1 | 张三 | 开发工程师 | 技术部 | 2020-01-15 | 1990-05-20 | 8000.00 | 1 |
3 | 张三 | 高级开发 | 技术部 | 2021-05-10 | 1990-05-20 | 12000.00 | 2 |
8 | 张三 | 架构师 | 技术部 | 2023-03-20 | 1990-05-20 | 18000.00 | 3 |
2 | 李四 | 产品经理 | 产品部 | 2019-03-20 | 1988-11-05 | 10000.00 | 1 |
6 | 李四 | 高级产品经理 | 产品部 | 2022-01-05 | 1988-11-05 | 15000.00 | 2 |
(2)筛选task表中task_id重复的所有记录
SELECt * FROM ( SELECt *, ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY start_time) AS rn FROM task) AS subWHERe task_id IN ( SELECt task_id FROM task GROUP BY task_id HAVINg COUNT(*) > 1);模拟结果:
task_id | task_name | start_time | end_time | status | rn |
101 | 用户登录模块开发 | 2023-05-01 09:00:00 | 2023-05-05 18:00:00 | 已完成 | 1 |
101 | 用户登录模块开发 | 2023-05-01 10:30:00 | 2023-05-06 12:00:00 | 已取消 | 2 |
102 | 订单支付接口 | 2023-05-02 08:45:00 | 2023-05-08 18:00:00 | 进行中 | 1 |
102 | 订单支付接口 | 2023-05-02 09:30:00 | 2023-05-09 12:00:00 | 已完成 | 2 |
3、使用自连接
通过表与自身连接,找出指定字段值完全相同的重复记录,适合精准定位重复数据。
(1)筛选employees表中name和birth_date都重复的记录
SELECt t1.*FROM employees t1JOIN employees t2 ON t1.name = t2.name AND t1.birth_date = t2.birth_date AND t1.emp_id < t2.emp_id; -- 避免重复匹配(如t1=1与t2=3、t1=3与t2=1视为同一组)模拟结果:
emp_id | name | position | department | hire_date | birth_date | salary |
1 | 张三 | 开发工程师 | 技术部 | 2020-01-15 | 1990-05-20 | 8000.00 |
1 | 张三 | 开发工程师 | 技术部 | 2020-01-15 | 1990-05-20 | 8000.00 |
3 | 张三 | 高级开发 | 技术部 | 2021-05-10 | 1990-05-20 | 12000.00 |
2 | 李四 | 产品经理 | 产品部 | 2019-03-20 | 1988-11-05 | 10000.00 |
(2)筛选task表中task_id和task_name都重复的记录
SELECt t1.*FROM task t1JOIN task t2 ON t1.task_id = t2.task_id AND t1.task_name = t2.task_name AND t1.start_time < t2.start_time; -- 按开始时间区分重复项模拟结果:
task_id | task_name | start_time | end_time | status |
101 | 用户登录模块开发 | 2023-05-01 09:00:00 | 2023-05-05 18:00:00 | 已完成 |
102 | 订单支付接口 | 2023-05-02 08:45:00 | 2023-05-08 18:00:00 | 进行中 |
SQL筛选重复数据方法小结
以上三种SQL筛选重复数据方法的适用场景:
- GROUP BY + HAVINg:快速统计重复值及次数,适合初步排查;
- 窗口函数:查看重复组内的所有记录,便于分析重复数据的差异;
- 自连接:精准定位完全重复的记录,适合需要详细对比重复项的场景。
我们根据实际需求选择合适的方法,可高效识别并处理表中的重复数据。
三、SQL去重查询(保留唯一数据)
SQL去重查询是从重复数据中筛选出唯一的记录,即:去除重复项后保留需要的结果。
1、使用DISTINCT关键字
直接返回指定列的唯一值(单列或多列组合)。
(1)单列去重(查询唯一部门)
SELECt DISTINCT department FROM employees;模拟结果:
department |
技术部 |
产品部 |
测试部 |
设计部 |
(2)多列去重(查询唯一姓名+职位组合)
SELECt DISTINCT name, position FROM employees;模拟结果:
name | position |
张三 | 开发工程师 |
李四 | 产品经理 |
张三 | 高级开发 |
王五 | 测试工程师 |
赵六 | UI设计师 |
李四 | 高级产品经理 |
孙七 | 开发工程师 |
张三 | 架构师 |
2、使用GROUP BY子句
按指定列分组,默认返回每组的第一条记录(可结合聚合函数保留特定值)。
(1)单列去重(查询唯一姓名)
SELECt name FROM employees GROUP BY name;模拟结果:
name |
张三 |
李四 |
王五 |
赵六 |
孙七 |
(2)多列去重(查询唯一姓名+部门+入职日期组合)
SELECt name, department, hire_date FROM employees GROUP BY name, department, hire_date;模拟结果:
name | department | hire_date |
张三 | 技术部 | 2020-01-15 |
李四 | 产品部 | 2019-03-20 |
张三 | 技术部 | 2021-05-10 |
王五 | 测试部 | 2020-07-01 |
赵六 | 设计部 | 2018-10-25 |
李四 | 产品部 | 2022-01-05 |
孙七 | 技术部 | 2020-01-15 |
张三 | 技术部 | 2023-03-20 |
(3)结合聚合函数(查询每个姓名的最小入职日期)
SELECt name, MIN(hire_date) AS first_hire_date FROM employees GROUP BY name;模拟结果:
name | first_hire_date |
张三 | 2020-01-15 |
李四 | 2019-03-20 |
王五 | 2020-07-01 |
赵六 | 2018-10-25 |
孙七 | 2020-01-15 |
3、使用窗口函数(ROW_NUMBER())
为重复组内的记录编号,保留编号为1的记录(即每组的第一条)。
按task_id分组,取每组中start_time最小的记录
SELECt task_id, task_name, start_time FROM ( SELECt task_id, task_name, start_time, ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY start_time) AS rn FROM task) AS tmp WHERe rn = 1;模拟结果:
task_id | task_name | start_time |
101 | 用户登录模块开发 | 2023-05-01 09:00:00 |
102 | 订单支付接口 | 2023-05-02 08:45:00 |
103 | 商品列表优化 | 2023-05-03 09:15:00 |
104 | 购物车逻辑开发 | 2023-05-04 10:00:00 |
4、使用子查询
(1)NOT EXISTS子查询(取重复姓名中emp_id最小的记录)
SELECt emp_id, name, position FROM employees eWHERe NOT EXISTS ( SELECt 1 FROM employees WHERe name = e.name AND emp_id < e.emp_id);模拟结果:
emp_id | name | position |
1 | 张三 | 开发工程师 |
2 | 李四 | 产品经理 |
4 | 王五 | 测试工程师 |
5 | 赵六 | UI设计师 |
7 | 孙七 | 开发工程师 |
(2)IN子查询(取重复姓名中emp_id最大的记录)
SELECt emp_id, name, salary FROM employees eWHERe emp_id IN (SELECt MAX(emp_id) FROM employees GROUP BY name);模拟结果:
emp_id | name | salary |
8 | 张三 | 18000.00 |
6 | 李四 | 15000.00 |
4 | 王五 | 7500.00 |
5 | 赵六 | 9000.00 |
7 | 孙七 | 8500.00 |
5、使用UNIOn操作符(合并两表姓名并去重)
合并多个查询结果时自动去重(UNIOn ALL不会去重)。
-- 先创建临时表salaries用于演示CREATE TABLE salaries (name VARCHAr(50), salary DECIMAL(10,2));INSERT INTO salaries VALUES('张三', 9000), ('周八', 12000), ('王五', 8000);-- 执行UNIOn去重SELECt name FROM employeesUNIOnSELECt name FROM salaries;模拟结果:
name |
张三 |
李四 |
王五 |
赵六 |
孙七 |
周八 |
SQL去重查询方法小结
以上SQL去重方法,实际使用时需根据场景选择:
- 简单去重优先用DISTINCT或GROUP BY;
- 需要保留特定行(如最新/最早记录)时用窗口函数或子查询;
- 多表合并去重用UNIOn。
总结
SQL筛选重复数据:关注“哪些数据重复了”,结果包含重复的记录或重复组信息。
SQL去重查询:关注“如何保留唯一数据”,结果是去除重复后的唯一记录。
两者操作方向相反,但我们要常结合使用(先筛选重复数据,再根据需求去重)。
