当前位置: 首页
数据库
SQL中使用GROUP BY子句配合多字段实现复杂去重的方法

SQL中使用GROUP BY子句配合多字段实现复杂去重的方法

热心网友 时间:2026-07-01
转载

在数据查询与数据库优化中,GROUP BY 多字段分组究竟承担什么角色?许多初学者容易把它理解为“高级去重”,但其本质是“归并”而非“删除”。GROUP BY 按照指定的字段组合将数据划分为多个小组,每组只返回一行。当执行 SELECT a, b FROM t GROUP BY a, b 时,结果看似实现了去重,实际上是将所有 a、b 值相同的行聚合到一起,再从每组中抽取一行。至于抽取哪一行,MySQL 本身并不能保证确定性。

一个常见的错误场景:当你写出 SELECT code, cdate, ctotal FROM tt GROUP BY code 时,数据库可能直接报错 Expression #2 of SELECT list is not in GROUP BY clause。这通常是因为你使用的是 MySQL 8.0+ 版本,并且默认开启了 ONLY_FULL_GROUP_BY 模式。

这里有三个关键点你必须掌握:

  • SELECT 列表中所有非聚合字段必须出现在 GROUP BY 列表中,否则数据库拒绝执行。
  • 如果你执意只按 code 分组,同时又想输出 cdatectotal,就必须通过聚合函数来“包装”它们,例如 MAX(cdate)ANY_VALUE(ctotal)
  • ANY_VALUE() 是 MySQL 提供的“逃生门”,表示你确认“该组内各行的值相同,或者任意取值均可接受”。但它并不保证稳定,在不同版本或执行计划下可能返回不同行。

用 MIN/MAX 等聚合函数控制“留哪一条”

当你想保留每组中某个字段的最小或最大值(例如最早的日期、最小的 ID)时,MIN()MAX() 是最实用且可控的组合方案。

举例说明:从 students 表中,按 nameclass 进行去重,并且希望保留每组内 id 最小的完整记录。正确写法如下:

SELECT MIN(id) AS id, name, classFROM studentsGROUP BY name, class;

请注意,nameclass 是分组依据,MIN(id) 是聚合结果。你不能直接写成 SELECT id, name, class GROUP BY name, class,因为 id 既未被聚合,也未被包含在 GROUP BY 子句中,必然导致错误。

这里有两个实用技巧:

  • 如果表中包含 created_at 字段,想保留每组最新的一条记录,就用 MAX(created_at),再配合子查询或 JOIN 将整行数据取回。
  • 聚合函数存在一个天然局限:它会丢弃原始行中的其他字段信息(如 emailphone)。如需保留这些字段,应改用窗口函数或关联子查询。
  • 性能方面,如果在 (name, class, id) 上创建了复合索引,GROUP BY 可以直接利用索引,避免临时表和文件排序,显著提升效率。

MySQL 8.0+ 推荐用 ROW_NUMBER() 实现精确逻辑去重

当需求更为精细时,例如“每个 code 只保留 cdate 最大的一条,并且要带上该行的所有字段”,GROUP BY + MIN/MAX 就显得力不从心——它只能返回聚合后的值,无法原封不动地返回完整行。

此时,窗口函数是最干净的解决方案:

WITH ranked AS (  SELECT *,         ROW_NUMBER() OVER (PARTITION BY code ORDER BY cdate DESC, id ASC) AS rn  FROM tt)SELECT code, cdate, ctotal, other_colFROM rankedWHERE rn = 1;

ROW_NUMBER() 确保每组内严格按指定顺序编号。PARTITION BY code 定义分组规则,ORDER BY cdate DESC, id ASC 决定“谁排第一”(先按日期降序,日期相同时再按 ID 升序,避免歧义)。

使用时的注意事项:

  • 窗口函数必须通过 CTE 或子查询封装,不能直接出现在 WHERE 子句中。
  • ORDER BY 中的字段必须能决定唯一顺序(例如加上 id),否则相同 cdate 下的行顺序不可预测。
  • 如果只需要去重后的部分字段,可以精简 SELECT 列表,但不要在 SELECT * 中随意删列,以免遗漏业务关键字段。

别在 GROUP BY 里混入高粒度字段

一个极易踩坑的地方:在 GROUP BY 中加入近似唯一的字段(如 order_idcreated_atuuid),导致分组粒度极细,结果看上去根本没有去重效果。

例如,你写 SELECT user_id, COUNT(DISTINCT product_id) FROM orders GROUP BY user_id, order_id。由于每个订单的 order_id 都不同,实际上每一行自成一组,COUNT(DISTINCT product_id) 的结果永远是 1。

预防方法:

  • 检查 GROUP BY 列表是否只包含真正代表“业务维度”的字段,比如 user_iddateregion
  • 运行一句 SELECT COUNT(*)COUNT(DISTINCT target_col) 对比,如果两个数字非常接近,说明分组粒度可能过细。
  • 如果既要保留明细,又要粗粒度统计,可以先在子查询中按目标维度聚合,再在外层对结果进行计算。

归根结底,SQL 中最难的从来不是语法本身,而是想清楚“我到底要用什么逻辑来定义重复”。字段组合的语义理不清,再漂亮的 SQL 也救不回来。

来源:https://www.php.cn/faq/2659341.html

游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

同类文章
更多
Redis 7.0增量AOF重写RDB前导码配置详解

Redis 7.0增量AOF重写RDB前导码配置详解

先说一个几乎所有人都踩过的典型误区:很多人把 aof-use-rdb-preamble yes 当作开启“增量重写”的开关。实际上,这个配置只干了一件事——让重写后的 AOF 文件头部带上 RDB 快照。它解决的是加载速度问题,跟“增量重写”本身的概念压根不是一回事。真正的增量重写,依赖的是 Red

时间:2026-07-02 09:05
在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

直接在Tornado里用SQLAlchemy同步执行SQL,结果就是阻塞IOLoop,所谓“异步框架里写同步数据库代码”,等于白搭。安全执行的关键不是“怎么写SQL”,而是“怎么不卡住事件循环”。 为什么不能在RequestHandler里直接调用session execute() 因为sessio

时间:2026-07-02 09:04
利用SQL触发器实现在INSERT数据时自动同步到审计表

利用SQL触发器实现在INSERT数据时自动同步到审计表

先说结论:可以用触发器把 INSERT 数据同步到审计表,但必须用 AFTER INSERT,并且审计表的字段顺序、类型、字符集得和源表严格一致。否则,轻则写入错位、数据截断,重则直接报错、丢数据。下面把这些坑一个一个掰开说。 能,但必须用 AFTER INSERT,且审计表字段顺序、类型、字符集要

时间:2026-07-02 09:04
如何用SQL编写按不同工作日统计员工出勤率

如何用SQL编写按不同工作日统计员工出勤率

在实际业务中,统计不同工作日的出勤率是HR系统里的高频需求。如果直接按日期函数分组,很容易掉进语言环境、索引失效或分母口径的坑里。下面就来拆解具体的实现要点。 必须用 CASE WHEN 将日期映射为固定 weekday 标签(如 Mon )再分组,避免语言环境导致的分组断裂;需过滤 DOW IN

时间:2026-07-02 09:03
Spring Boot 3动态拼接SQL为何引发严重安全漏洞

Spring Boot 3动态拼接SQL为何引发严重安全漏洞

SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()与last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须

时间:2026-07-02 09:03
热门专题
更多
刀塔传奇破解版无限钻石下载大全 刀塔传奇破解版无限钻石下载大全
洛克王国正式正版手游下载安装大全 洛克王国正式正版手游下载安装大全
思美人手游下载专区 思美人手游下载专区
好玩的阿拉德之怒游戏下载合集 好玩的阿拉德之怒游戏下载合集
不思议迷宫手游下载合集 不思议迷宫手游下载合集
百宝袋汉化组游戏最新合集 百宝袋汉化组游戏最新合集
jsk游戏合集30款游戏大全 jsk游戏合集30款游戏大全
宾果消消消原版下载大全 宾果消消消原版下载大全
  • 日榜
  • 周榜
  • 月榜