背景介绍
最近做了一个阅读记录的系统,打算统计一下各个时段的阅读时长展示在报表上,所以思考如何仅通过SQL实现数据统计。
此处我将一天24小时分为24个时段,如[12:00:00,13:00:00)
的左闭右开区间为一个时段,记为12:00:00
。
阅读记录表
表名:dwd_book_read_record
表结构
字段名 | 数据类型 | 备注 |
---|---|---|
id | int | 自增id |
book_name | varchar | 书名(与书单书名对齐) |
page_start | float | 阅读开始的页码(使用浮点数兼容百分比进度,不小于0) |
page_end | float | 阅读结束的页码(使用浮点数兼容百分比进度,不大于图书总页码) |
datetime_start | datetime | 阅读开始的时间 |
datetime_end | datetime | 阅读结束的时间(取消或结束的时间) |
read_time | int | 阅读时长(单位分钟,不小于0,不大于开始、结束时间之差,可人为指定) |
status | varchar | 阅读状态(reading、cancel、end,全表只能有一条reading记录) |
is_finish | int | 是否读完(1:读完,0:未读完。当结束页码等于图书总页码时为1) |
数据样例
id | book_name | page_start | page_end | datetime_start | datetime_end | read_time | status | is_finish |
---|---|---|---|---|---|---|---|---|
1 | 西线无战事 | 0 | 12 | 2022-10-13 13:28:26 | 2022-10-13 13:51:26 | 23 | end | 0 |
6 | 只差一个谎言 | 36 | 83 | 2022-10-08 18:46:22 | 2022-10-08 19:22:22 | 36 | end | 0 |
71 | 夜晚的潜水艇 | 35 | 52 | 2022-01-27 23:59:38 | 2022-01-28 00:28:38 | 29 | end | 0 |
思路分析
我个人的阅读时长通常不会超过三小时,由于阅读记录中只记录了阅读的开始时间和结束时间,即使是阅读时长小于一个小时,由于开始时间的不同也可能导致两个时间点分别位于不同的时段内,所以直接根据一条记录的两个时间点并不能直接得到各个时段的阅读时长。需要将该记录覆盖的时间范围切割成多条记录,分别计算对应各个时段的阅读时长。
数据膨胀
我这里采用的方法是通过非等值连接将阅读记录所覆盖到的时段左连接上去,将一条记录膨胀为多条,额外增加一个对应时段的字段。
首先是要计算开始时间和结束时间所对应的时段,直接使用时间格式化函数即刻:
1 | DATE_FORMAT(datetime_start,'%H:00:00') AS time_start_stage |
接下来为了实现膨胀需要一张包含24个时段的辅助表,该表可以提前准备好,我这里采用临时生成的方式。
time_stage |
---|
00:00:00 |
01:00:00 |
… |
23:00:00 |
最后通过非等值左连接的方式即可对原始记录进行膨胀:
1 | t2.time_start_stage <= t1.time_stage AND t2.time_end_stage >= t1.time_stage |
这里还涉及一个零点问题不适用上面的判断条件,需要单独判断处理,之后会进行讨论。
分段计算
现在对与一条记录的开始时间,结束时间以及时段分别会产生四种情况,如图1所示,最多可以将所覆盖的时间范围划分为三段进行处理。
1 | CASE |
零点问题
由于我喜欢在睡觉前读书,所以会存在跨越零点的阅读记录,该类记录不管是非等值连接也好,还是分段计算也好,都不适用于上面的通用判断条件,需要单独写处理条件。
对于跨零点数据的非等值连接条件为:
1 | time_start_stage>time_end_stage |
对于跨零点数据的分段计算接条件为:
1 | WHEN time_end_stage<time_start_stage THEN ( -- 单独处理跨零点记录 |
注意每一个条件的先后顺序
完整的代码实现
1 | with t1 AS ( -- 手动构建时段辅助表 |
效果展示
time_start | time_end | time_start_stage | time_end_stage | time_stage | read_time_sec |
---|---|---|---|---|---|
13:28:26 | 13:51:26 | 13:00:00 | 13:00:00 | 13:00:00 | 1380 |
12:58:04 | 13:33:04 | 12:00:00 | 13:00:00 | 13:00:00 | 1984 |
12:58:04 | 13:33:04 | 12:00:00 | 13:00:00 | 12:00:00 | 116 |
23:59:38 | 00:28:38 | 23:00:00 | 00:00:00 | 23:00:00 | 22 |
23:59:38 | 00:28:38 | 23:00:00 | 00:00:00 | 00:00:00 | 1718 |