前言
看过笔者之前文章的读者应该知道,由于"降本增效",承担了运维工作,其实还承担了一部分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-14到2025-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类型选择
- 精确的时间差计算和格式化
- 聚合函数的使用