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