How to make calculation on time intervals?

后端 未结 5 683
逝去的感伤
逝去的感伤 2021-01-07 23:09

I have a problem ,i solve it but i have written a long procedure and i can\'t be sure that it covers all the possible cases .

The problem:

If i have a

5条回答
  •  醉梦人生
    2021-01-07 23:53

    I've updated my answer with your data example and I'm adding another example for an employee 248 that uses case 2 and 5 from your graph.

    --load example data for emply 547
    select CONVERT(int, 547) emp_num, 
        Convert(datetime, '2015-4-1') day_date, 
        Convert(datetime, '2015-4-1 08:00') work_st,
        Convert(datetime, '2015-4-1 16:00') work_end, 
        Convert(datetime, '2015-4-1 07:45') check_in, 
        Convert(datetime, '2015-4-1 12:10') check_out, 
        'W' day_state
    into #SecondaryIntervals
    insert into #SecondaryIntervals select 547, '2015-4-1', '2015-4-1 08:00', '2015-4-1 16:00', '2015-4-1 12:45', '2015-4-1 17:24', 'W'
    insert into #SecondaryIntervals select 547, '2015-4-2', '2015-4-2 00:00', '2015-4-2 00:00', '2015-4-2 07:11', '2015-4-2 13:11', 'E'
    
    select CONVERT(int, 547) emp_num, 
        Convert(datetime, '2015-4-1') day_date, 
        Convert(datetime, '2015-4-1 15:00') mission_in,
        Convert(datetime, '2015-4-1 21:30') mission_out
    into #MainIntervals
    insert into #MainIntervals select 547, '2015-4-2', '2015-4-2 8:00', '2015-4-2 14:00'
    
    --load more example data for an employee 548 with overlapping secondary intervals
    insert into #SecondaryIntervals select 548, '2015-4-1', '2015-4-1 06:00', '2015-4-1 11:00', '2015-4-1 9:00', '2015-4-1 10:00', 'W'
    insert into #SecondaryIntervals select 548, '2015-4-1', '2015-4-1 06:00', '2015-4-1 11:00', '2015-4-1 10:30', '2015-4-1 12:30', 'W'
    insert into #SecondaryIntervals select 548, '2015-4-1', '2015-4-1 06:00', '2015-4-1 11:00', '2015-4-1 13:15', '2015-4-1 16:00', 'W'
    
    insert into #MainIntervals select 548, '2015-4-1', '2015-4-1 8:00', '2015-4-1 14:00'
    
    --Populate your Offline table with the intervals in #SecondaryIntervals
    select 
        ROW_NUMBER() over (Order by emp_num, day_date, StartDateTime, EndDateTime) Rownum,
        emp_num,
        day_date,
        StartDateTime, 
        EndDateTime
    into #Offline
    from 
        (select emp_num,
            day_date,
            work_st StartDateTime, 
            work_end EndDateTime
        from #SecondaryIntervals
        where day_state = 'W'
        Group by emp_num,
            day_date,
            work_st, 
            work_end
        union
        select 
            emp_num,
            day_date,
            check_in StartDateTime, 
            check_out EndDateTime
        from #SecondaryIntervals
        Group by emp_num,
            day_date,
            check_in, 
            check_out
        ) SecondaryIntervals
    
    
    --Populate your Online table
    select 
        ROW_NUMBER() over (Order by emp_num, day_date, mission_in, mission_out) Rownum,
        emp_num,
        day_date,
        mission_in StartDateTime, 
        mission_out EndDateTime
    into #Online
    from #MainIntervals
    group by emp_num,
        day_date,
        mission_in,
        mission_out
    
    
    -------------------------------
    --find overlaping offline times
    -------------------------------
    declare @Finished as tinyint
    set @Finished = 0
    
    while @Finished = 0
    Begin
        update #Offline
        set #Offline.EndDateTime = OverlapEndDates.EndDateTime
        from #Offline
        join
            (
            select #Offline.Rownum,
                MAX(Overlap.EndDateTime) EndDateTime
            from #Offline
            join #Offline Overlap
            on #Offline.emp_num = Overlap.emp_num
                and #Offline.day_date = Overlap.day_date
                and Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime
                and #Offline.Rownum <= Overlap.Rownum
            group by #Offline.Rownum
            ) OverlapEndDates
        on #Offline.Rownum = OverlapEndDates.Rownum
    
        --Remove Online times completely inside of online times
        delete #Offline
        from #Offline
        join #Offline Overlap
        on #Offline.emp_num = Overlap.emp_num
            and #Offline.day_date = Overlap.day_date
            and #Offline.StartDateTime between Overlap.StartDateTime and Overlap.EndDateTime
            and #Offline.EndDateTime between Overlap.StartDateTime and Overlap.EndDateTime
            and #Offline.Rownum > Overlap.Rownum
    
        --LOOK IF THERE ARE ANY MORE CHAINS LEFT    
        IF NOT EXISTS(
                select #Offline.Rownum,
                    MAX(Overlap.EndDateTime) EndDateTime
                from #Offline
                join #Offline Overlap
                on #Offline.emp_num = Overlap.emp_num
                    and #Offline.day_date = Overlap.day_date
                    and Overlap.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime
                    and #Offline.Rownum < Overlap.Rownum
                group by #Offline.Rownum
                )
            SET @Finished = 1
    END
    
    -------------------------------
    --Modify Online times with offline ranges
    -------------------------------
    
    --delete any Online times completely inside offline range
    delete #Online 
    from #Online
    join #Offline
    on #Online.emp_num = #Offline.emp_num
        and #Online.day_date = #Offline.day_date
        and #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime
        and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime
    
    --Find Online Times with offline range at the beginning
    update #Online
    set #Online.StartDateTime = #Offline.EndDateTime
    from #Online
    join #Offline
    on #Online.emp_num = #Offline.emp_num
        and #Online.day_date = #Offline.day_date
        and #Online.StartDateTime between #Offline.StartDateTime and #Offline.EndDateTime
        and #Online.EndDateTime >= #Offline.EndDateTime
    
    --Find Online Times with offline range at the end
    update #Online
    set #Online.EndDateTime = #Offline.StartDateTime
    from #Online
    join #Offline
    on #Online.emp_num = #Offline.emp_num
        and #Online.day_date = #Offline.day_date
        and #Online.StartDateTime <= #Offline.StartDateTime
        and #Online.EndDateTime between #Offline.StartDateTime and #Offline.EndDateTime
    
    --Find Online Times with offline range punched in the middle
    select #Online.Rownum, 
        #Offline.Rownum OfflineRow,
        #Offline.StartDateTime,
        #Offline.EndDateTime,
        ROW_NUMBER() over (Partition by #Online.Rownum order by #Offline.Rownum Desc) OfflineHoleNumber
    into #OfflineHoles
    from #Online
    join #Offline
    on #Online.emp_num = #Offline.emp_num
        and #Online.day_date = #Offline.day_date
        and #Offline.StartDateTime between #Online.StartDateTime and #Online.EndDateTime
        and #Offline.EndDateTime between #Online.StartDateTime and #Online.EndDateTime
    
    declare @HoleNumber as integer
    select @HoleNumber = isnull(MAX(OfflineHoleNumber),0) from #OfflineHoles
    
    --Punch the holes out of the online times
    While @HoleNumber > 0
    Begin
        insert into #Online 
        select
            -1 Rownum,
            #Online.emp_num,
            #Online.day_date,
            #OfflineHoles.EndDateTime StartDateTime,
            #Online.EndDateTime EndDateTime
        from #Online
        join #OfflineHoles
        on #Online.Rownum = #OfflineHoles.Rownum
        where OfflineHoleNumber = @HoleNumber
    
        update #Online
        set #Online.EndDateTime = #OfflineHoles.StartDateTime
        from #Online
        join #OfflineHoles
        on #Online.Rownum = #OfflineHoles.Rownum
        where OfflineHoleNumber = @HoleNumber
    
        set @HoleNumber=@HoleNumber-1
    end
    
    --Output total hours
    select emp_num, day_date, 
        SUM(datediff(second,StartDateTime, EndDateTime)) / 3600.0 TotalHr, 
        SUM(datediff(second,StartDateTime, EndDateTime)) / 60.0 TotalMin
    from #Online
    group by emp_num, day_date
    order by 1, 2
    
    --see how it split up the online intervals
    select emp_num, day_date, StartDateTime, EndDateTime
    from #Online
    order by 1, 2, 3, 4
    

    Output is:

    emp_num     day_date                TotalHr                                 TotalMin
    ----------- ----------------------- --------------------------------------- ---------------------------------------
    547         2015-04-01 00:00:00.000 4.100000                                246.000000
    547         2015-04-02 00:00:00.000 0.816666                                49.000000
    548         2015-04-01 00:00:00.000 0.750000                                45.000000
    
    (3 row(s) affected)
    
    emp_num     day_date                StartDateTime           EndDateTime
    ----------- ----------------------- ----------------------- -----------------------
    547         2015-04-01 00:00:00.000 2015-04-01 17:24:00.000 2015-04-01 21:30:00.000
    547         2015-04-02 00:00:00.000 2015-04-02 13:11:00.000 2015-04-02 14:00:00.000
    548         2015-04-01 00:00:00.000 2015-04-01 12:30:00.000 2015-04-01 13:15:00.000
    
    (3 row(s) affected)
    

    I left my other answer posted because it's more generic in case someone else wants to snag it. I see you added a bounty to this question. Let me know if there's something specific about my answer that doesn't satisfy you and I'll try to help you out. I process thousands of intervals with this method and it returns in just a few seconds.

提交回复
热议问题