我需要根据如下表格计算花费的总时间:
编号 | 开始时间 | 结束时间 |
期间可以重叠的地方。我只需要计算一次卵泡期。
例如,如果我有这样的时期:
*----A----* *--------C-----* *------------D----------* * - - -是 - -*
总和将是:(A.end-A.start) + (C.end - B.start) + (D.end - D.start)
我对编写此查询应使用的方法有点困惑,我将不胜感激。
我需要根据如下表格计算花费的总时间:
编号 | 开始时间 | 结束时间 |
期间可以重叠的地方。我只需要计算一次卵泡期。
例如,如果我有这样的时期:
*----A----* *--------C-----* *------------D----------* * - - -是 - -*
总和将是:(A.end-A.start) + (C.end - B.start) + (D.end - D.start)
我对编写此查询应使用的方法有点困惑,我将不胜感激。
好的,我认真地坚持你在生产中使用它之前要以各种方式测试它。特别是测试如果在 1 个时间跨度内有 MULTIPLE 重叠会发生什么。
此查询所做的是计算每个时间跨度的持续时间,以及与具有更高 id 的其他时间跨度存在多少重叠。
select
t1.id,
t1.start_time,
t1.end_time,
t1.end_time - t1.start_time as duration,
sum(
if(t2.start_time < t1.start_time and t2.end_time > t1.end_time , t1.end_time - t1.start_time, 0) -- t2 completely around t1
+ if(t2.start_time >= t1.start_time and t2.end_time <= t1.end_time , t2.end_time - t2.start_time, 0) -- t2 completely within t1
+ if(t2.start_time < t1.start_time and t2.end_time > t1.start_time and t2.end_time < t1.end_time , t2.end_time - t1.start_time, 0) -- t2 starts before t1 starts and overlaps partially
+ if(t2.start_time < t1.end_time and t2.end_time > t1.end_time and t2.start_time > t1.start_time, t1.end_time - t2.start_time, 0) -- t2 starts before t1 ends and overlaps partially
) as overlap
from
times t1
left join times t2 on
t2.id > t1.id -- t2.id is greater than t1.id
and (
(t2.start_time < t1.start_time and t2.end_time > t1.end_time ) -- t2 completely around t1
or (t2.start_time >= t1.start_time and t2.end_time <= t1.end_time ) -- t2 completely within t1
or (t2.start_time < t1.start_time and t2.end_time > t1.start_time) -- t2 starts before t1 starts and overlaps
or (t2.start_time < t1.end_time and t2.end_time > t1.end_time ) -- t2 starts before t1 ends and overlaps
)
group by
t1.id
所以你最终想要的是这样的:
select
sum(t.duration) - sum(t.overlap) as filtered_duration
from
(
OTHER QUERY HERE
) as t
所以最后你有这个查询:
select
sum(t.duration) - sum(t.overlap) as filtered_duration
from
(
select
t1.id,
t1.start_time,
t1.end_time,
t1.end_time - t1.start_time as duration,
sum(
if(t2.start_time < t1.start_time and t2.end_time > t1.end_time , t1.end_time - t1.start_time, 0) -- t2 completely around t1
+ if(t2.start_time >= t1.start_time and t2.end_time <= t1.end_time , t2.end_time - t2.start_time, 0) -- t2 completely within t1
+ if(t2.start_time < t1.start_time and t2.end_time > t1.start_time and t2.end_time < t1.end_time , t2.end_time - t1.start_time, 0) -- t2 starts before t1 starts and overlaps partially
+ if(t2.start_time < t1.end_time and t2.end_time > t1.end_time and t2.start_time > t1.start_time, t1.end_time - t2.start_time, 0) -- t2 starts before t1 ends and overlaps partially
) as overlap
from
times t1
left join times t2 on
t2.id > t1.id -- t2.id is greater than t1.id
and (
(t2.start_time < t1.start_time and t2.end_time > t1.end_time ) -- t2 completely around t1
or (t2.start_time >= t1.start_time and t2.end_time <= t1.end_time ) -- t2 completely within t1
or (t2.start_time < t1.start_time and t2.end_time > t1.start_time) -- t2 starts before t1 starts and overlaps
or (t2.start_time < t1.end_time and t2.end_time > t1.end_time ) -- t2 starts before t1 ends and overlaps
)
group by
t1.id
) as t
我想建议另一种方法来获得时间,同时确保结果是正确的。但我不知道,如何用 MySQL 完成这项工作。
我将在接下来的几个小时内重复使用上面的示例 - 甚至可能有一个 3 级条目“F”:
1 3 7 12 13 (15 16) 20
|----A----| |------C-----| |----------D----------|
|-----B-----| |---E---|
5 9 14 17
|F|
查询按时间排序的所有时间戳的组合列表,并添加每个“动作”的类型
SELECT 1 as onoff, start_time as time FROM table
UNION
SELECT -1 as onoff, end_time as time FROM table
ORDER BY time
通过带有临时计数器的循环 (?) 处理列表,该计数器在开始/登录时递增 1,在结束/注销时递减 1
tmp.start=<time>
如果计数器从 0 更改为 1 并更新临时表中的前一行,则计数器应导致脚本将新行添加到临时表tmp.end=<time>
中。表,如果它从 1 变为 0。
该脚本将为上面的示例执行此操作,如下所示:
QUERY TMP TABLE
onoff | time | ctr ID | start | end
1 | 01:00 | 1 1 | 01:00 | (record 1 added, ctr 0->1)
-1 | 03:00 | 0 1 | 01:00 | 03:00 (record 1 updated, ctr 1->0)
1 | 05:00 | 1 2 | 05:00 | (record 2 added, ctr 0->1)
1 | 07:00 | 2 (nothing to do)
-1 | 09:00 | 1 (nothing to do)
-1 | 12:00 | 0 2 | 05:00 | 12:00 (record 2 updated, ctr 1->0)
1 | 13:00 | 1 3 | 13:00 | (record 3 added, ctr 0->1)
1 | 14:00 | 2 (nothing to do)
1 | 15:00 | 3 (nothing to do)
-1 | 16:00 | 2 (nothing to do)
-1 | 17:00 | 1 (nothing to do)
-1 | 20:00 | 0 3 | 13:00 | 20:00 (record 3 updated, ctr 1->0)
最后一步非常简单:在单元中获取timestampdiff()
from start
to end
,您需要/喜欢它并进行任何进一步的过滤或分组。
例如:在别处使用数据
SELECT ID, start, end, timestampdiff(MINUTE, start, end) FROM tmp
或例如:总结每个用户的工作时间/登录时间
SELECT user_id, SUM(timestampdiff(MINUTE, start, end)) FROM tmp GROUP BY user_id
我敢肯定,这将为任何级别的嵌套提供正确的持续时间,但是有人知道如何在 MySQL 中完成此操作吗?我也想用这个。
最好的祝福
PS:脚本也可能“关闭”最后一个会话或抛出错误,如果它以计数器 > 1 结束并抛出错误,如果计数器在任何时候变为 < 0
我为另一个问题写了一个类似的查询,所以我想我会针对这个问题调整它,以防有人感兴趣。
SELECT SUM(a.end_time - a.start_time) total_duration
FROM (
SELECT MIN(g.start_time) start_time, MAX(g.end_time) end_time
FROM (
SELECT @group_id := @group_id + (@end_time IS NULL OR o.start_time > @end_time) group_id,
start_time,
@end_time := CAST(CASE
WHEN (@end_time IS NULL OR o.start_time > @end_time) THEN o.end_time
ELSE GREATEST(o.end_time, @end_time)
END AS DATETIME) end_time
FROM times o
JOIN (SELECT @group_id := 0, @end_time := NULL) init
ORDER BY o.start_time ASC
) g
GROUP BY g.group_id
) a
最里面的查询将您的时间组合在重叠的组中,在适当的情况下延长 end_time。end_time 可以灵活处理完全被前一个包围的时间。
下一个包装查询从每个组中提取完整的时间范围。
外部查询总结了每个组的差异。