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

相关文章

Python实现精确小数计算的完全指南

《Python实现精确小数计算的完全指南》在金融计算、科学实验和工程领域,浮点数精度问题一直是开发者面临的重大挑战,本文将深入解析Python精确小数计算技术体系,感兴趣的小伙伴可以了解一下... 目录引言:小数精度问题的核心挑战一、浮点数精度问题分析1.1 浮点数精度陷阱1.2 浮点数误差来源二、基础解决

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

Java实现远程执行Shell指令

《Java实现远程执行Shell指令》文章介绍使用JSch在SpringBoot项目中实现远程Shell操作,涵盖环境配置、依赖引入及工具类编写,详解分号和双与号执行多指令的区别... 目录软硬件环境说明编写执行Shell指令的工具类总结jsch(Java Secure Channel)是SSH2的一个纯J

使用Python实现Word文档的自动化对比方案

《使用Python实现Word文档的自动化对比方案》我们经常需要比较两个Word文档的版本差异,无论是合同修订、论文修改还是代码文档更新,人工比对不仅效率低下,还容易遗漏关键改动,下面通过一个实际案例... 目录引言一、使用python-docx库解析文档结构二、使用difflib进行差异比对三、高级对比方

深度解析Python中递归下降解析器的原理与实现

《深度解析Python中递归下降解析器的原理与实现》在编译器设计、配置文件处理和数据转换领域,递归下降解析器是最常用且最直观的解析技术,本文将详细介绍递归下降解析器的原理与实现,感兴趣的小伙伴可以跟随... 目录引言:解析器的核心价值一、递归下降解析器基础1.1 核心概念解析1.2 基本架构二、简单算术表达

QT Creator配置Kit的实现示例

《QTCreator配置Kit的实现示例》本文主要介绍了使用Qt5.12.12与VS2022时,因MSVC编译器版本不匹配及WindowsSDK缺失导致配置错误的问题解决,感兴趣的可以了解一下... 目录0、背景:qt5.12.12+vs2022一、症状:二、原因:(可以跳过,直奔后面的解决方法)三、解决方

MySQL中On duplicate key update的实现示例

《MySQL中Onduplicatekeyupdate的实现示例》ONDUPLICATEKEYUPDATE是一种MySQL的语法,它在插入新数据时,如果遇到唯一键冲突,则会执行更新操作,而不是抛... 目录1/ ON DUPLICATE KEY UPDATE的简介2/ ON DUPLICATE KEY UP

Python中Json和其他类型相互转换的实现示例

《Python中Json和其他类型相互转换的实现示例》本文介绍了在Python中使用json模块实现json数据与dict、object之间的高效转换,包括loads(),load(),dumps()... 项目中经常会用到json格式转为object对象、dict字典格式等。在此做个记录,方便后续用到该方

MySQL分库分表的实践示例

《MySQL分库分表的实践示例》MySQL分库分表适用于数据量大或并发压力高的场景,核心技术包括水平/垂直分片和分库,需应对分布式事务、跨库查询等挑战,通过中间件和解决方案实现,最佳实践为合理策略、备... 目录一、分库分表的触发条件1.1 数据量阈值1.2 并发压力二、分库分表的核心技术模块2.1 水平分

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom