前言
最近有一个需求是这样的,需要统计老师在教室内的实际时长,目前,客户端会每隔1分钟上报一次状态,并将上报数据存入数据库中。 我们需要计算在特定时间区间内的有效持续时间。本文将深入分析一个复杂但高效的SQL解决方案,该方案可以计算在指定时间段内的有效在线时长。
核心SQL解析
sql
SELECT COALESCE(
GREATEST(UNIX_TIMESTAMP(MIN(created_at)) - UNIX_TIMESTAMP(#{startTime}), 0) +
COALESCE(
SUM(UNIX_TIMESTAMP(next_time) - UNIX_TIMESTAMP(created_at)), 0
) +
GREATEST(UNIX_TIMESTAMP(#{endTime}) - UNIX_TIMESTAMP(MAX(created_at)), 0
, 0) AS total_seconds
FROM (
SELECT
n1.created_at,
(
SELECT MIN(n2.created_at)
FROM network_stats n2
WHERE n2.klass_id = n1.klass_id
AND n2.server = n1.server
AND n2.created_at > n1.created_at
) AS next_time
FROM network_stats n1
WHERE n1.klass_id = #{klassId}
AND n1.server = #{server}
AND n1.created_at >= #{startTime}
) t
关键技术点
时间区间处理的三段式模型
-
起始段处理:
GREATEST(UNIX_TIMESTAMP(MIN(created_at)) - UNIX_TIMESTAMP(#{startTime}), 0)
- 计算第一条记录与查询开始时间的时间差
GREATEST
可以确保结果不为负
-
中间段处理:
SUM(UNIX_TIMESTAMP(next_time) - UNIX_TIMESTAMP(created_at))
- 计算所有相邻记录之间的时间差总和
- 统计系统正常运行的累计时长
-
结束段处理:
GREATEST(UNIX_TIMESTAMP(#{endTime}) - UNIX_TIMESTAMP(MAX(created_at)), 0)
- 计算最后一条记录与查询结束时间的时间差
- 同样使用
GREATEST
避免负值
子查询与自连接技巧
ini
SELECT MIN(n2.created_at)
FROM network_stats n2
WHERE n2.klass_id = n1.klass_id
AND n2.server = n1.server
AND n2.created_at > n1.created_at
这段sql在整段sql中属于内部子查询,使用了巧妙的自我引用技术
实现效果:
- 对每条记录n1,查找同一klass_id和server的下一条记录n2
- 构建了记录链,使每条记录都知道自己的"下一跳"时间
防御性编程思想
SQL中多处体现了防御性编程:
COALESCE
处理可能的NULL值GREATEST
防止时间计算出现负数
COALESCE函数:数据安全的守护者
定义与语法:
scss
COALESCE(expression1, expression2, ..., expressionN)
核心作用:
- 按顺序检查参数,返回第一个非NULL的值
- 相当于其他编程语言中的"空值合并运算符"
在本SQL中的应用:
scss
COALESCE(SUM(UNIX_TIMESTAMP(next_time) - UNIX_TIMESTAMP(created_at)), 0)
- 当SUM结果为NULL时(无符合条件的数据),返回0
- 避免NULL参与后续计算导致整个表达式结果为NULL
实际业务意义:
- 确保没有数据记录时仍能返回合理的0值
- 防止前端应用因收到NULL而崩溃
GREATEST函数:智能的边界守卫
定义与语法:
scss
GREATEST(value1, value2, ..., valueN)
核心作用:
- 返回参数列表中的最大值
- 常用于确保数值不低于某个阈值
在本SQL中的应用:
scss
GREATEST(UNIX_TIMESTAMP(MIN(created_at)) - UNIX_TIMESTAMP(#{startTime}), 0)
- 计算第一条记录与查询开始时间的时间差
- 当MIN(created_at)早于startTime时,避免出现负值
实际业务意义:
- 保证时间差值不会为负,符合业务逻辑(时长不能为负)
- 处理数据早于查询时间范围的边界情况
延伸应用场景
这种计算模式还可以适用于:
- 服务可用性监控 :计算服务器在特定时段内的实际在线时长
- 用户行为分析 :统计用户在APP中的有效停留时间
- 设备监控 :计算设备在线时长用于计费或维护
注意
索引优化: 确保(klass_id, server, created_at)
有复合索引
最后
对了,我们的项目中使用的是MySQL 5.7
, MySQL 8.0
应该可以使用窗口函数LEAD
来实现。