【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 会被忽略)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/11799.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NumPy常用操作

目录 一&#xff1a;简介 二&#xff1a;NumPy 常用操作 三&#xff1a;总结 一&#xff1a;简介 是一个开源的Python库&#xff0c;它为Python提供了强大的多维数组对象和用于处理这些数组的函数。NumPy的核心是ndarray&#xff0c;它是一个高效的多维数组容器&#xff0c;用…

力扣【旋转函数】python

如果直接用暴力的话&#xff0c;只能过4个样例好像&#xff0c;超时 因此得用递推公式 F1F0前n-1个数-(n-1)*第n个数 F0sum(nums)-n*第n个数 nlen(nums) ans[]#定义一个存最大值值的列表 ss sum(nums) dm 0 for j in range(n):dm j * nums[j] ans.append(dm) print(dm) n…

springmvc返回json

springmvc返回json 现在很多项目已经前后端分离了&#xff0c;不再使用jsp或者使用jsp但是数据使用ajax来获取&#xff0c;实现局部刷新的效果&#xff0c;那么springmvc中如何不返回页面而返回页面所需要的数据呢。 前后端数据交互现在大多使用json来表示(当然有一部分还是使用…

面试中的算法(查找缺失的整数)

在一个无序数组里有99个不重复的正整数&#xff0c;范围是1~100&#xff0c;唯独缺少1个1~100中的整数。如何找出这个缺失的整数? 一个很简单也很高效的方法&#xff0c;先算出1~100之和&#xff0c;然后依次减去数组里的元素&#xff0c;最后得到的差值&#xff0c;就是那个缺…

目标检测YOLO实战应用案例100讲-基于深度学习的无人机航拍图像目标检测算法研究与应用(中)

目录 4.2旋转角度 4.3数据集预处理 4.4旋转框网络结构设计 4.5实验结果与分析

集合系列(二十五) -二叉树、平衡二叉树、红黑树性能总结

一、摘要 二叉树&#xff0c;作为一种数据结构&#xff0c;在实际开发中&#xff0c;有着非常广泛的应用&#xff0c;尤其是以平衡二叉树、红黑树为代表&#xff0c;在前几篇文章中&#xff0c;我们详细的介绍了BST、AVL、RBT的算法以及代码实践&#xff0c;下面简要概括描述一…

deveco studio 打开官方案例,不显示运行按钮。

就拿官方的search举例好了 git 地址 https://gitee.com/harmonyos/samples/tree/master/ETSUI/Search 使用deveco studio打开Search项目&#xff0c;打开Tools->Device-Manager中的Local Emulator本地模拟器&#xff0c; 此时会发现&#xff0c;运行按钮是灰色的&#xff0…

水利行业工程设计资质如何去申请

申请水利行业工程设计资质通常需要按照以下步骤进行&#xff1a; 事前准备&#xff1a; 制定材料清单&#xff0c;罗列出所需准备的文件。下载相关的申请表和模板。准备企业资料和人员资料等附件材料。人员要求&#xff1a; 确保企业拥有符合水利行业工程设计资质标准要求的注…

源码 axios 的创建过程模拟实现

1、在实例对象上添加两个属性&#xff1a;default(默认配置) 与 interscptors // //构造函数function Axios(config) {//初始化this.defaults config;//为了创建 default 默认属性this.interceptors {request: {},response: {}}} 2、在原型对象上添加方法 //原型添加相关的…

从零学算法994

994. 腐烂的橘子 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b; 值 1 代表新鲜橘子&#xff1b; 值 2 代表腐烂的橘子。 每分钟&#xff0c;腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。 返回 直…

微信小程序中的数据可视化组件封装艺术【附代码】

微信小程序中的数据可视化组件封装艺术 一、数据可视化的魅力与重要性数据可视化简述为什么要在小程序中封装数据可视化组件 二、微信小程序数据可视化基础小程序中的绘图工具&#xff1a;Canvas 三、实战&#xff1a;封装一个简易折线图组件设计思路组件结构&#xff08;line-…

java mybatis配置

MyBatis是一种支持自定义SQL、存储过程和高级映射的持久层框架。下面是一个简单的Java MyBatis配置示例&#xff1a; 首先&#xff0c;需要添加MyBatis的依赖到项目的pom.xml文件中&#xff1a; <dependency><groupId>org.mybatis</groupId><artifactId…

Python3 笔记:顺序结构

三种程序执行结构&#xff1a;顺序结构、选择结构和循环结构。 这三种结构对应的是&#xff1a;顺序执行所有的语句、选择执行部分语句和循环执行部分语句。 顺序结构是程序最基本的结构。就是程序按照语句顺序&#xff0c;从上到下依次执行各条语句。 例如&#xff1a; nu…

【运维实践项目|003】:Nginx集群化运维升级项目

项目名称 项目简称或代号&#xff1a;SUN项目&#xff08;这个可以自己随便编一个&#xff0c;每个公司的每个项目简称或代号都是内部任意起名的&#xff0c;显得专业一点&#xff0c;一般是项目关键词的首拼&#xff0c;比如这个CSUN是&#xff1a;ScaleUp Nginx&#xff09;…

一道dp错题

dis(a,b)就是两点之间的距离公式 那么这道题该怎么解呢,.先看数据范围x,y<1e4,so,18个点两点之间距离最大18*1e4*sqrt(2)<2^18,所以如果跳过的点大于18个点,那么显然一个区间内最多不会跳跃超过17个点 现在我们想知道前i个点跳跃几次在哪跳跃能够达到最小花费,不妨设跳…

【OceanBase诊断调优】—— 转储错误(错误代码 4138/ORA-01555)

当读事务很长时&#xff0c;租户进行转储会报 4138/ORA-01555 错误。本文介绍该错误的处理方法。 适用版本 OceanBase 数据库 V2.X 及以后的版本 问题现象 当读事务很长&#xff0c;租户进行转储时会出现以下错误。 Oracle 租户&#xff1a; ORA-01555&#xff1a;snapsho…

Keil调用跟踪

调试时程序卡在一个位置&#xff0c;恰巧这个函数被很多地方调用&#xff0c;需要知道上一步在哪。 程序暂停后&#xff0c; 查看调用堆栈&#xff0c;点击Keil菜单栏中的“View”&#xff0c;然后选择“Call Stack”&#xff08;调用堆栈&#xff09;选项。这将显示当前的调用…

市场活动系统搭建

精细差异化运营在今天的企业越来越普遍&#xff0c;运营驱动占据了业务经营的主导地位。各种营销活动&#xff0c;帮助我们差异化运营、激发潜在客户、带动连带消费、增加销售额度、提升用户增长、实现品牌宣传。 天猫、京东上有各种各样的促销活动。如&#xff1a;满减、满返、…

算法day04

第一题 &#xff1a; 209. 长度最小的子数组 有上题可知&#xff0c;我们会采用双指针和单调性的思路来解决 我们本题采用左右双指针从数组的0位置同向前进&#xff0c;所以将此类模型称为滑块&#xff1b; 步骤思路如下&#xff1a; 步骤一&#xff1a; 定义所有双指针都指向…

Prompt提示词的技巧

Prompt提示词的技巧 要让GPT类模型产生最符合我们需求的输出&#xff0c;我们需要精心设计和调整输入的提示词&#xff08;Prompt&#xff09;。 1、明确性&#xff1a; 确保你的提示词清晰、具体。GPT类模型会根据你给出的信息来生成文本&#xff0c;因此&#xff0c;提供详…