SQL筛选(出)重复数据与SQL去重(查询)
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重复name
2、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 employees
GROUP BY name
HAVING COUNT(*) > 1;
模拟结果:
|
|
|
|---|---|
|
|
|
|
|
|
(2)筛选task表中task_id重复的数据
SELECT task_id, task_name, COUNT(*) AS 重复次数
FROM task
GROUP BY task_id, task_name
HAVING COUNT(*) > 1;
模拟结果:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
2、使用窗口函数(ROW_NUMBER())
可查询出所有重复记录(包括重复组内的每条数据),便于查看重复数据的具体内容。
(1)筛选employees表中name重复的所有记录
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY name ORDER BY emp_id) AS rn
FROM employees
) AS sub
WHERE name IN (
SELECT name
FROM employees
GROUP BY name
HAVING COUNT(*) > 1
);
模拟结果:
|
|
|
|
|
|
|
|
|
|---|---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(2)筛选task表中task_id重复的所有记录
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY task_id ORDER BY start_time) AS rn
FROM task
) AS sub
WHERE task_id IN (
SELECT task_id
FROM task
GROUP BY task_id
HAVING COUNT(*) > 1
);
模拟结果:
|
|
|
|
|
|
|
|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3、使用自连接
通过表与自身连接,找出指定字段值完全相同的重复记录,适合精准定位重复数据。
(1)筛选employees表中name和birth_date都重复的记录
SELECT t1.*
FROM employees t1
JOIN 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视为同一组)
模拟结果:
|
|
|
|
|
|
|
|
|---|---|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(2)筛选task表中task_id和task_name都重复的记录
SELECT t1.*
FROM task t1
JOIN task t2
ON t1.task_id = t2.task_id
AND t1.task_name = t2.task_name
AND t1.start_time < t2.start_time; -- 按开始时间区分重复项
模拟结果:
|
|
|
|
|
|
|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
SQL筛选重复数据方法小结
以上三种SQL筛选重复数据方法的适用场景:
-
GROUP BY + HAVING:快速统计重复值及次数,适合初步排查; -
窗口函数:查看重复组内的所有记录,便于分析重复数据的差异; -
自连接:精准定位完全重复的记录,适合需要详细对比重复项的场景。
我们根据实际需求选择合适的方法,可高效识别并处理表中的重复数据。
三、SQL去重查询(保留唯一数据)
SQL去重查询是从重复数据中筛选出唯一的记录,即:去除重复项后保留需要的结果。
1、使用DISTINCT关键字
直接返回指定列的唯一值(单列或多列组合)。
(1)单列去重(查询唯一部门)
SELECT DISTINCT department FROM employees;
模拟结果:
|
|
|---|
|
|
|
|
|
|
|
|
(2)多列去重(查询唯一姓名+职位组合)
SELECT DISTINCT name, position FROM employees;
模拟结果:
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2、使用GROUP BY子句
按指定列分组,默认返回每组的第一条记录(可结合聚合函数保留特定值)。
(1)单列去重(查询唯一姓名)
SELECT name FROM employees GROUP BY name;
模拟结果:
|
|
|---|
|
|
|
|
|
|
|
|
|
|
(2)多列去重(查询唯一姓名+部门+入职日期组合)
SELECT name, department, hire_date FROM employees
GROUP BY name, department, hire_date;
模拟结果:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(3)结合聚合函数(查询每个姓名的最小入职日期)
SELECT name, MIN(hire_date) AS first_hire_date FROM employees GROUP BY name;
模拟结果:
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
模拟结果:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4、使用子查询
(1)NOT EXISTS子查询(取重复姓名中emp_id最小的记录)
SELECT emp_id, name, position FROM employees e
WHERE NOT EXISTS (
SELECT 1 FROM employees WHERE name = e.name AND emp_id < e.emp_id
);
模拟结果:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(2)IN子查询(取重复姓名中emp_id最大的记录)
SELECT emp_id, name, salary FROM employees e
WHERE emp_id IN (SELECT MAX(emp_id) FROM employees GROUP BY name);
模拟结果:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 employees
UNION
SELECT name FROM salaries;
模拟结果:
|
|
|---|
|
|
|
|
|
|
|
|
|
|
|
|
SQL去重查询方法小结
以上SQL去重方法,实际使用时需根据场景选择:
-
简单去重优先用 DISTINCT或GROUP BY; -
需要保留特定行(如最新/最早记录)时用窗口函数或子查询; -
多表合并去重用 UNION。
总结
SQL筛选重复数据:关注“哪些数据重复了”,结果包含重复的记录或重复组信息。
SQL去重查询:关注“如何保留唯一数据”,结果是去除重复后的唯一记录。
两者操作方向相反,但我们要常结合使用(先筛选重复数据,再根据需求去重)。
版权保护: 本文由 绿茶加糖-郭保升 原创,转载请保留链接: https://www.guobaosheng.com/shujuku/406.html
- 上一篇:CTE、临时表和子查询,谁才是初恋?
- 下一篇:一次讲透事务、锁和性能的秘密!
