记一次有意思的 SQL 实现 → 分组后取每组的第一条记录
开心一刻
今天,朋友气冲冲的走到我面前
朋友:我不是谈了个女朋友,谈了三个月嘛,昨天我偷看她手机,你猜她给我备注什么
我:备注什么?
朋友:舔狗 2 号!
我一听,气就上来了,说道:走,找她去,这婆娘确实该骂,臭不要脸的
朋友拉住我,劝到:哎哎,不是去骂她,是找她理论,叫她改成舔狗1号,是我先来的!
我:滚,我不认识你
需求背景
环境
MySQL 版本:8.0.27
有四张表:业务信息表、任务表、业务任务表、任务执行日志表
CREATE TABLE `t_business` (
`business_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '业务id',
`business_name` VARCHAR(100) NOT NULL COMMENT '业务名',
`note` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '备注',
`create_user` BIGINT(20) NOT NULL COMMENT '创建人',
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`modify_user` BIGINT(20) NOT NULL COMMENT '最终修改人',
`modify_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最终修改时间',
PRIMARY KEY (`business_id`) USING BTREE
) ENGINE=InnoDB COMMENT='业务信息'; CREATE TABLE `t_task` (
`task_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '任务id',
`task_name` VARCHAR(100) NOT NULL COMMENT '业务名',
`note` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '备注',
`create_user` BIGINT(20) NOT NULL COMMENT '创建人',
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`modify_user` BIGINT(20) NOT NULL COMMENT '最终修改人',
`modify_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最终修改时间',
PRIMARY KEY (`task_id`) USING BTREE
) ENGINE=InnoDB COMMENT='任务信息'; CREATE TABLE `t_business_task` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键id',
`business_id` BIGINT(20) UNSIGNED NOT NULL COMMENT '业务id',
`task_id` BIGINT(20) UNSIGNED NOT NULL COMMENT '任务id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='业务任务关系'; CREATE TABLE `t_task_exec_log` (
`log_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '日志id',
`task_id` BIGINT(20) UNSIGNED NOT NULL COMMENT '任务id',
`exec_status` VARCHAR(50) NOT NULL COMMENT '执行状态, 失败:fail,成功:success',
`data_date` DATE NOT NULL COMMENT '数据日期',
`note` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '备注',
`create_user` BIGINT(20) NOT NULL COMMENT '创建人',
`create_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`modify_user` BIGINT(20) NOT NULL COMMENT '最终修改人',
`modify_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最终修改时间',
PRIMARY KEY (`log_id`) USING BTREE
) ENGINE=InnoDB COMMENT='任务执行日志';
它们关系如下
一个业务下有多个任务,一个任务又可以属于不同的业务;同个业务下,一个任务最多关联一次
任务每执行一次就会生成一条执行日志;执行日志的数据日期 小于等于 任务执行的当前日期,比如昨天执行的任务的数据日期可以是前天的
四张表的数据量分别如下
需求
按业务分页,每个业务可以展开显示关联的任务信息以及任务最新的执行成功信息
任务最新的执行成功信息:状态成功,数据日期最大的那条执行日志信息;如果数据日期一致,则取最终修改时间最大的
后端返回的 JSON 数据类似如下
实现方式
先分页查业务和任务,再根据任务id循环查最新的执行成功信息
1、关联查询业务和任务
如果查询条件带任务信息(任务ID,任务名),那么 t_business 需要关联 t_business_task 、 t_task 来查
因为这三张表的数据量都比较小,联表查没什么问题
2、根据上一步查到的 task_id 集逐个去查 t_task_exec_log
SQL 类似如下
可以建个组合索引 idx_status_task_date_modify(exec_status,task_id,data_date,modify_time)
3、将第 1、2 步的数据进行组合
将任务的最新执行成功信息添加到任务信息中
逻辑非常清晰,代码实现起来也非常简单
但是,一个任务id就查一次数据库,这显然是有很大性能问题的(一般,公司的开发规范内都会有一条:禁止循环查数据库)
先分页查业务和任务,再根据任务id批量查最新的执行成功信息
1、关联查询业务和任务
2、根据第 1 步查到的任务id集批量查 t_task_exec_log
因为这是多个任务一起查,也就没法用 LIMIT 1 了
那如何查出每个任务的最新执行成功的那一条记录了?
这里也就对应了文章的标题:分组后取每组的第 1 条记录
实现方式其实有很多,我这里提供一种,如下
结合索引 idx_status_task_date_modify(exec_status,task_id,data_date,modify_time) ,查询速度还行
大家细看这个 SQL ,是不是发现了有意思的东西:GROUP_CONCAT(log_id ORDER BY data_date DESC,modify_time DESC)
是不是知识盲区,是不是有点东西?
3、将第 1、2 步的数据进行组合
新增任务最新执行成功记录表
一般而言,大数据量的日志表是不参与复杂查询的,所以单独拎出来一个表,专门记录任务最新执行成功信息
一个任务最多只有一条记录,不存在则直接插入表中,存在则根据 data_date DESC,modify_time DESC 与表中记录做比较,看是否需要进行表中记录更新
因为一个任务最多只有一条记录,那么 t_task_latest_exec_log 的数据量是 小于等于 t_task 的数据量的,也就是说数据量不大
那么用一个 SQL 就可以实现业务(直接联表 t_business 、 t_business_task 、 t_task 、 t_task_latest_exec_log )
然后在后端代码中进行数据格式的处理,返回前端需要的格式
新增表后,其初始数据该如何导入了?
总结
1、大家写 SQL 的时候,一定要多结合执行计划来写
神奇的 SQL 之 MySQL 执行计划 → EXPLAIN,让我们了解 SQL 的执行过程!
2、 t_task_latest_exec_log 初始数据的导入
其实比较简单, 如下所示
INSERT INTO t_task_latest_exec_log(task_id,data_date,create_user,create_time,modify_user,modify_time)
SELECT t2.task_id, t2.data_date, t2.create_user, t2.create_time, t2.modify_user,t2.modify_time FROM (
SELECT SUBSTRING_INDEX(GROUP_CONCAT(log_id ORDER BY data_date DESC,modify_time DESC),',',1) log_id
FROM t_task_exec_log
WHERE exec_status = 'success'
GROUP BY task_id
) t INNER JOIN t_task_exec_log t2 ON t.log_id = t2.log_id;
一定要去执行,你会发现大惊喜!
3、多和同事沟通,多和需求方沟通
多和同事沟通,集思广益,说不定就找到合适的解决方案了
多和需求方沟通,多谈谈个人的见解,也许需求改动一丢丢,但我们实现却容易很多
4、留疑
1、分组后如何取前 N 条
2、分组后如何取倒数 N 条
记一次有意思的 SQL 实现 → 分组后取每组的第一条记录的更多相关文章
- SQL获取分组后取某字段最大一条记录(求每个类别中最大的值的列表)
获取分组后取某字段最大一条记录 方法一:(效率最高) select * from test as a where typeindex = (select max(b.typeindex) from t ...
- MySQL 分组后取每组前N条数据
与oracle的 rownumber() over(partition by xxx order by xxx )语句类似,即:对表分组后排序 创建测试emp表 1 2 3 4 5 6 7 8 9 ...
- sql语句 查询分组后的每组的第一条或最后一条数据
select * from(select row_number() over(partition by IDCARD order by DATATM desc) as rownum,* from (S ...
- [mysql] 先按某字段分组再取每组中前N条记录
From: http://blog.chinaunix.net/uid-26729093-id-4294287.html 请参考:http://bbs.csdn.net/topics/33002126 ...
- SQL Server 分组后取Top N
SQL Server 分组后取Top N(转) 近日,工作中突遇一需求:将一数据表分组,而后取出每组内按一定规则排列的前N条数据.乍想来,这本是寻常查询,无甚难处.可提笔写来,终究是困住了笔者好一会儿 ...
- sql 分组后按时间降序排列再取出每组的第一条记录
原文:sql 分组后按时间降序排列再取出每组的第一条记录 竞价记录表: Aid 为竞拍车辆ID,uid为参与竞价人员ID,BidTime为参与竞拍时间 查询出表中某人参与的所有车辆的最新的一条的竞价记 ...
- SQL分组排序后取每组最新一条数据的另一种思路
在hibernate框架和mysql.oracle两种数据库兼容的项目中实现查询每个id最新更新的一条数据. 之前工作中一直用的mybatis+oracle数据库这种,一般写这类分组排序取每组最新一条 ...
- MSSQL 分组后取每组第一条(group by order by)
查询中经常遇到这种查询,分组后取每组第一条.分享下一个SQL语句: --根据 x 分组后.根据 y 排序后取第一条 select * from ( select ROW_NUMBER() over(p ...
- mysql单列去重复group by分组取每组前几条记录加order by排序
mysql分组取每组前几条记录(排名) 附group by与order by的研究,需要的朋友可以参考下 --按某一字段分组取最大(小)值所在行的数据 复制代码代码如下: /* 数据如下: name ...
随机推荐
- 「Java分享客栈」Nacos配置中心称王称霸,我Apollo一生也不弱于人!
前言 Apollo又称阿波罗配置中心,在前两年还是挺火的,但阿里SpringCloud套件席卷国内之后,nacos就成为了最被亲睐的分布式配置中心,nacos是配置中心和注册中心二合一的产品,单纯功能 ...
- django框架8
内容概要 ajax简介 前后端传输数据编码格式 ajax发送json格式数据 ajax携带文件数据 回调机制处理策略 内容详情 ajax简介 页面不刷新的情况下可以与后端进行数据交互 异步提交 局部刷 ...
- 更换conda镜像源、pip镜像源
镜像源一般有两点特别需要注意,一个是Conda源,一个是Pip源: 更换Conda源,以更换清华Conda源为例: Anaconda 镜像使用帮助 Anaconda 是一个用于科学计算的 Python ...
- 高性能 Jsonpath 框架,Snack3 3.2.29 发布
Snack3,一个高性能的 JsonPath 框架 借鉴了 Javascript 所有变量由 var 申明,及 Xml dom 一切都是 Node 的设计.其下一切数据都以ONode表示,ONode也 ...
- 毕设着急了吧?Python股票数据分析,制作动态柱状图
写在前面的一些屁话: 雪球成立于 2010 年,是北京雪球信息科技有限公司旗下推出的投资者社区.雪球一直致力于为中国投资者提供跨市场(沪深.香港.美国),跨品种(股票.基金.债券等)的数据查询.资讯获 ...
- 论文解读(AGC)《Attributed Graph Clustering via Adaptive Graph Convolution》
论文信息 论文标题:Attributed Graph Clustering via Adaptive Graph Convolution论文作者:Xiaotong Zhang, Han Liu, Qi ...
- Spring Boot:整合knife4j
前言 这玩意就swagger的升级版,但是用起来比swagger舒服些,界面也看着好看. knife4j 是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger- ...
- TypeScript let与var的区别
1.作用域不同 用var声明的变量,只有函数作用域和全局作用域,没有块级作用域.而let可以实现块级作用域,只能在代码块{}内有效,在{}之外不能访问,如下代码所示: { let a = 0; var ...
- cmd中常用的dos命令
在电脑中除了我们常见的图形界面之外,图形页面的操作相信都会.那么还有在cmd执行的一些dos命令,可以简单记一下,方便日后复习所用 首先打开cmd窗口,windows+R,然后在对话框输入cmd,进入 ...
- P3480 [POI2009]KAM-Pebbles 题解
题目链接 首先,这道题看上去就是个博弈论,很显然的 \(Nim\) 游戏. 因为每一个的取法都和它的上一位有关. 有一种非常显然的转换方式 :我们把这若干堆石子从前向后做一个差分 . 我们记 \(a_ ...