• 记一次有意思的 SQL 实现 → 分组后取每组的第一条记录


    🚀 优质资源分享 🚀

    学习路线指引(点击解锁)知识定位人群定位
    🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
    💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

    开心一刻

    今天,朋友气冲冲的走到我面前

    朋友:我不是谈了个女朋友,谈了三个月嘛,昨天我偷看她手机,你猜她给我备注什么

    我:备注什么?

    朋友:舔狗 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='任务执行日志';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    View Code
      它们关系如下

    一个业务下有多个任务,一个任务又可以属于不同的业务;同个业务下,一个任务最多关联一次

    任务每执行一次就会生成一条执行日志;执行日志的数据日期  小于等于 任务执行的当前日期,比如昨天执行的任务的数据日期可以是前天的

    四张表的数据量分别如下

    需求

    按业务分页,每个业务可以展开显示关联的任务信息以及任务最新的执行成功信息

    任务最新的执行成功信息:状态成功,数据日期最大的那条执行日志信息;如果数据日期一致,则取最终修改时间最大的

    后端返回的 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)

    是不是知识盲区,是不是有点东西?

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zWUF2y14-1661143272709)(https://img2022.cnblogs.com/blog/747662/202208/747662-20220821170129198-1283832773.gif)]

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    View Code

    一定要去执行,你会发现大惊喜

    3、多和同事沟通,多和需求方沟通

    多和同事沟通,集思广益,说不定就找到合适的解决方案了

    多和需求方沟通,多谈谈个人的见解,也许需求改动一丢丢,但我们实现却容易很多

    4、留疑

    1、分组后如何取前 N 条

    2、分组后如何取倒数 N 条

  • 相关阅读:
    OpenFlow协议原理及基本配置-网络测试仪实操
    【7】Spring Boot 3 集成组件:缓存组件 spring cache + spring data redis
    JS中的set集合和map映射
    Leetcode21:合并两个有效链表
    边际图和组合折线图
    anaconda使用系列教程--2)conda命令选项
    ubuntu16.04上安装gstreamer
    JDBC增删改查练习案例
    【老生谈算法】matlab实现DCCA算法源码——DCCA算法
    数据结构—查找方法改进流程
  • 原文地址:https://blog.csdn.net/m0_56069948/article/details/126463605