SQL查询深度解析:获取上周完课机构统计

前言

看过笔者之前文章的读者应该知道,由于"降本增效",承担了运维工作,其实还承担了一部分BI工作,只是一直 没啥新需求,所以一直没有写过BI相关的文章,这次正好有个需求,正好可以写一篇文章,记录一下。

需求

需求是这样的,需要获取上周完课机构统计,包括上周日期、机构数量、总时长。 其中,机构数量是指上周机构有过课程的机构数量,总时长是指上周机构有过课程的机构的总时长。

思路

这个需求比较简单,只需要使用SQL查询即可实现。

实现

首先,我们需要获取上周的日期范围,这里我们使用MySQL的DATE_SUB函数,获取上周的日期范围。

vbscript 复制代码
SELECT DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 7 DAY) AS start_date, DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 1 DAY) AS end_date;

执行这条sql ,可以得到上周的日期范围,如下所示:

diff 复制代码
+------------+------------+
| start_date | end_date   |
+------------+------------+
| 2025-04-14 | 2025-04-20 |
+------------+------------+

我执行这条sql时,今天是2025-04-23 ,所以上周的日期范围是2025-04-142025-04-20

解释一下:

  • NOW(): 返回当前日期和时间

  • DATE(NOW()): 提取日期部分,去掉时间

  • WEEKDAY(NOW()): 返回当前日期的星期索引(0=周一, 1=周二,...,6=周日)

  • DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 7 DAY) :获取上周的起始日期

  • DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 1 DAY) :获取上周的结束日期

  • DATE_SUB函数

    • 基本语法: DATE_SUB(date, INTERVAL expr unit) 从指定日期减去一个时间间隔值
    • date: 要减去时间的日期
    • INTERVAL: 关键字告诉数据库如何执行日期操作
    • expr: 要减去的时间量
    • unit: 时间单位,例如DAY, HOUR, MINUTE, SECOND
    • 示例: DATE_SUB('2025-04-23', INTERVAL 7 DAY) ,返回2025-04-16

计算逻辑:

  • 上周一 = 今天日期 - (今天星期索引 + 7天)
  • 上周日 = 今天日期 - (今天星期索引 + 1天)

例如,如果今天是周三(索引2):

  • 上周一 = 今天 - (2 + 7) = 今天 -9天
  • 上周日 = 今天 - (2 + 1) = 今天 -3天

然后,我们需要获取上周机构有过课程的机构数量,这里我们使用MySQL的DISTINCT函数,获取上周机构有过课程的机构数量。

sql 复制代码
SELECT
    DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 7 DAY) AS start_date,
    DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 1 DAY) AS end_date,
    COUNT(DISTINCT ou.name) AS 机构数量
FROM klasses kl
         INNER JOIN organization_users ou ON kl.organization_id = ou.id
WHERE start_date >= DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 7 DAY)
  AND start_date <= DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 1 DAY);

执行这条sql ,可以得到上周机构有过课程的机构数量,如下所示:

diff 复制代码
+------------+------------+--------+
| start_date | end_date   | 机构数量 |
+------------+------------+--------+
| 2025-04-14 | 2025-04-20 | 100    |
+------------+------------+--------+

解释一下:

  • COUNT(DISTINCT ou.name): 计算不同ou.name的数量
  • DISTINCT: 关键字告诉数据库只计算不同的值
  • INNER JOIN: 关键字告诉数据库如何连接两个表

使用了INNER JOIN,这意味着:

结果集只包含两表中匹配的记录,只有当kl表的organization_id在ou表中存在对应id时,记录才会出现在结果中.

与其它JOIN类型的区别:

  • LEFT JOIN: 返回左表的所有记录,即使右表没有匹配的记录
  • RIGHT JOIN: 返回右表的所有记录,即使左表没有匹配的记录
  • FULL JOIN: 返回左表和右表的所有记录,即使没有匹配的记录

在这个场景下使用INNER JOIN是合适的,因为我们只关心有对应机构信息的课程记录。

最后,我们需要获取上周机构有过课程的机构的总时长,这里我们使用MySQL的SUM函数,获取上周机构有过课程的机构的总时长。

less 复制代码
CONCAT(
    FLOOR(SUM(kl.end_unix - kl.start_unix) / 3600), '小时',
    FLOOR((SUM(kl.end_unix - kl.start_unix) % 3600 / 60)), '分钟',
    (SUM(kl.end_unix - kl.start_unix) % 60), '秒'
) AS 总时长

解释一下:

  • end_unix - start_unix: 计算每节课的秒数差值

  • SUM(kl.end_unix - kl.start_unix): 计算总时长的秒数

  • FLOOR(SUM(kl.end_unix - kl.start_unix) / 3600): 计算小时数

    • 基本语法: FLOOR(expr) 向下取整
    • expr: 要取整的数字
    • 示例: FLOOR(10.5) ,返回10
  • FLOOR((SUM(kl.end_unix - kl.start_unix) % 3600 / 60)): 计算分钟数

  • (SUM(kl.end_unix - kl.start_unix) % 60): 计算秒数

  • CONCAT: 关键字告诉数据库如何连接字符串

    • 基本语法: CONCAT(str1, str2, ...) 连接多个字符串
    • str1, str2, ...: 要连接的字符串
    • 示例: CONCAT('Hello', 'World') ,返回HelloWorld

为什么需要FLOOR:

  • 时间转换时确保整数部分:

    • 总秒数/3600可能得到小数小时(如1.78小时)
    • FLOOR确保只取整数部分(1小时)
  • 与取模运算配合:

    • % 3600 先得到不足1小时的剩余秒数
    • / 60 转换为分钟时同样需要取整

对比其他取整函数:

  • CEILING():向上取整
  • ROUND():四舍五入
  • TRUNCATE():截断小数

计算逻辑:

  • 总时长 = 总秒数 / 3600 小时 + 总秒数 % 3600 / 60 分钟 + 总秒数 % 60 秒 例如,如果总秒数是10000秒:
  • 总时长 = 10000 / 3600 小时 + 10000 % 3600 / 60 分钟 + 10000 % 60 秒
  • 总时长 = 2小时 + 16分钟 + 40秒

最终实现sql语句是这样的:

sql 复制代码
SELECT
    DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 7 DAY) AS start_date,
    DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 1 DAY) AS end_date,
    COUNT(DISTINCT ou.name) AS 机构数量,
    CONCAT(
        FLOOR(SUM(kl.end_unix - kl.start_unix) / 3600), '小时',
        FLOOR((SUM(kl.end_unix - kl.start_unix) % 3600 / 60)), '分钟',
        (SUM(kl.end_unix - kl.start_unix) % 60), '秒'
    ) AS 总时长
FROM klasses kl
INNER JOIN organization_users ou ON kl.organization_id = ou.id
WHERE start_date >= DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 7 DAY)
  AND start_date <= DATE_SUB(DATE(NOW()), INTERVAL WEEKDAY(NOW()) + 1 DAY);

执行这条sql ,可以得到上周机构有过课程的机构的总时长,如下所示:

diff 复制代码
+------------+------------+--------+------------------+
| start_date | end_date   | 机构数量 | 总时长           |
+------------+------------+--------+------------------+
| 2025-04-14 | 2025-04-20 | 100    | 20小时16分钟0秒   |
+------------+------------+--------+------------------+

总结

这个需求比较简单,只需要使用SQL查询即可实现。但笔者对一些函数不是很清晰,所以写了这篇文章,记录一下。 希望对大家有所帮助。

这个SQL查询展示了几个关键技能:

  • 复杂日期计算确定周范围
  • 适当的JOIN类型选择
  • 精确的时间差计算和格式化
  • 聚合函数的使用
相关推荐
Yuhang12 分钟前
数据库无锁变更工具gh-ost的使用
mysql
一个小坑货31 分钟前
Docker 部署 MySQL 数据库
数据库·mysql·docker
考虑考虑2 小时前
explicit_defaults_for_timestamp使用
数据库·后端·mysql
萤火夜3 小时前
MYSQL之基础认识(卸载安装登录, 基本概念)
数据库·mysql
java奋斗者4 小时前
健身房管理系统(springboot+ssm+vue+mysql)含运行文档
spring boot·后端·mysql
声声codeGrandMaster6 小时前
django之数据的翻页和搜索功能
数据库·后端·python·mysql·django
爱码驱动6 小时前
MySQL快速入门篇---库的操作
数据库·mysql
HackerKevn7 小时前
【上海大学数据库原理实验报告】MySQL数据库的C/S模式部署
数据库·mysql
神仙别闹7 小时前
基于PHP+MySQL实现(Web)单词助手网站
数据库·mysql