数据重复是数据库常见问题,易导致统计失真、存储冗余。下面我们系统了解 6 个 SQL 去重关键字或操作符,并系统梳理 SQL 去重方法,即从基础的 DISTINCT 到复杂的窗口函数,结合多数据库特性,让我们能从中精准选择去重方案。(所有代码块与表格都可以左右滚动)
1. DISTINCT:单表基础去重
DISTINCT 是 SQL 中用于消除查询结果集重复行的关键字。它作用于整个 SELECT 子句返回的所有列,仅保留组合值完全唯一的行。若查询多列,需所有列值均相同才算重复。例如: SELECT DISTINCT col1, col2 FROM table
会对两列组合去重。注意:DISTINCT 会隐式排序结果,可能影响性能,大数据量时建议结合索引优化。
原理:在查询结果集上直接过滤重复行,保留唯一值。
语法:SELECT DISTINCT column1, column2 FROM table;
模拟数据:
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
department VARCHAR(50)
);
INSERT INTO employees VALUES
(1, 'Alice', 'HR'),
(2, 'Bob', 'Engineering'),
(3, 'Alice', 'HR'), -- 姓名重复
(4, 'Charlie', 'Marketing'),
(5, 'Bob', 'Engineering'); -- 姓名+部门重复
查询示例:
-- 单列去重
SELECT DISTINCT name FROM employees;
/* 输出:
+----------+
| name |
+----------+
| Alice |
| Bob |
| Charlie |
+----------+
注释:重复的 'Alice' 和 'Bob' 被合并 */
-- 多列联合去重
SELECT DISTINCT name, department FROM employees;
/* 输出:
+----------+---------------+
| name | department |
+----------+---------------+
| Alice | HR |
| Bob | Engineering |
| Charlie | Marketing |
| Bob | Engineering | -- 此行保留(因id不同)
+----------+---------------+
注释:多列去重需所有字段值完全相同 */
2. GROUP BY:分组去重
GROUP BY 是 SQL 中按指定字段分组实现去重的关键字,可结合聚合函数(如 COUNT、MAX)处理分组数据。语法为 SELECT 列 FROM 表 GROUP BY 列
,仅保留每组唯一值。例如按部门分组统计人数,比 DISTINCT 更灵活,支持复杂计算,大数据量下性能更优(依赖索引)。
原理:按指定字段分组实现去重,支持聚合计算。
语法:SELECT column1, COUNT(*) FROM table GROUP BY column1;
查询示例:
-- 统计每个部门的员工数
SELECT department, COUNT(*) AS employee_count
FROM employees
GROUP BY department;
/* 输出:
+---------------+----------------+
| department | employee_count |
+---------------+----------------+
| HR | 2 |
| Engineering | 2 |
| Marketing | 1 |
+---------------+----------------+
注释:按部门分组并计数 */
GROUP BY 与 DISTINCT 对比:
均能去重,GROUP BY 按字段分组,支持聚合计算(如 COUNT),适合需统计分析的场景;DISTINCT 直接返回唯一行,语法更简洁。大数据量时 GROUP BY 性能可能更优(依赖索引),且结果可排序,而 DISTINCT 作用于全列组合,无分组逻辑。
3. UNION 与 UNION ALL:多结果集去重
在SQL中,UNION 和 UNION ALL 都是用于合并多个查询结果集的操作符,需保证各查询返回的列数、顺序和数据类型一致。
- UNION:会对合并后的结果集自动去除重复行,相当于合并+去重,但可能因排序去重消耗更多性能。
- UNION ALL:直接合并所有结果,包括重复行,不进行去重处理,执行效率更高。
实际使用时,若确认无重复或无需去重,优先用 UNION ALL 提升性能。
原理:
模拟数据:
CREATE TABLE hr_dept (id INT, name VARCHAR(50));
CREATE TABLE eng_dept (id INT, name VARCHAR(50));
INSERT INTO hr_dept VALUES (1, 'Alice'), (2, 'David');
INSERT INTO eng_dept VALUES (2, 'David'), (3, 'Bob'), (1, 'Alice');
查询对比:
-- UNION:自动去重
SELECT name FROM hr_dept
UNION
SELECT name FROM eng_dept;
/* 输出:
+-------+
| name |
+-------+
| Alice |
| David |
| Bob |
+-------+ */
-- UNION ALL:保留所有记录
SELECT name FROM hr_dept
UNION ALL
SELECT name FROM eng_dept;
/* 输出:
+-------+
| name |
+-------+
| Alice |
| David |
| David |
| Bob |
| Alice |
+-------+ */
性能提示:
4. EXCEPT / MINUS:差集去重
EXCEPT / MINUS 是SQL中用于求差集的操作符,返回第一个查询结果中不存在于第二个查询的记录并去重。
- EXCEPT支持SQL Server、PostgreSQL等;
需两查询列数、类型一致,MySQL不支持,需用LEFT JOIN模拟。适用于查找数据集差异场景。具体拆解如下:
原理:返回第一个结果集,剔除第二个结果集的重复项。
数据库支持:
模拟数据:
CREATE TABLE all_products (id INT, name VARCHAR(50));
CREATE TABLE sold_products (id INT, name VARCHAR(50));
INSERT INTO all_products VALUES (1, 'Laptop'), (2, 'Phone'), (3, 'Tablet');
INSERT INTO sold_products VALUES (1, 'Laptop'), (3, 'Tablet');
标准实现:
-- SQL Server/Oracle
SELECT * FROM all_products
EXCEPT
SELECT * FROM sold_products; -- Oracle用MINUS
/* 输出:
+----+-------+
| id | name |
+----+-------+
| 2 | Phone |
+----+-------+ */
MySQL替代方案:
-- LEFT JOIN模拟
SELECT ap.*
FROM all_products ap
LEFT JOIN sold_products sp ON ap.id = sp.id
WHERE sp.id IS NULL;
-- NOT IN模拟
SELECT * FROM all_products
WHERE id NOT IN (SELECT id FROM sold_products);
5. INTERSECT:交集去重
INTERSECT 是SQL中求交集的操作符,返回两个查询结果集中共有的记录并自动去重。需保证两查询列数、顺序和数据类型一致。
支持场景:如查找同时存在于两表的用户ID。
注意:MySQL不支持,需用INNER JOIN或IN子查询模拟;Oracle、SQL Server等主流数据库支持。性能依赖索引,大数据量慎用。具体介绍如下:
原理:返回两个结果集的共同项(自动去重)。
数据库支持:
模拟数据:
CREATE TABLE all_members (id INT, name VARCHAR(50));
CREATE TABLE active_members (id INT, name VARCHAR(50));
INSERT INTO all_members VALUES (1, 'Tom'), (2, 'Jerry'), (3, 'Spike');
INSERT INTO active_members VALUES (1, 'Tom'), (3, 'Spike'), (4, 'Tyke');
标准实现:
-- SQL Server/Oracle
SELECT * FROM all_members
INTERSECT
SELECT * FROM active_members;
/* 输出:
+----+-------+
| id | name |
+----+-------+
| 1 | Tom |
| 3 | Spike |
+----+-------+ */
MySQL替代方案:
-- INNER JOIN模拟
SELECT am.*
FROM all_members am
INNER JOIN active_members ac ON am.id = ac.id;
-- IN子查询模拟
SELECT * FROM all_members
WHERE id IN (SELECT id FROM active_members);
6. 高级去重技巧
1. 窗口函数去重
-- 保留每个部门最新入职记录
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY department
ORDER BY hire_date DESC
) AS rn
FROM employees
) t WHERE rn = 1;
2. 子查询去重
-- 查找有订单的用户
SELECT * FROM users
WHERE user_id IN (
SELECT DISTINCT user_id FROM orders
);
3. DELETE去重
-- 删除重复邮箱记录
DELETE FROM users
WHERE user_id NOT IN (
SELECT MIN(user_id)
FROM users
GROUP BY email
);
7. 数据库去重差异参考表
| | | |
---|
EXCEPT | | | |
INTERSECT | | | |
ROW_NUMBER | | | |
ROWID | | | |
8. 去重性能优化指南
索引策略:
-- 为去重字段创建索引
CREATE INDEX idx_dept ON employees(department);
去重代价排序(效率降序):
UNION ALL
> DISTINCT
≈ GROUP BY
> UNION
> EXCEPT
/INTERSECT
9. 去重应用场景速查表
| | |
---|
| | SELECT DISTINCT dept FROM emp |
| | SELECT col FROM A UNION SELECT col FROM B |
| | PARTITION BY id ORDER BY date DESC |
| | DELETE WHERE id NOT IN (SELECT MIN(id)...) |
| | SELECT FROM A EXCEPT SELECT FROM B |
| | SELECT FROM A INTERSECT SELECT FROM B |
10. SQL去重方法总结
SQL去重方法主要有三类:
- 基础去重:
DISTINCT
和GROUP BY
适合单表操作 - 集合运算:
UNION
/EXCEPT
/INTERSECT
处理多结果集
选择依据:
- 数据量大小(小数据用
DISTINCT
,大数据用GROUP BY
)
SQL 去重需兼顾场景与性能,小数据用 DISTINCT,大数据靠索引与分批。理解这些方法原理、差异及组合使用,可高效解决实际工作中的各类重复问题,提升数据质量。
阅读原文:原文链接
该文章在 2025/9/1 12:18:25 编辑过