Mysql-组分去重/排序-不同版本实现row_number()

2023-10-08 04:20

本文主要是介绍Mysql-组分去重/排序-不同版本实现row_number(),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、 使用场景

  1. 在数据有重复情况下,取ID最大或者最小的数据
  2. 按照某个规则取提取数据行,比如需要分组排序后取topN数据。最简单的一个例子就比如提取一个班级不同科目排名前3同学的信息。下面以此为例进行mysql-5.7与mysql-8.0分析(5.7和8.0差异性比较大)。

二、创建模拟数据

DROP TABLE IF EXISTS `class_score`;
CREATE TABLE `class_score` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '学号',`class` int(11) NOT NULL  COMMENT '科目',`score`  int(11) NOT NULL COMMENT '分数',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT= 'class_score';INSERT INTO class_score (class,score) VALUES(1,88),(1,90),(1,70),(2,98),(2,86),(2,93),(3,87),(3,79),(3,95);

三、按照科目分组排序

MySql-8.0添加了窗口函数,功能更加强大,而5.7并没有窗口函数,可以通过在sql语句中定义变量来实现。

1. Mysql-8.0 版本实现

使用窗口函数很方便实现

selectid,class,score,row_number() over(partition by class order by score desc ) as scorerank,version() mysqlVersion
fromclass_score

说明: partition by 进行分桶,分桶之后排序。关于8.0窗口函数可参考:[[MySQL 8.0窗口函数 ROW_NUMBER() OVER()函数的使用]],英文好的大佬也可以在mysql官网查看。
在这里插入图片描述
如果需要取每科的第一名成绩,则在上一结果集中使用scorerank就可以控制

selectid,class,score
from(selectid,class,score,row_number() over(partition by class order by score desc ) as scorerankfromclass_score) row_score
wherescorerank = 1

在这里插入图片描述

注意:此sql在5.7环境执行会报错版本问题SQL 错误 [1064] [42000]: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(partition by class order by score desc ) as scorerank

2. Mysql-5.7 版本实现row_number()

核心注意点

-- 条件是 =  赋值是 :=  (这个大坑会在 四、注意事项中详细说明)
-- 有一点需要注意的就是if之后的 @classgroup := cs.class 相当于 if 的语句块
-- 其中也可以写为 case when 语句
if(@classgroup = cs.class, @rownum := @rownum + 1, @rownum := 1) as rownum,@classgroup := cs.class-- case when 格式 @classgroup := cs.class 也相当于语句块
case when @classgroup = cs.class then  @rownum := @rownum + 1 else @rownum := 1 end as rownum,@classgroup := cs.class
2.1 方式一:先排序,后分组
selectcs.id,cs.class,cs.score,if(@classgroup = cs.class, @rownum := @rownum + 1, @rownum := 1) as rownum,@classgroup := cs.class
from(select @rownum := 0, @classgroup := 0) temp,(select id, class, score from class_score order by class, score desc) cs-- 有一点需要注意的就是if之后的 @classgroup := cs.class 相当于 if 的语句块
-- 其中也可以写为 case when 语句
if(@classgroup = cs.class, @rownum := @rownum + 1, @rownum := 1) as rownum,@classgroup := cs.class-- case when 格式
case when @classgroup = cs.class then  @rownum := @rownum + 1 else @rownum := 1 end as rownum,@classgroup := cs.class

结果如下
在这里插入图片描述
重点:这种方式有个缺点就是后面不能之间使用having,不管是5.7还是8.0都会导致rownum混乱,如下

selectcs.id,cs.class,cs.score,if(@classgroup = cs.class, @rownum := @rownum + 1, @rownum := 1) as rownum,@classgroup := cs.class
from(select @rownum := 0, @classgroup := 0) temp,(select id, class, score from class_score order by class, score desc) cs
having rownum <3

结果却是

是不是和预想不太一样,不是想要的结果呢,用此方式取topN则需要在查询一次才可以

select id, class ,score , rownum  from (selectcs.id,cs.class,cs.score,if(@classgroup = cs.class, @rownum := @rownum + 1, @rownum := 1) as rownum,@classgroup := cs.classfrom(select @rownum := 0, @classgroup := 0) temp,(select id, class, score from class_score order by class, score desc) cs) cs2
where rownum < 2

这种方式

2.2 方式二:先排序,后分组

这种方式对比方式一的有点就是取topN可以少查询一次,但是,但是重点来啦,这种方式在8.0上的结果和5.7的结果完全不一样,请看结果

-- having之前可以添加where条件
selectversion(),cs.id,cs.class,cs.score,casewhen @classgroup = cs.class then @rownum := @rownum + 1else @rownum := 1end as rownum,@classgroup := cs.class
from(select@rownum := 0,@classgroup := 0) temp,class_score cs
where class < 3
havingrownum < 2
order byclass,score desc

5.7是正确的结果,但是8.0就混乱了
在这里插入图片描述

在这里插入图片描述

四、注意事项(采坑记录)

由于开发和测试环境mysql版本不同,使用分组去重之后,在开发5.7版本环境下使用sql定义变量方式是正常的,但是在测试8.0环境下就出现问题了。。。好吧,有小伙伴可能会说怎么环境信息怎么还不一样?但是项目实际情况就是这样子,而且创建的表还有区别,请往下看。(偷偷抱怨一哈)

1. 同样的sql在不同版本的MySql执行结果不一致…

问题就出在使用了三、2.2的方式,5.7中是正常的,但是8.0就懵*了…业务是在sql中去重之后分页排序,所以只能在sql中去重,不然就要内存分页了(个人想法,有大佬有其他思路可以共同探讨),去重之后数据竟然在8.0环境上还有重复数据,还是作为返回体的主键冲突。

详见三、2.2,此处就不加赘述了。

2. 是关于 = 与 := 直接导致的问题(被 : 支配的一天)

出现了问题1,于是乎各种找问题(各种搜索,顺便问了一个对 Oracle 比较熟悉的朋友,好吧,本作者表示不熟悉),然后再 if后的将 = 改为 :=,更神奇的是既然效果实现了。。。但是,但是隐藏着一个大坑:加其他条件的时候基本只能查询到一条数据(实际上有多条)
注意:总感觉这个是个闪现的问题,因为有时候报的错误还不一样,但是能确定的是报错 3。有可能是mysql桌面化工具的问题,看看 = 与 :=的差别就很好理解了。

if(@classgroup := cs.class, @rownum := @rownum + 1, @rownum := 1) as rownum,@classgroup := cs.class

参考 https://blog.csdn.net/qq_37155959/article/details/81005287
具体差别如下
= 只有在set和update时才是和:=一样,赋值的作用,其它都是等于的作用。鉴于此,用变量实现行号时,必须用:=
:= 不只在set和update时时赋值的作用,在select也是赋值的作用。

3.关于5.7和8.0排序规则的不同导致的问题

SQL 错误 [1267] [HY000]: Illegal mix of collations (utf8mb4_0900_ai_ci,IMPLICIT) and (utf8mb4_general_ci,IMPLICIT) for operation '='

原因就是8.0默认排序规则是 utf8mb4_0900_ai_ci,但是5.7使用utf8mb4默认的是utf8mb4_general_ci,这里涉及到Mysql底层的东西,了解即可,毕竟不是专业的DBA,
在这里插入图片描述
具体可参考:https://www.yisu.com/zixun/167157.html
这个问题比较容易,网上的帖子搜索一下一大把,主要有个批量处理的sql说一下,参考https://blog.csdn.net/weixin_46080292/article/details/124195512

SELECTCONCAT('ALTER TABLE `',TABLE_NAME,'` MODIFY `',COLUMN_NAME,'` ',DATA_TYPE,'(',CHARACTER_MAXIMUM_LENGTH,') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci',( CASE WHEN IS_NULLABLE = 'NO' THEN ' NOT NULL' ELSE '' END ),';' 
) 
FROMinformation_schema.COLUMNS 
WHERETABLE_SCHEMA = '库名' AND (DATA_TYPE = 'varchar' OR DATA_TYPE = 'char')and TABLE_NAME = '表名'

动态生成修改排序规则的sql,不用一个个字段修改其排序规则

五、总结

1.一个项目最好使用相同版本的Mysql。

重要的事情说三遍:
不同版本MySql的坑比较多。
不同版本MySql的坑比较多。
不同版本MySql的坑比较多。

2.Mysql版本可能有变换处理方式:

case1

如果涉及到去重排序的使用5.7版本的2.1方式,虽然可以多查询了一次,但是能适配8.0版本。
重新写一遍sql吧

select id, class ,score , rownum  from (selectcs.id,cs.class,cs.score,if(@classgroup = cs.class, @rownum := @rownum + 1, @rownum := 1) as rownum,@classgroup := cs.classfrom(select @rownum := 0, @classgroup := 0) temp,(select id, class, score from class_score order by class, score desc) cs) cs2
where rownum < 2
case2

已经是8.0版本的,建议之间用窗口函数即可

selectid,class,score
from(selectid,class,score,row_number() over(partition by class order by score desc ) as scorerankfromclass_score) row_score
wherescorerank = 1

4. where和having区别

1.执行的时机不一样:where是分组之前进行限定,不满足where条件,则不参与分组,而having是分组之后对结果进行过滤
2.可判断的条件不一样:where不能对聚合函数进行判断,having可以。
3.执行顺序:where>聚合函数>having
参考:
https://blog.csdn.net/Trybravely/article/details/122435317
https://blog.csdn.net/weixin_38860401/article/details/81332605

5. sql关键字执行顺序

参考:https://blog.csdn.net/u014044812/article/details/51004754

这篇关于Mysql-组分去重/排序-不同版本实现row_number()的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/162664

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

MyBatis 动态 SQL 优化之标签的实战与技巧(常见用法)

《MyBatis动态SQL优化之标签的实战与技巧(常见用法)》本文通过详细的示例和实际应用场景,介绍了如何有效利用这些标签来优化MyBatis配置,提升开发效率,确保SQL的高效执行和安全性,感... 目录动态SQL详解一、动态SQL的核心概念1.1 什么是动态SQL?1.2 动态SQL的优点1.3 动态S

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

Mysql表的简单操作(基本技能)

《Mysql表的简单操作(基本技能)》在数据库中,表的操作主要包括表的创建、查看、修改、删除等,了解如何操作这些表是数据库管理和开发的基本技能,本文给大家介绍Mysql表的简单操作,感兴趣的朋友一起看... 目录3.1 创建表 3.2 查看表结构3.3 修改表3.4 实践案例:修改表在数据库中,表的操作主要