游乐游手机版
首页/数据库/文章详情

SQL基础查询中DISTINCT和GROUP BY的去重选择

时间:2026-07-20 06:58
DISTINCT用于整行去重和多列组合去重,不能与聚合函数混用,单列去重性能更优。GROUPBY用于分组统计,非分组字段必须显式聚合,多列组合去重更稳定。去重并保留最新记录需使用窗口函数或子查询。

许多拥有多年经验的SQL开发者,在面试时遇到这个问题也常常会陷入思考:进行数据去重时,DISTINCT和GROUP BY应该如何选择?虽然两者看似都能实现去重功能,但其底层语义和执行逻辑截然不同。若使用不当,轻则引发SQL语法错误,重则导致查询结果出现偏差而难以察觉。本文将深入对比分析,帮你彻底理清DISTINCT与GROUP BY的核心区别与最佳实践。

SQL基础查询中DISTINCT和GROUP BY去重到底该怎么选择?

需求一:仅需罗列“不重复值”——DISTINCT实现单列与多列去重

当业务场景仅需获取部门列表、城市名称或状态码等不重复值时,DISTINCT关键字是最直接、最高效的选择。它的核心使命就是返回“不重复的行”,避免了复杂的统计逻辑。

一个常见的误用场景是:强行使用GROUP BY代替DISTINCT进行简单去重。在MySQL 8.0及以上版本默认开启的only_full_group_by模式下,执行SELECT dept, salary FROM emp GROUP BY dept会直接报错;即使关闭该模式,查询返回的salary值也是随机且不可信的。

掌握DISTINCT的以下几个关键特性,能让你更得心应手:

  • 它作用于整行记录,而非单一列。只有当所有被选中的字段值完全相同时,才判定为重复行。
  • 多列组合去重是其核心优势,例如SELECT DISTINCT user_id, order_date FROM orders,精准表达“同一用户同一订单日仅保留一条”的语义。
  • NULL值在去重时被视为一个独立的值,多个NULL行只会保留一个。
  • 它不能直接与聚合函数混用,类似SELECT DISTINCT COUNT(*)的语法是无效的。

需求二:既要去重又要统计——GROUP BY配合聚合函数

当查询需求中包含“每个分类下的数量”、“平均销售额”、“最高温度”等分组统计指标时,这已不再是纯粹的去重问题,而是典型的分组聚合查询。此时,GROUP BY是唯一正确的语法选择。

之前提到的错误,本质上是试图用分组来实现去重,却未对非分组字段进行聚合包裹。正确的SQL写法是:SELECT dept, MAX(salary) FROM emp GROUP BY dept。必须牢记:出现在SELECT列表中的非分组字段,必须包含在聚合函数中。

在多字段分组场景下,GROUP BY dept, role的含义是“对部门与角色的组合进行分组”,而非分别对部门和角色去重。若需先对组合去重再进行统计,则应使用子查询嵌套:内层用DISTINCT生成唯一组合,外层进行GROUP BY聚合。

性能对比:大数据量下DISTINCTGROUP BY的执行计划差异

当数据表记录超过百万级时,DISTINCTGROUP BY的执行计划可能天差地别。特别是在处理多列组合或未建立索引的字段时,性能差异会急剧放大。

  • 单列去重:通常DISTINCT性能更优,数据库对其有专门的哈希算法优化,以快速过滤重复值。
  • 多列组合去重(例如user_id, product_id):在某些数据库引擎中,GROUP BY的表现更为稳定,因为它天然利用排序分组路径。
  • 在无索引的字段上使用DISTINCT,极易触发临时表与文件排序操作,相比GROUP BY更消耗内存资源。
  • 无论使用哪种方式,在执行前务必通过EXPLAIN命令检查是否有效利用了索引,切勿仅凭语句的简洁性来判断性能。

进阶难题:SQL去重后如何保留最新一条记录?

业务方提出“按账单号去重,并保留最新金额”的需求时,一个典型的SQL难题就出现了。DISTINCT无法控制保留哪一条记录,而GROUP BY的随机取值行为也不可靠。要实现“单字段去重 + 保留指定行”,必须借助窗口函数或优化的子查询。

  • MySQL 8.0及以上版本:使用ROW_NUMBER() OVER (PARTITION BY bill_no ORDER BY create_time DESC)窗口函数,对每组账单按时间倒序编号,从而筛选出最新记录。
  • MySQL 5.7等旧版本:通过LEFT JOINWHERE NOT EXISTS子查询,逻辑上找出“不存在更新时间比当前记录更大的同账单号”的行,从而确保获取的是最新记录。
  • 特别提醒:强行使用GROUP BY bill_no配合MAX(amount),虽然语法正确,但获取的是最大金额,而非最新金额——这两者在业务语义上完全不同。

归根结底,SQL开发中最容易踩的坑往往不是语法本身,而是对业务语言中“去重”二字的理解偏差。很多时候,业务方所说的“去重”已经隐含了“保留哪一条记录”的规则。然而,SQL标准中的DISTINCTGROUP BY都未对保留哪条记录做出承诺。因此,在沟通需求时,务必追问一句:“需要保留的是哪一条?”

来源:https://www.php.cn/faq/2808859.html
上一篇如何解决SQL插入数据时字段长度超限错误 下一篇SQL中如何将多个字段用字符串函数拼接成一列
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
为什么SQL中 NOT IN 子查询遇到 NULL 会导致 JOIN 逻辑完全崩溃失效
数据库 · 2026-07-21

为什么SQL中 NOT IN 子查询遇到 NULL 会导致 JOIN 逻辑完全崩溃失效

SQL的NOTIN子查询若结果包含NULL,三值逻辑会使整行判断为UNKNOWN,WHERE仅保留TRUE,导致所有行被过滤,返回空集。推荐使用NOTEXISTS替代,它不比较值,只判断子查询是否返回行,天然规避NULL问题。LEFTJOIN+ISNULL易写错,COALESCE或加ISNOTNULL仅权宜之计,可能掩盖数据问题。

完整Redis集群架构图及搭建步骤详解,新手必看
数据库 · 2026-07-21

完整Redis集群架构图及搭建步骤详解,新手必看

一、简介 Redis集群功能从3 0版本开始引入,到5 0 14版本已经相当成熟。本文就来聊聊如何搭建一个最简单的集群,以及常用的集群管理命令。版本锁定在5 0 14,所有操作均基于此版本。 二、架构图 先来看一个最基础的集群架构,一目了然: 三、搭建集群 3 1、下载 这里是在一台Linux服务器

SQL存储过程结合XML数据类型的高性能解析技巧
数据库 · 2026-07-21

SQL存储过程结合XML数据类型的高性能解析技巧

直接用 nodes() + value(),别碰 OPENXML 从 SQL Server 2005 起,OPENXML 就应该被淘汰了。它需要手动调用 sp_xml_preparedocument 和 sp_xml_removedocument,一旦遗漏后者就会引发内存泄漏;而且整个过程基于临

SQL窗口函数生成带层级结构的财务流水号技巧
数据库 · 2026-07-21

SQL窗口函数生成带层级结构的财务流水号技巧

财务流水号按业务类型分组连续编号,需用ROW_NUMBER()OVER(PARTITIONBYbusiness_typeORDERBYcreate_time)生成,避免先GROUPBY致明细丢失。日期前缀和补零拼接需注意数据库差异。多级嵌套结构需在PARTITIONBY中增加额外分类字段,并发环境下窗口函数无法保证唯一性,需结合序列或锁机制。

SQL中COALESCE函数优雅处理NULL值技巧与最佳实践全面指南
数据库 · 2026-07-21

SQL中COALESCE函数优雅处理NULL值技巧与最佳实践全面指南

COALESCE函数从左到右返回首个非NULL值,参数顺序决定兜底是否生效;类型不兼容时PostgreSQL和SQLServer报错,需显式CAST对齐;运算前需对每个可能为NULL的项单独包裹,否则表达式整体为NULL;避免在WHERE或JOIN条件中使用,否则导致语义错乱或索引失效;不处理空字符串,需嵌套NULLIF。