当前位置: 首页 > news >正文

【MySQL】分组排序取每组第一条数据

需求:MySQL根据某一个字段分组,然后组内排序,最后每组取排序后的第一条数据。

准备表:

CREATE TABLE `t_student_score` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',`stu_name` varchar(32) NOT NULL COMMENT '学生姓名',`course_name` varchar(32) NOT NULL COMMENT '课程名称',`score` int(11) NOT NULL COMMENT '份数',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='学生-分数';

准备数据:

INSERT INTO `t_student_score` (`id`, `stu_name`, `course_name`, `score`) VALUES (1, '张三', '数学', 90);
INSERT INTO `t_student_score` (`id`, `stu_name`, `course_name`, `score`) VALUES (2, '李四', '语文', 94);
INSERT INTO `t_student_score` (`id`, `stu_name`, `course_name`, `score`) VALUES (3, '张三', '语文', 98);
INSERT INTO `t_student_score` (`id`, `stu_name`, `course_name`, `score`) VALUES (4, '李四', '数学', 97);
INSERT INTO `t_student_score` (`id`, `stu_name`, `course_name`, `score`) VALUES (5, '李四', '英语', 99);
INSERT INTO `t_student_score` (`id`, `stu_name`, `course_name`, `score`) VALUES (6, '张三', '英语', 100);

数据如下:

mysql> select * from t_student_score;
+----+----------+-------------+-------+
| id | stu_name | course_name | score |
+----+----------+-------------+-------+
|  1 | 张三     | 数学        |    90 |
|  2 | 李四     | 语文        |    94 |
|  3 | 张三     | 语文        |    98 |
|  4 | 李四     | 数学        |    97 |
|  5 | 李四     | 英语        |    99 |
|  6 | 张三     | 英语        |   100 |
+----+----------+-------------+-------+
6 rows in set (0.08 sec)

要求:查询出各科分数最高的学生姓名。

group by

查询出各科分数最高的学生姓名一开始可能会这样写:

select stu_name,course_name,max(score) from t_student_score group by course_name;

sql中只是简单的按课程进行分组,这样写就会导致一个问题也就是查询出来的各科最高分数可能不是那个学生的,结果如下:

mysql> select stu_name,course_name,max(score) from t_student_score group by course_name;
+----------+-------------+------------+
| stu_name | course_name | max(score) |
+----------+-------------+------------+
| 张三     | 数学        |         97 |
| 李四     | 英语        |        100 |
| 李四     | 语文        |         98 |
+----------+-------------+------------+
3 rows in set (0.05 sec)

很明显数学得97分的压根就不是张三,这是为什么呢,group by后的显示的列会只会根据所有组的第一行来显示,张三刚好在数学组的第一行,所以出来的是张三。

group by+子查询order by

既然我们知道group by后的显示的列会只会根据所有组的第一行来显示,那么我们先根据分数进行排序,这样分数最高的肯定是所有组的第一行,然后根据课程进行分组这样是不是就对了?

mysql> select stu_name,course_name,max(score) from (select * from t_student_score order by score desc) t group by course_name;
+----------+-------------+------------+
| stu_name | course_name | max(score) |
+----------+-------------+------------+
| 张三     | 数学        |         97 |
| 李四     | 英语        |        100 |
| 李四     | 语文        |         98 |
+----------+-------------+------------+
3 rows in set (0.13 sec)

什么情况,以前我怎么记得这么使用是对的呢?然后去查看SQL的执行计划:

mysql> explain select stu_name,course_name,max(score) from (select * from t_student_score order by score desc) t group by course_name;
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table           | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                           |
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
|  1 | SIMPLE      | t_student_score | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |   100.00 | Using temporary; Using filesort |
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
1 row in set (0.06 sec)

执行计划显示只有一个步骤,为什么不是分为两个步骤执行呢?第一步先根据表t_student_score的score字段进行倒序排序,第二步根据第一步生成的临时表t的course_name字段进行分组???

而在MySQL5.6中,执行上面的sql会出现不一样的结果:

mysql> select stu_name,course_name,max(score) from (select * from t_student_score order by score desc) t group by course_name;
+----------+-------------+------------+
| stu_name | course_name | max(score) |
+----------+-------------+------------+
| 李四     | 数学        |         97 |
| 张三     | 英语        |        100 |
| 张三     | 语文        |         98 |
+----------+-------------+------------+
3 rows in set (0.10 sec)

MySQL5.6中返回的结果正是我们想要的。

再来看下MySQL5.6中这个SQL的执行计划:

mysql> explain select stu_name,course_name,max(score) from (select * from t_student_score order by score desc) t group by course_name;
+----+-------------+-----------------+------+---------------+------+---------+------+------+---------------------------------+
| id | select_type | table           | type | possible_keys | key  | key_len | ref  | rows | Extra                           |
+----+-------------+-----------------+------+---------------+------+---------+------+------+---------------------------------+
|  1 | PRIMARY     | <derived2>      | ALL  | NULL          | NULL | NULL    | NULL |    6 | Using temporary; Using filesort |
|  2 | DERIVED     | t_student_score | ALL  | NULL          | NULL | NULL    | NULL |    6 | Using filesort                  |
+----+-------------+-----------------+------+---------------+------+---------+------+------+---------------------------------+
2 rows in set (0.09 sec)

MySQL5.6中这个SQL的执行计划分为两个步骤执行的。

那么为什么切换了版本后就好了呢?

derived_merge

MySQL5.7针对于5.6版本做了一个优化,针对MySQL本身的优化器增加了一个控制优化器的参数叫derived_merge,什么意思呢,“派生类合并”。

官方文档介绍:https://dev.mysql.com/doc/refman/5.7/en/derived-table-optimization.html

使用合并或实现来优化派生表和视图引用优化器可以使用两种策略(也适用于视图引用)处理派生表引用:

  • 将派生表合并到外部查询块中
  • 将派生表实现为内部临时表

例如:

SELECT * FROM (SELECT *FROM t1) AS derived_t1

通过合并派生表derived_t1,该查询的执行类似于:

SELECT * FROM t1;

原来是派生类合并在作怪,通过对MySQL官方使用手册的了解,MySQL5.7对derived_merge参数默认设置为on,也就是开启状态,我们在MySQL5.7中把这个特性关闭使用就行了,如下命令:

# 针对当前session关闭
set session optimizer_switch="derived_merge=off";# 全局关闭
set global optimizer_switch="derived_merge=off";

这样如果from中查询出来的的结果就不会与外部查询块合并了,sql执行结果如下:

mysql> set session optimizer_switch="derived_merge=off";
Query OK, 0 rows affected (0.01 sec)mysql> select stu_name,course_name,max(score) from (select * from t_student_score order by score desc) t group by course_name;
+----------+-------------+------------+
| stu_name | course_name | max(score) |
+----------+-------------+------------+
| 李四     | 数学        |         97 |
| 张三     | 英语        |        100 |
| 张三     | 语文        |         98 |
+----------+-------------+------------+
3 rows in set (0.07 sec)mysql> explain select stu_name,course_name,max(score) from (select * from t_student_score order by score desc) t group by course_name;
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table           | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                           |
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
|  1 | PRIMARY     | <derived2>      | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |   100.00 | Using temporary; Using filesort |
|  2 | DERIVED     | t_student_score | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |   100.00 | Using filesort                  |
+----+-------------+-----------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
2 rows in set (0.10 sec)

其实修改derived_merge参数得谨慎而行之,因为MySQL5.7版本有了这个优化的机制是有它的道理的,之所以去除派生类与外部块合并,是因为减少查询开销,派生类是个临时表,开辟一个临时表的同时还要维护和排序或者分组,都会影响效率,所以尽量不要去修改此参数。

其实也有多种办法不需要修改derived_merge参数而使合并派生类失效,具体做法可参考官方使用手册,可以通过在子查询中使用任何阻止合并的构造来禁用合并,尽管这些构造对实现的影响并不明确。

防止合并的构造对于派生表和视图引用是相同的:

  • 聚合函数(SUM(),MIN(),MAX(),COUNT()等)
  • DISTINCT
  • GROUP BY
  • HAVING
  • LIMIT
  • UNION或UNION ALL
  • 选择列表中的子查询
  • 分配给用户变量
  • 仅引用文字值(在这种情况下,没有基础表)

下面通过在子查询中使用distinct关键字来禁用derived_merge:

mysql> explain select stu_name,course_name,max(score) from (select distinct(id) tid,s.* from t_student_score s order by score desc) t group by course_name;
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                           |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |   100.00 | Using temporary; Using filesort |
|  2 | DERIVED     | s          | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    6 |   100.00 | Using filesort                  |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------+
2 rows in set (0.08 sec)

子查询order by失效的场景

因为临时表(派生表derived table)中使用order by且使其生效,必须满足三个条件:

  • 外部查询禁止分组或者聚合
  • 外部查询未指定having, order by
  • 外部查询将派生表或者视图作为from句中唯一指定源

不满足这三个条件,order by会被忽略。

一旦外部表使用了group by,那么临时表(派生表 derived table)将不会执行filesort操作(即order by 会被忽略)。

相关文章:

【MySQL】分组排序取每组第一条数据

需求&#xff1a;MySQL根据某一个字段分组&#xff0c;然后组内排序&#xff0c;最后每组取排序后的第一条数据。 准备表&#xff1a; CREATE TABLE t_student_score (id int(11) NOT NULL AUTO_INCREMENT COMMENT ID,stu_name varchar(32) NOT NULL COMMENT 学生姓名,course…...

滚珠螺杆在精密机械设备中如何维持精度要求?

滚珠螺杆在精密设备领域中的运用非常之广泛&#xff0c;具有精度高、效率高的特点。为了确保滚珠螺杆在生产设备中能够发挥最佳性能&#xff0c;我们必须从多个维度进行深入考量&#xff0c;并采取针对性的措施&#xff0c;以确保其稳定、精准地服务于现代化生产的每一个环节。…...

现代 c++ 三:右值引用与移动语义

c11 为了提高效率&#xff0c;引入了右值引用及移动语义&#xff0c;这个概念不太好理解&#xff0c;需要仔细研究一下&#xff0c;下文会一并讲讲左值、右值、左值引用、右值引用、const 引用、移动构造、移动赋值运行符 … 这些概念。 左值和右值 左值和右值是表达式的属性。…...

Java学习【类与对象—封装】

Java学习【类与对象—封装】 封装的概念封装的实现包的概念import 导包导包中*的介绍import static 导入包中的静态方法和字段 static关键字的使用static 修饰成员变量static修饰方法静态成员变量的初始化 代码块静态代码块非静态代码块/实例化代码块/构造代码块加载顺序 封装的…...

Co-Driver:基于 VLM 的自动驾驶助手,具有类人行为并能理解复杂的道路场景

24年5月来自俄罗斯莫斯科研究机构的论文“Co-driver: VLM-based Autonomous Driving Assistant with Human-like Behavior and Understanding for Complex Road Scenes”。 关于基于大语言模型的自动驾驶解决方案的最新研究&#xff0c;显示了规划和控制领域的前景。 然而&…...

硅胶可以镭射吗?

在科技发展的今天&#xff0c;我们经常会遇到各种各样的材料&#xff0c;其中就有一种叫做硅胶的材料。那么&#xff0c;硅胶可以镭射吗&#xff1f;答案是肯定的&#xff0c;硅胶不仅可以镭射&#xff0c;而且在某些应用中&#xff0c;它的镭射特性还非常突出。 首先&#xff…...

财务风险管理:背后真相及应对策略

市场经济蓬勃发展&#xff0c;机遇与风险并存也是市场经济的一项重要特征。而财务状况的好坏影响着一个企业的发展前景&#xff0c;作为市场经济的必然产物&#xff0c;财务风险贯穿于企业的一切生产经营活动中&#xff0c;无法预知也不以人的意志为转移。 一、企业财务风险的特…...

MySQL深入理解事务(详解)

事务概述 事务是数据库区别于文件系统的重要特性之一&#xff0c;当我们有了事务就会让数据库始终保持一致性&#xff0c;同时我们还能通过事务机制恢复到某个时间点&#xff0c;这样可以保证已提交到数据库的修改不会因为系统崩溃而丢失。 1、基本概念 事务&#xff1a;一组…...

【Linux系统】进程控制

本篇博客整理了进程控制有关的创建、退出、等待、替换操作方面的知识&#xff0c;最终附有模拟实现命令行解释器shell来综合运用进程控制的知识&#xff0c;旨在帮助读者更好地理解进程与进程之间的交互&#xff0c;以及对开发有一个初步了解。 目录 一、进程创建 1.创建子进…...

Go语言数值类型教程

Go语言提供了丰富的数值类型&#xff0c;包括整数类型、浮点类型和复数类型。每种类型都有其特定的用途和存储范围。下面将详细介绍这些类型&#xff0c;并附带示例代码。 原文链接&#xff1a; Go语言数值类型教程 - 红客网-网络安全与渗透技术 1. 整数类型 原文链接&#xf…...

Linux进程控制——Linux进程等待

前言&#xff1a;接着前面进程终止&#xff0c;话不多说我们进入Linux进程等待的学习&#xff0c;如果你还不了解进程终止建议先了解&#xff1a; Linux进程终止 本篇主要内容&#xff1a; 什么是进程等待 为什么要进行进程等待 如何进程等待 进程等待 1. 进程等待的概念2. 进…...

GPT-4o:融合文本、音频和图像的全方位人机交互体验

引言: GPT-4o(“o”代表“omni”)的问世标志着人机交互领域的一次重要突破。它不仅接受文本、音频和图像的任意组合作为输入,还能生成文本、音频和图像输出的任意组合。这一全新的模型不仅在响应速度上达到了惊人的水平,在文本、音频和图像理解方面也表现出色,给人带来了…...

灵活的静态存储控制器 (FSMC)的介绍(STM32F4)

目录 概述 1 认识FSMC 1.1 应用介绍 1.2 FSMC的主要功能 1.2.1 FSMC用途 1.2.2 FSMC的功能 2 FSMC的框架结构 2.1 AHB 接口 2.1.1 AHB 接口的Fault 2.1.2 支持的存储器和事务 2.2 外部器件地址映射 3 地址映射 3.1 NOR/PSRAM地址映射 3.2 NAND/PC卡地址映射 概述…...

nginx-rtmp

1.已经安装nginx&#xff1b;configure配置模块&#xff1b;make编译无需安装&#xff1b;把objs/nginx复制到已安装的宁目录下 ./configure --prefix/usr/local/nginx --add-module/usr/local/src/fastdfs-nginx-module/src --add-module/usr/local/src/nginx-rtmp-module-mas…...

nginx 代理java 请求报502

情况&#xff1a;nginx代理java 请求 后端返回正常&#xff0c;但是经过nginx 时报502 经过多次对比其他接口发现可能是返回的请求头过大&#xff0c;导致nginx 报错&#xff1a;如下 2024/05/13 02:57:12 [error] 88#88: *3755 upstream sent too big header while reading r…...

面试集中营—Redis面试题

一、Redis的线程模型 Redis是基于非阻塞的IO复用模型&#xff0c;内部使用文件事件处理器&#xff08;file event handler&#xff09;&#xff0c;这个文件事件处理器是单线程的&#xff0c;所以Redis才叫做单线程的模型&#xff0c;它采用IO多路复用机制同时监听多个socket&a…...

关于使用git拉取gitlab仓库的步骤(解决公钥问题和pytho版本和repo版本不对应的问题)

先获取权限&#xff0c;提交ssh-key 虚拟机连接 GitLab并提交代码_gitlab提交mr-CSDN博客 配置完成上诉步骤之后&#xff0c;执行下列指令进行拉去仓库的内容 sudo apt install repo export PATHpwd/.repo/repo:$PATH python3 "实际路径"/repo init -u ssh://gitxx…...

Django图书馆综合项目-学习(2)

接下来我们来实现一下图书管理系统的一些相关功能 1.在书籍的book_index.html中有一个"查看所有书毂"的超链接按钮&#xff0c;点击进入书籍列表book_list.html页面. 这边我们使用之前创建的命名空间去创建超连接 这里的book 是在根路由创建的namespacelist是在bo…...

vue3+ts 获取input 输入框中的值

从前端input 输入框获取值&#xff0c;通过封装axios 将值传给后端服务 数据格式为json html <el-form> <el-form-item label"域名"><el-input v-model"short_url" style"width: 240px"type"text"placeholder&quo…...

Gin框架返回Protobuf类型:提升性能的利器

在构建高效、高性能的微服务架构时&#xff0c;数据序列化和反序列化的性能至关重要。Protocol Buffers&#xff08;简称Protobuf&#xff09;作为一种轻量级且高效的结构化数据存储格式&#xff0c;已经在众多领域得到广泛应用。Gin框架作为Go语言中流行的Web框架&#xff0c;…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...

十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建

【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟

2025年4月29日&#xff0c;在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上&#xff0c;可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞&#xff0c;强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...