不计算时间的总和范围重叠两次

时间:2019-02-06 01:22:40

标签: mysql sql algorithm datetime mariadb

对于给定的用户ID“ 1”和给定的日期2018年1月2日,我想计算记录的总小时数,其中可能存在重叠。

为此子集计算:

+-----+---------------------+---------------------+
| uid | time_start          | time_end            |
+-----+---------------------+---------------------+
|   1 | 2018-01-02 04:00:00 | 2018-01-02 04:30:00 |
|   1 | 2018-01-02 04:25:00 | 2018-01-02 04:35:00 |
|   1 | 2018-01-02 04:55:00 | 2018-01-02 05:15:00 |
+-----+---------------------+---------------------+

结果时间应为: 00:55

3 个答案:

答案 0 :(得分:2)

MariaDB 10.3具有窗口功能和CTE,因此您可以使用这些功能来生成结果。 CTE通过将当天的当前time_start与当天的最大time_end进行比较,并取其最大值(最大),然后简单地查询SUM,从会话时间中删除重叠部分每个会话时间,按用户ID和日期分组。请注意,如果一个会话完全被另一个会话所重叠,则CTE会将startend的时间都设置为重叠会话的end的时间,因此有效会话长度为0。已将我的演示扩展到包括这种情况以及多个重叠的会话:

WITH sessions AS 
    (SELECT uid,
            GREATEST(time_start, COALESCE(MAX(time_end) OVER (PARTITION BY DATE(time_start) ORDER BY time_start ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING), '2000-01-01')) AS start,
            MAX(time_end) OVER (PARTITION BY DATE(time_start) ORDER BY time_start ROWS UNBOUNDED PRECEDING)  AS end
            FROM sessions)
SELECT uid, DATE(start) AS `date`, SEC_TO_TIME(SUM(TO_SECONDS(end) - TO_SECONDS(start))) AS totaltime
FROM sessions
GROUP BY uid, `date`

输出:

uid     date        totaltime
1       2018-01-02  00:55:00
1       2018-01-03  01:00:00
1       2018-01-04  01:15:00

Demo on dbfiddle

答案 1 :(得分:0)

这是一种空白和岛屿问题。在MySQL中这确实是一个痛苦,但我认为您可以使用变量来做到这一点。

想法是遍历记录并记下新的开始与以前的“岛”重叠的时间。它成为下一个岛屿的起点。然后,您可以汇总并获得每个岛屿的持续时间:

ansible 2.1.0.0

您可以将其用作子查询来加总差异。

答案 2 :(得分:0)

这是一个很棒且令人愉快的练习。

因此,这里的窍门如下:

  1. 此人从上次会话注销之前再次登录,并在第一次会话后结束会话;或
  2. 此人从上次会话注销之前再次登录,并在结束第一次会话之前结束了会话

因此,解决问题的技巧就是为新会话分配一个开始时间,该时间等于其上一个会话的结束时间。在这种情况下,您可以将每个会话排成一行,并且可以计算时差。好,让我们模拟一下这个例子 :

  create table #temp (userId int, timeComienza datetime, timeTermina dateTime )

-- exemplo de overlap
  insert into #temp values (1, '20180102 16:00', '20180102 16:30')
  insert into #temp values (1, '20180102 16:25', '20180102 16:35')
  insert into #temp values (1, '20180102 16:55', '20180102 17:15')
-- ejemplo de no overlap
  insert into #temp values (2, '20180102 16:00', '20180102 16:30')
  insert into #temp values (2, '20180102 16:35', '20180102 16:50')
  insert into #temp values (2, '20180102 16:40', '20180102 16:45')


userId  timeComienza    timeTermina
1   2018-01-02 16:00:00.000 2018-01-02 16:30:00.000
1   2018-01-02 16:25:00.000 2018-01-02 16:35:00.000
1   2018-01-02 16:55:00.000 2018-01-02 17:15:00.000
2   2018-01-02 16:00:00.000 2018-01-02 16:30:00.000
2   2018-01-02 16:35:00.000 2018-01-02 16:50:00.000
2   2018-01-02 16:40:00.000 2018-01-02 16:45:00.000

您可以看到,用户1在结束其第一会话之前遭受了第二会话的登录,用户2在其第三会话中遭受了几乎相同的问题,除了他的第三会话在结束其第二会话之前终止(总重叠和他第二届会议的日食。

我们要做的第一件事是使用order by来提供这些会话的顺序。

select *, ROW_NUMBER() over(partition by userId order by timeComienza) as unOrden 
into #temp2 
from #temp 

userId  timeComienza    timeTermina         unOrden
1   2018-01-02 16:00:00.000 2018-01-02 16:30:00.000 1
1   2018-01-02 16:25:00.000 2018-01-02 16:35:00.000 2
1   2018-01-02 16:55:00.000 2018-01-02 17:15:00.000 3
2   2018-01-02 16:00:00.000 2018-01-02 16:30:00.000 1
2   2018-01-02 16:35:00.000 2018-01-02 16:50:00.000 2
2   2018-01-02 16:40:00.000 2018-01-02 16:45:00.000 3

现在,使用我们的迭代将容易100倍。 让我们创建一个与表#2具有相同结构的空表作为我们要插入分析的表。

select * 
into #tablaInsertar
from #temp2

delete from #tablaInsertar

最后,这是我们分析的核心:)

-- variable to iterate users
declare @x int = 1 , @usuarios int = 1, @usuariosMax int
--num dif de usuarios:
select @usuariosMax = count(distinct(userId)) from #temp2 


while(@usuarios <= @usuariosMax)
begin

/*trabajando cada usuario*/
    /*Primero necesitamos saber la longitud de cada Usuario*/
    declare @trabajaUsuario int = 1, @longUsuario int

    --obtiene longitud usuario
    select @longUsuario = count(1) from #temp
    where userId = @usuarios

    while(@trabajaUsuario <= @longUsuario)
    begin 

        if(@trabajaUsuario = 1)
        begin 

            insert into #tablaInsertar
            select 
                *
            from #temp2
            where userId = @usuarios and unOrden = @trabajaUsuario

        end 

        else -- dado que no sea la primera fila
        -- comparando horas
        begin 
                declare @horaInicioEstePeriodo dateTime, @horaTerminaAnterior dateTime
                select @horaInicioEstePeriodo = #temp2.timeComienza from #temp2 where userId = @usuarios and unOrden = @trabajaUsuario
                select @horaTerminaAnterior = #temp2.timeTermina from #temp2 where userId = @usuarios and unOrden = @trabajaUsuario - 1

                if(@horaInicioEstePeriodo < @horaTerminaAnterior) -- las modificaciones dado que el periodo inicio sea menro a la hora anterior
                begin 

                    insert into #tablaInsertar
                    select 
                        t2.userId
                        , t1.timeTermina as tiempoComienzaActualizado
                        , t2.timeTermina
                        , t2.unOrden
                    from 
                        (
                            select 
                                #temp2.userId
                                ,#temp2.timeComienza
                                , #temp2.timeTermina
                                , #temp2.unOrden
                            from #temp2
                            where userId = @usuarios and unOrden = @trabajaUsuario - 1
                        )t1
                        join
                        (
                            select 
                                #temp2.userId
                                --, as tiempoComienzaActualizado --#temp2.timeComienza
                                , #temp2.timeTermina
                                , #temp2.unOrden
                            from #temp2
                            where userId = @usuarios and unOrden = @trabajaUsuario
                        ) t2 on t1.userId = t2.userId and t1.unOrden + 1 = t2.unOrden
                end 

                else -- dado que el periodo inicia sea mayor o igual a la hora anterior
                begin 

                    insert into #tablaInsertar
                    select 
                        *
                    from #temp2
                    where userId = @usuarios and unOrden = @trabajaUsuario
                end 

        end 

    select @trabajaUsuario += 1
    end

select @usuarios += 1
end

让我们看看我们的新表:)

select *, DATEDIFF(s,timeComienza,timeTermina) timeInSeconds
from #tablaInsertar

userId  timeComienza    timeTermina unOrden timeInSeconds
1   2018-01-02 16:00:00.000 2018-01-02 16:30:00.000 1   1800
1   2018-01-02 16:30:00.000 2018-01-02 16:35:00.000 2   300
1   2018-01-02 16:55:00.000 2018-01-02 17:15:00.000 3   1200
2   2018-01-02 16:00:00.000 2018-01-02 16:30:00.000 1   1800
2   2018-01-02 16:35:00.000 2018-01-02 16:50:00.000 2   900
2   2018-01-02 16:50:00.000 2018-01-02 16:45:00.000 3   -300

我们可以看到,用户1会话2现在可以正确反映出他实际工作的300秒(5分钟)。对于用户2会话3的问题,我们有一个负数,原因是他正在从会话2中抽出时间。因此,我们要做的就是求和正值,只知道每个用户记录的实时时间,像这样:

select 
    t1.userId,
    sum(case when timeInSeconds > 0 then timeInSeconds else 0 end) totalTimeLogged
from 
(
    select *, DATEDIFF(s,timeComienza,timeTermina) timeInSeconds
    from #tablaInsertar
) t1
group by t1.userId

最终结果:

userId  totalTimeLogged
1           3300
2           2700