问题
I have two SQL Server tables employee(empid, empname)
and employee_movements(empid, MoveDatetime)
which I store employees actions from fingerprint by datetime, I want to count hours every employee spent after work from 03:30pm until 6:00am in the next day
So if I have this data
| EmpID | MoveDateTime | |-------|--------------------| | 1 | 01.01.2020 3:30pm | | 1 | 01.01.2020 5:30pm | | 1 | 01.01.2020 11:00pm | | 1 | 02.01.2020 02:30am | | 2 | 01.01.2020 4:00am | | 2 | 01.01.2020 10:15am | | 1 | 02.01.2020 4:00pm | | 1 | 02.01.2020 5:00pm |
I should get this result
| EmpID | Entrance | Departure | Actual_hours | |-------|--------------------|--------------------|--------------| | 1 | 01.01.2020 3:30PM | 01.01.2020 5:30PM | 2.00 | | 1 | 01.01.2020 11:00PM | 02.01.2020 2:30Am | 3.5 | | 2 | 01.01.2020 4:00AM | 01.01.2020 10:15PM | 7.25 | | 1 | 02.01.2020 04:00am | 02.01.2020 5:00am | 01.00 |
I tried this code
with cte as
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY [EmpID], cast ([MoveDateTime] as Date)
ORDER BY [MoveDateTime]) as rn
FROM employee_movements t1
)
SELECT c1.EmpID, c1.rn,c2.rn,
cast (c1.MoveDateTime as Date) as the_day,c1.MoveDateTime as enterance,c2.MoveDateTime as departure, cast (sum (DATEDIFF (S, c1.MoveDateTime, c2.MoveDateTime)/3600.00) as decimal(18,2)) as Actual_Hours
FROM cte c1
left JOIN cte c2
ON
c1.rn = c2.rn -1 AND
c1.EmpID = c2.EmpID and c1.rn % 2 <> 0
and (cast (c1.MoveDateTime as Date)=cast (c2.MoveDateTime as Date) ) or (c2.rn=1 and cast(dateadd(day,1,c1.MoveDateTime) as date)= cast(c2.MoveDateTime as Date) and (cast(c2.MoveDateTime as time) between '00:00:00' and '06:00:00'))
AND c2.rn % 2 = 0
where c2.rn is not null
GROUP BY c1.EmpID,c1.rn,c2.rn,
cast (c1.MoveDateTime as Date) ,c1.MoveDateTime,c2.MoveDateTime
Which I got from it the in and out of every employee and count of hours, but the problem it is partitioned by one day doesn't return the second case because the employee still in work and left on the next day.
Any help will be appreciated, thanks in advance.
回答1:
As a starter: you can interleave the rows using window functions to generate the in/out pairs:
select *
from (
select empid, movedatetime as dt_in,
lead(movedatetime) over(partition by empid order by movedatetime) as dt_out,
row_number() over(partition by empid order by movedatetime) rn
from employee_movements
) t
where rn % 2 = 1
Then we need to comput the overtime for each date range. SQL Server is not that good at date arithmetics; if you don't have too many rows to proceed at once, the simplest approach might be brute force: enumerate all the minutes between the two dates, the count only those that do not belong to the working hours.
So:
with
data as (
select empid, movedatetime as dt_in,
lead(movedatetime) over(partition by empid order by movedatetime) as dt_out,
row_number() over(partition by empid order by movedatetime) rn
from employee_movements
),
cte as (
select empid, dt_in, dt_out, dt_in as dt
from data
where rn % 2 = 1
union all
select empid, dt_in, dt_out, dateadd(minute, 1, dt)
from cte
where dateadd(minute, 1, dt) < dt_out
)
select empid, dt_in, dt_out,
sum(
case when convert(time, dt) >= '06:00:00' and convert(time, dt) < '15:30:00'
then 0
else 1
end) ot_minutes
from cte
group by empid, dt_in, dt_out
order by empid, dt_in
option (maxrecursion 0)
Demo on DB Fiddlde:
empid | dt_in | dt_out | ot_minutes ----: | :---------------------- | :---------------------- | ---------: 1 | 2020-01-01 15:30:00.000 | 2020-01-01 17:30:00.000 | 120 1 | 2020-01-01 23:00:00.000 | 2020-01-02 02:30:00.000 | 210 1 | 2020-01-02 16:00:00.000 | 2020-01-02 17:00:00.000 | 60 2 | 2020-01-01 04:00:00.000 | 2020-01-01 22:15:00.000 | 525
来源:https://stackoverflow.com/questions/64722173/how-to-sum-overtime-hours-users-spent-on-work