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

相关文章

SQL中的外键约束

外键约束用于表示两张表中的指标连接关系。外键约束的作用主要有以下三点: 1.确保子表中的某个字段(外键)只能引用父表中的有效记录2.主表中的列被删除时,子表中的关联列也会被删除3.主表中的列更新时,子表中的关联元素也会被更新 子表中的元素指向主表 以下是一个外键约束的实例展示

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

如何去写一手好SQL

MySQL性能 最大数据量 抛开数据量和并发数,谈性能都是耍流氓。MySQL没有限制单表最大记录数,它取决于操作系统对文件大小的限制。 《阿里巴巴Java开发手册》提出单表行数超过500万行或者单表容量超过2GB,才推荐分库分表。性能由综合因素决定,抛开业务复杂度,影响程度依次是硬件配置、MySQL配置、数据表设计、索引优化。500万这个值仅供参考,并非铁律。 博主曾经操作过超过4亿行数据

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

2. c#从不同cs的文件调用函数

1.文件目录如下: 2. Program.cs文件的主函数如下 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using System.Windows.Forms;namespace datasAnalysis{internal static

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi