DISTINCT 关键字或
GROUP BY 子
SQL中,去除重复的行是一个常见的数据清理任务,重复的行可能由于数据录入错误、数据合并等原因产生,这些重复数据会影响查询性能和数据分析的准确性,下面将详细介绍如何在SQL中去掉重复的行,包括使用不同的SQL语句和函数,以及在不同数据库系统中的实现方法。
理解重复行的概念
在关系型数据库中,一张表由若干行(记录)和列(字段)组成,当两张或多张行的特定列(或所有列)的值完全相同时,这些行就被认为是重复的,去除重复行的目标是保留每组重复行中的一行,通常是第一行或根据某些条件选择的特定行。
使用DISTINCT关键字
DISTINCT是SQL中用于去除结果集中重复行的关键字,它通常与SELECT语句一起使用,用于返回唯一不同的值。
语法:
SELECT DISTINCT column1, column2, ... FROM table_name;
示例:
假设有一个名为employees的表,包含以下数据:
| id | name | department |
|---|---|---|
| 1 | 张三 | 销售 |
| 2 | 李四 | 人事 |
| 3 | 王五 | 销售 |
| 4 | 张三 | 销售 |
如果我们想获取不重复的name和department组合,可以使用:
SELECT DISTINCT name, department FROM employees;
结果:
| name | department |
|---|---|
| 张三 | 销售 |
| 李四 | 人事 |
| 王五 | 销售 |
在这个例子中,DISTINCT去除了id为1和4的重复行,只保留了每个name和department组合的唯一一行。
使用GROUP BY语句
GROUP BY语句不仅用于聚合数据,还可以结合聚合函数来去除重复行,通过将需要的列进行分组,并选择每组的一行,可以达到去重的效果。
语法:
SELECT column1, column2, ..., aggregate_function(column) FROM table_name GROUP BY column1, column2, ...;
示例:
继续使用employees表,如果我们想获取每个部门的唯一员工姓名,可以使用:
SELECT MIN(id) AS id, name, department FROM employees GROUP BY name, department;
结果:
| id | name | department |
|---|---|---|
| 1 | 张三 | 销售 |
| 2 | 李四 | 人事 |
| 3 | 王五 | 销售 |
这里,MIN(id)选择了每组中id最小的那一行,从而去除了重复的name和department组合。
使用窗口函数(如ROW_NUMBER())
窗口函数如ROW_NUMBER()可以为每一行分配一个唯一的序号,基于指定的排序规则,这在去除重复行时非常有用,尤其是当我们需要保留特定的行(如最新的一行)时。
语法(以SQL Server为例):
SELECT
FROM (
SELECT ,
ROW_NUMBER() OVER (PARTITION BY column1, column2 ORDER BY some_column) AS row_num
FROM table_name
) sub
WHERE row_num = 1;
示例:
假设employees表中有一个hire_date列,表示员工的入职日期,我们想去除重复的name和department,但保留最新入职的员工。
SELECT
FROM (
SELECT ,
ROW_NUMBER() OVER (PARTITION BY name, department ORDER BY hire_date DESC) AS row_num
FROM employees
) sub
WHERE row_num = 1;
结果:
| id | name | department | hire_date |
|---|---|---|---|
| 4 | 张三 | 销售 | 2023-01-15 |
| 2 | 李四 | 人事 | 2022-05-20 |
| 3 | 王五 | 销售 | 2021-07-30 |
在这个例子中,ROW_NUMBER()为每组name和department分配了一个序号,按照hire_date降序排列,外层查询只选择了row_num = 1的行,即每组中最新入职的员工。
删除重复行并保留原始表结构
我们需要直接从表中删除重复的行,而不仅仅是查询时去重,这可以通过结合子查询和DELETE语句来实现。
语法(以MySQL为例):
DELETE t1
FROM table_name t1
INNER JOIN table_name t2
WHERE
t1.id > t2.id AND
t1.name = t2.name AND
t1.department = t2.department;
示例:
在employees表中,我们想删除id较大的重复行,保留id较小的行。
DELETE e1
FROM employees e1
INNER JOIN employees e2
WHERE
e1.id > e2.id AND
e1.name = e2.name AND
e1.department = e2.department;
执行后,employees表的数据为:
| id | name | department | hire_date |
|---|---|---|---|
| 1 | 张三 | 销售 | 2023-01-15 |
| 2 | 李四 | 人事 | 2022-05-20 |
| 3 | 王五 | 销售 | 2021-07-30 |
这里,id为4的行被删除,因为它与id为1的行在name和department上重复,并且id较大。
不同数据库系统的注意事项
不同的数据库系统在处理去重时可能有细微的差别,以下是一些常见数据库系统的注意事项:
-
MySQL:
- 使用
DISTINCT时,如果查询的列包含NULL,则NULL会被视为一个单独的值。 DELETE语句结合JOIN时,需要注意避免自我连接导致的错误。
- 使用
-
SQL Server:
- 支持窗口函数如
ROW_NUMBER(),RANK(),DENSE_RANK(),非常适合复杂的去重需求。 CTE(公用表表达式)可以简化复杂的去重查询。
- 支持窗口函数如
-
PostgreSQL:
- 类似于SQL Server,支持丰富的窗口函数。
- 可以使用
DISTINCT ON (column1, column2, ...)来保留每组的第一行。
-
Oracle:
- 支持
ROW_NUMBER()等分析函数。 - 可以使用
MERGE语句来删除重复行。
- 支持
-
SQLite:
- 支持基本的
DISTINCT和GROUP BY去重。 - 窗口函数的支持较为有限,需注意版本差异。
- 支持基本的
相关FAQs
Q1: 使用DISTINCT和GROUP BY去重有什么区别?
A1: DISTINCT用于直接去除结果集中完全相同的行,适用于简单的去重需求,而GROUP BY不仅可以去重,还可以结合聚合函数对数据进行汇总和计算,使用COUNT()可以统计每组重复行的数量。GROUP BY在需要对数据进行进一步处理时更为灵活。
Q2: 如何在删除重复行时选择保留哪一条记录?
A2: 在删除重复行时,可以通过指定排序规则来决定保留哪一条记录,可以使用ROW_NUMBER()窗口函数,根据某个列(如id、hire_date等)进行排序,并为每组分配一个序号,选择序号为1的行进行保留,删除其他行。
