MySQL中的数据去重,该用DISTINCT还是GROUP BY?

在日常工作中,数据库查询操作无处不在,而处理数据中的重复项与分组汇总是非常常见的需求。MySQL 提供了两种常见的方式来管理和检索唯一值:SELECT DISTINCTGROUP BY。这两者虽然在生成输出上可能相似,但用途与性能各有不同,使用场景也有所区分。

这篇文章带大家将从功能、性能以及实际应用等方面详细介绍 DISTINCTGROUP BY 的差异,并结合具体的示例数据来理解其使用场景。

SELECT DISTINCT

DISTINCT 是一个用于去重的关键字。SELECT DISTINCT 语句用于从结果集中删除重复行,只返回唯一值。因此,在需要仅获取数据的唯一部分时,DISTINCT 是一种简单高效的方式。

基本语法

sql 复制代码
SELECT DISTINCT column1, column2
FROM table_name;

参数说明:

  • column1, column2:要检索的字段名。
  • table_name:查询的表名。

特性说明

  • DISTINCT 可以基于单列或多列进行去重,只有多列的值完全相同时,才会被判定为重复行。
  • DISTINCT中,NULL 被视为一个独立的值,因此即使列中有多个 NULL 值,结果中只会保留一个 NULL

GROUP BY

GROUP BY 是一个用于分组的子句,通常与聚合函数配合使用以对分组后的数据进行汇总处理。它按指定列的值将行划分为不同的组。

基本语法

sql 复制代码
SELECT column1, aggregate_function(column_name)
FROM table_name
WHERE condition
GROUP BY column1, column2, ...;

参数说明:

  • column1, column2:分组的字段。
  • aggregate_function(column_name) :用于对分组内的行进行计算的聚合函数,例如 COUNT, SUM, AVG 等。
  • table_name:查询的表名字。
  • condition:可选,用于过滤行,在分组之前应用。
  • GROUP BY column1, column2... :定义用于分组的字段,具有相同值的行被分配到同一个组。

示例表结构与数据:

为了便于说明,我们定义两个表 customersorders,并插入一些示例数据。

创建表:

sql 复制代码
CREATE TABLE customers (
  customer_id INT PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  city VARCHAR(255) NOT NULL
);
​
INSERT INTO customers (customer_id, name, city) VALUES
  (1, 'John Doe', 'New York'),
  (2, 'Jane Smith', 'London'),
  (3, 'Mike Brown', 'Paris'),
  (2, 'Jane Smith', 'London'); -- 存在重复项
​
CREATE TABLE orders (
  order_id INT PRIMARY KEY,
  customer_id INT NOT NULL,
  product VARCHAR(255) NOT NULL,
  price DECIMAL(10,2) NOT NULL,
  FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
​
INSERT INTO orders (order_id, customer_id, product, price) VALUES
  (1, 1, 'Phone', 100.00),
  (2, 2, 'Laptop', 500.00),
  (3, 1, 'Tablet', 200.00),
  (4, 2, 'Watch', 150.00);

SELECT DISTINCT与GROUP BY使用对比

示例 1:检索唯一的客户城市

场景:我们希望查询 customers 表中的唯一城市,不关心重复的城市名称。

使用 DISTINCT

sql 复制代码
SELECT DISTINCT city
FROM customers;

输出:

markdown 复制代码
city
-----
New York
London
Paris

解释:

  • SQL 查询去除了数据集中重复的城市,只返回唯一值,简单直观。

示例 2:按客户城市统计订单数量

场景:我们希望统计每个城市对应的订单数量,涉及分组统计。

使用 GROUP BY

vbnet 复制代码
SELECT city, COUNT(*) AS order_count
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
GROUP BY city;

输出:

sql 复制代码
city         order_count
------------------------
London       2
New York     2

解释:

  • SQL 查询通过 GROUP BY 以城市分组,并结合 COUNT 聚合函数统计每组的订单数量,提供了更丰富的汇总信息。

SELECT DISTINCT 与 GROUP BY 的性能分析

虽然 DISTINCTGROUP BY 都会涉及底层的分组操作,但在某些情况下,它们可以互换使用,而在性能、功能上的表现会有所偏差。

两者实现的相似性

对于以下两条查询:

vbnet 复制代码
SELECT DISTINCT int1_index FROM test_table;
SELECT int1_index FROM test_table GROUP BY int1_index;

在某些情况下(如 int1_index 上有索引),两者使用相同的执行计划。例如,通过以下 EXPLAIN 分析,查询会通过索引扫描优化:

vbnet 复制代码
mysql> explain select distinct int1_index from test_distinct_groupby;
mysql> explain select int1_index from test_distinct_groupby group by int1_index;

两者结果中 Extra 字段显示 Using index for group-by,说明索引用于优化查询,效率相当。

GROUP BY的隐式排序问题

在 MySQL 8.0 之前,GROUP BY 默认对结果进行隐式排序。这可能导致额外的排序操作(filesort),增加了查询开销。在无显式排序要求时,DISTINCT 的性能会优于 GROUP BY

例如:

vbnet 复制代码
SELECT int6_random FROM test_table GROUP BY int6_random;

通过 EXPLAIN 查询,可以看到隐式排序增加了开销:

vbnet 复制代码
Extra: Using filesort

从 MySQL 8.0 开始,GROUP BY 不再强制进行隐式排序,性能接近 DISTINCT,尤其是在无索引的大数据场景下,二者效率更加一致。

SELECT DISTINCT 与 GROUP BY 的应用场景及差异

功能和目的对比:

功能 SELECT DISTINCT GROUP BY
目的 去重 分组并聚合数据
是否支持聚合函数
排序行为 否(可选) 是(默认排序,8.0后优化)
性能 无索引场景更高效 无索引场景稍慢(排序)
语法复杂度 简单 较复杂

适用场景

根据具体需求选择 DISTINCTGROUP BY

  1. 使用 SELECT DISTINCT
  • 当仅需要去除重复项,返回唯一值时。
  • 适用于简单查询场景。
  1. 使用 GROUP BY
  • 当需要按特定条件分组并对分组内的数据进行汇总或聚合(如 COUNT, SUM, AVG)时。
  • 适合复杂的业务场景,支持更多灵活的操作,如结合 HAVING 子句筛选分组后的结果。

结论

SELECT DISTINCTGROUP BY 是两种功能强大的工具,用于不同类型的 SQL 查询需求:

  • DISTINCT 适合简单去重,避免数据重复。
  • GROUP BY 更注重分组数据并对分组进行汇总分析。

在 MySQL 8.0 后,性能差距进一步缩小,但从语义清晰度与灵活性来看,GROUP BY 在处理复杂业务场景时更胜一筹。选择使用哪种方式应根据具体应用场景而定。

相关推荐
有想法的py工程师17 分钟前
PostgreSQL + Debezium CDC 踩坑总结
数据库·postgresql
Nandeska31 分钟前
2、数据库的索引与底层数据结构
数据结构·数据库
Victor35644 分钟前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易1 小时前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
小卒过河01041 小时前
使用apache nifi 从数据库文件表路径拉取远程文件至远程服务器目的地址
运维·服务器·数据库
Kiri霧1 小时前
Range循环和切片
前端·后端·学习·golang
过期动态1 小时前
JDBC高级篇:优化、封装与事务全流程指南
android·java·开发语言·数据库·python·mysql
WizLC1 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Mr.朱鹏1 小时前
SQL深度分页问题案例实战
java·数据库·spring boot·sql·spring·spring cloud·kafka
Victor3561 小时前
Netty(19)Netty的性能优化手段有哪些?
后端