分组排序取第一条数据 SQL写法

2024-04-25 11:20

本文主要是介绍分组排序取第一条数据 SQL写法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 背景

在数据库查询过程中经常遇到需要分组排序查询第一条数据的情况。例如,在消息列表中需要展示每个联系人最近的一条信息。

2. 解决方案

目前我接触到的解决方案有两种,分别是开窗函数 row_number 和变量法。

2.1 开窗函数法

比较常用的解决方案是使用开窗函数 row_number() over(partition by xxx order by xxx) 。使用开窗函数比较简便,只需要两个步骤

2.1.1 分组+排序+标注序号

select *, row_number()  over (partition by ${group_col} order by ${order_col} ) as rownum 
from ${table}

以上 SQL 中 ${group_col} 表示要分组的字段;${order_col} 表示排序字段;rownum 是为序号字段取的别名。这条 SQL 的含义是将数据表 table 中的数据根据 group_col 分组后根据 order_col 进行排序,并为每条数据标注出在当前分组中的序号。

2.1.2 取第一条

select * from t1 where rownum = 1
  • t1分组+排序+标注序号 后的表,这里为了简洁就直接用 t1 代表子查询了。
  • rownum分组+排序+标注序号 中序号的别名。where rownum = 1 表示取 分组+排序+标注序号 后的第一条数据。

SQL 的含义是从 分组+排序+标注序号 后的表中查询序号为 1 的数据,这里的序号是各分组中排序后的序号。

3.1.3 Demo

将上述两个步骤连贯起来的 Demo 如下。

select * from (select *, row_number() over (partition by ${group_col} order by ${order_col} )  rownum from ${table}) t1 
where rownum = 1 

2.2 变量法

开窗函数的方法在 Hive 和高版本的 MySQL 里都是比较简单的查询方法,但是在低版本的 MySQL (比如 MySQL 5.7)中不支持开窗函数。所以只能换另一种方法,就是变量法。变量法需要通过变量来达成开窗函数的效果所以比较复杂,主要分为三部:数据排序、添加序号、取第一条。

2.2.1 数据排序

select * from ${table} order by ${group_col}, ${order_col}

首先对 table 表中的数据进行排序,排序的字段中第一个必须是想要用于分组的字段 group_col,后面才是需要排序的字段 order_col

2.2.2 添加序号

SELECT t.*, IF(@x = ${group_col} OR (@x IS NULL AND ${group_col} IS NULL), @rank:=@rank + 1, @rank:=1 AND @x:= ${group_col}) as rownum from (SELECT @x:=- 1) t0, t

这条 SQL 中定义了两个变量 xrank,整个 SQL 都围绕这两个变量展开,下面分别解释一下相关的内容。

  • x 变量用于保存当前分组的字段值,比如当前分组的字段值为 1,如果后面发现分组字段值不等于 1 了则说明换到另一个分组了,需要重新计数。
  • rank 变量用于保存当前分组中上一条记录的序号,如果上一条记录的序号是 1,则当前记录序号为 2。
  • (SELECT @x:=- 1) 的作用是为 x 变量初始化一个默认值,如果不初始化默认值则 x 默认为 nullx 的默认值必须是分组字段中没有的值,不建议设置为 null
  • IF(@x = ${group_col} OR (@x IS NULL AND ${group_col} IS NULL), @rank:=@rank + 1, @rank:=1 AND @x:=${group_col}) as rownum 这条语句就是序号赋值的关键所在。含义为如果 x 与当前记录的分组字段值相同则 rank = rank + 1(同分组内序号递增),否则 rank=1 (不同分组重新开始计数)并且 @x:=${group_col}(将 x 赋值为当前记录的分组字段值)。
  • t数据排序 后的表,这里为了简洁用 t 来代替子查询。

2.2.3 取第一条

select * from t2 where t2.rownum = 1

t2添加序号 后的表,这里为了简洁也是用 t2 代替子查询。

2.2.4 Demo

将上述三个步骤融合在一起如下。

select * from (SELECT t.*, IF(@x = ${group_col} OR (@x IS NULL AND ${group_col} IS NULL), 		@rank:=@rank + 1, @rank:=1 AND @x:= ${group_col}) as rownum from (SELECT @x:=- 1) as t0, (select * from ${table} order by ${group_col}, ${order_col}) as t) as t2 where t2.rownum = 1

3. 例子

3.1 需求

有一张留言记录表,表中记录的是每个用户的留言和管理员的回复。现需要取每个用户最新的一条留言记录

3.2 表结构

字段名字段类型说明
idBigintId
addtimeTimestamp添加时间
useridBigint用户 id
adminidBigint管理员 Id
askLongtext留言
replyLongtext回复
isreplyInt是否回复

3.3 表数据概览

IdAddtimeUseridAdminidAskReplyIsreply
12024-04-15 13:05:0011提问10
1422024-04-15 13:04:0021提问20
1432024-04-15 13:03:0031提问30
1442024-04-15 13:01:0041提问40
1822024-04-22 14:31:27171370351367011230
1832024-04-22 14:31:3317137035136701123123210
1842024-04-22 14:31:361713703513670112321330

3.4 查询语句

查询的思路就是根据用户 Id 分组,按添加时间倒序(从大到小)排,取每个用户 Id 分组中的第一条数据。为了方便查看,在查询最后再将记录根据添加时间倒序排列,将留言时间最近的用户放在前面。

3.4.1 开窗函数法

select * from (select *, row_number()  over (partition by userid order by addtime desc)  rownum from chat where ask is not null ) t1 where rownum = 1 order by addtime desc

3.4.2 变量法

select addtime, userid, ask from ((select *, IF(@x = userid OR (@x IS NULL AND userid IS NULL), @rank:=@rank + 1, @rank:=1 AND @x:=userid) AS rownumFROM(SELECT @x:=- 1) t0, (SELECT * FROM chat ORDER BY userid , addtime DESC) t) t2WHERE t2.rownum = 1 order by addtime desc; 

3.5 结果

AddtimeUseridAsk
2024-04-22 14:31:3617137035136701232133
2024-04-15 13:05:001提问 1
2024-04-15 13:04:002提问 2
2024-04-15 13:03:003提问 3
2024-04-15 13:01:004提问 4

这篇关于分组排序取第一条数据 SQL写法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Mysql虚拟列的使用场景

《Mysql虚拟列的使用场景》MySQL虚拟列是一种在查询时动态生成的特殊列,它不占用存储空间,可以提高查询效率和数据处理便利性,本文给大家介绍Mysql虚拟列的相关知识,感兴趣的朋友一起看看吧... 目录1. 介绍mysql虚拟列1.1 定义和作用1.2 虚拟列与普通列的区别2. MySQL虚拟列的类型2

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

mysql数据库分区的使用

《mysql数据库分区的使用》MySQL分区技术通过将大表分割成多个较小片段,提高查询性能、管理效率和数据存储效率,本文就来介绍一下mysql数据库分区的使用,感兴趣的可以了解一下... 目录【一】分区的基本概念【1】物理存储与逻辑分割【2】查询性能提升【3】数据管理与维护【4】扩展性与并行处理【二】分区的

MySQL中时区参数time_zone解读

《MySQL中时区参数time_zone解读》MySQL时区参数time_zone用于控制系统函数和字段的DEFAULTCURRENT_TIMESTAMP属性,修改时区可能会影响timestamp类型... 目录前言1.时区参数影响2.如何设置3.字段类型选择总结前言mysql 时区参数 time_zon

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3

Oracle数据库使用 listagg去重删除重复数据的方法汇总

《Oracle数据库使用listagg去重删除重复数据的方法汇总》文章介绍了在Oracle数据库中使用LISTAGG和XMLAGG函数进行字符串聚合并去重的方法,包括去重聚合、使用XML解析和CLO... 目录案例表第一种:使用wm_concat() + distinct去重聚合第二种:使用listagg,

Python实现将实体类列表数据导出到Excel文件

《Python实现将实体类列表数据导出到Excel文件》在数据处理和报告生成中,将实体类的列表数据导出到Excel文件是一项常见任务,Python提供了多种库来实现这一目标,下面就来跟随小编一起学习一... 目录一、环境准备二、定义实体类三、创建实体类列表四、将实体类列表转换为DataFrame五、导出Da

Python实现数据清洗的18种方法

《Python实现数据清洗的18种方法》本文主要介绍了Python实现数据清洗的18种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录1. 去除字符串两边空格2. 转换数据类型3. 大小写转换4. 移除列表中的重复元素5. 快速统