Please start any new threads on our new site at https://forums.sqlteam.com. We've got lots of great SQL Server experts to answer whatever question you can come up with.

 All Forums
 General SQL Server Forums
 New to SQL Server Programming
 Calculate hours worked

Author  Topic 

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-13 : 04:09:25
Hi,

I am trying to calculate the hours worked by all employees. The data I have includes all the times the employee came in and out of the building. I need to to know the total hours that the employee was in the building. Here is the query I have to return the result below:

Select Emp.Emp_Name + ' ' + Emp.Emp_Surname As FullName, Time.ClockedTimes, IO.InorOut FROM Employee Emp INNER JOIN Employee_Times Time ON Emp.Emp_ID = Times.Employee_ID INNER JOIN InOrOut IO ON Times.IO_ID = IO.IO_ID

Result:

FullName ClockedTimes InOrOut
Person1 2011-04-01 07:55:00 IN
Person1 2011-04-01 10:00:00 OUT
Person1 2011-04-01 10:35:00 IN
Person1 2011-04-01 17:05:00 OUT

I want to be able to calculate the total hours, so in this case, it should be 2.05hrs + 6.5hrs = 8.55hrs

Can alyone help me solve this. I am a newbie, so please give me as much explanation as possible.

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2011-04-13 : 04:19:27
is the record always in pair of IN and OUT ?

will there be cases of missing record ?

What is the version of SQL Server are you using ?


KH
[spoiler]Time is always against us[/spoiler]

Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-13 : 04:27:10
There will sometimes be missing values of In or Out. If the person clocked in for the day, but did not clock out, we can use a default value of 17:00:00 as the clock out time. If the person clocked out, but did not clock in for the day, we can use the defaukt of 8:00:00.

I am using MS SQL Server 2005.
Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-13 : 06:02:43
I have the following code, which seems to work, but only if the person ensured that they clocked OUT at the end of the day. If there are missing IN or OUT clocks, then the result is incorrect. How do I add default values if there are missing records?

select Pers.Name + ' ' + Pers.surname As FullName ,
convert(varchar(10),RC.ClockTime,111),
sum(case when Dir.Name='IN' then
-1*(datepart(hh,RC.Clocktime)+datepart(mi,RC.Clocktime)/60.0)
else datepart(hh,RC.Clocktime)+datepart(mi,RC.Clocktime)/60.0
end)
from Personnel Pers
INNER JOIN RawClocks RC ON Pers.Personel_Id = RC.Person_ID
INNER JOIN Direction Dir ON RC.Direction_Id = Dir.Direction_ID
group by Pers.Name + ' ' + Pers.surname, convert(varchar(10),RC.ClockTime,111)
Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-13 : 07:32:36
Anybody???
Go to Top of Page

Seventhnight
Master Smack Fu Yak Hacker

2878 Posts

Posted - 2011-04-13 : 09:50:32
This gets you part way... but there are some problems:

1) if a person forgets to clock twice in a row... ie. no clock OUT and no clock IN, then the range still looks valid:

1 IN
-2 OUT - forgot
-3 IN - forgot
4 OUT

So range looks valid: 1-4 (In-Out).

As far as assuming defaults... you can use IsNull() to put whatever you want in.


Declare @t table (
id int,
clockTime datetime,
InOrOut varchar(10)
)
/*
Insert Into @t Select 1, '2011-04-01 07:55:00', 'IN'
Insert Into @t Select 1, '2011-04-01 10:00:00', 'OUT'
Insert Into @t Select 1, '2011-04-01 10:35:00', 'IN'
Insert Into @t Select 1, '2011-04-01 17:05:00', 'OUT'
*/

Insert Into @t Select 2, '2011-04-01 07:55:00', 'IN'
Insert Into @t Select 2, '2011-04-01 10:00:00', 'OUT'
Insert Into @t Select 2, '2011-04-01 10:35:00', 'IN'
--Insert Into @t Select 2, '2011-04-01 17:05:00', 'OUT'
Insert Into @t Select 2, '2011-04-02 07:55:00', 'IN'
--Insert Into @t Select 2, '2011-04-02 10:00:00', 'OUT'
Insert Into @t Select 2, '2011-04-02 10:35:00', 'IN'
Insert Into @t Select 2, '2011-04-02 17:05:00', 'OUT'

;with cte1(id, clockTime, InOrOut, r)
As (

Select
id, clockTime, InOrOut, r = Row_Number() Over(Partition By id Order By clockTime)
From @t
), cte2(id, InTime, OutTime)
As (

Select
id = isnull(A.id,B.id),
InTime = A.ClockTime,
OutTime = B.Clocktime
From (Select * From cte1 Where InOrOut = 'IN') A
Full Join (Select * From cte1 Where InOrOut = 'OUT') B
On A.id = B.id
and A.r = B.r-1
)

Select *
From cte2
Order By id, InTime


Corey

I Has Returned!!
Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-14 : 03:05:55
Thanks, but I tried to radd your code to my query. The result is not what I am looking for.

I am looking for a result that looks like this:

FullName Date Hours_Worked
PersonA 2011-04-01 08:45:23
PersonB 2011-04-01 07:56:00
PersonC 2011-04-01 16:15:10
Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2011-04-14 : 03:30:26
borrowing the @t from Corey


; with cte as
(
select *, row_no = row_number() over (partition by id order by ClockTime)
from @t
),
cte2 as
(
select c1.id, ClockIn = c1.ClockTime, ClockOut = case when c2.InOrOut = 'OUT'
then c2.ClockTime
else dateadd(day, datediff(day, 0, c1.ClockTime), 0) + '17:00'
end
from cte c1
left join cte c2 on c1.row_no = c2.row_no - 1
and c1.id = c2.id
where c1.InOrOut = 'IN'
)
select id,
Date = dateadd(day, datediff(day, 0, ClockIn), 0),
Hours_Worked = convert(varchar(10), dateadd(second, datediff(second, ClockIn, ClockOut), 0), 108)
from cte2



KH
[spoiler]Time is always against us[/spoiler]

Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-14 : 04:13:57
Thanks,

I get a much more legible result now, but can you explain what you did in this line:

case when c2.Name = 'OUT' then c2.ClockTime
else dateadd(day, datediff(day, 0, c1.ClockTime), 0) + '17:00' end

I am trying to figure out how you got to the results you ended up with. My results dont seem to be correct.
Go to Top of Page

khtan
In (Som, Ni, Yak)

17689 Posts

Posted - 2011-04-14 : 04:54:03
in the cte, row_no is a running number in the order of ClockTime.
So by joining a row with the next row (row_no + 1) and checking the next row's state. If it is not OUT means, there is a OUT record missing, so default it to 17:00.

quote:
My results dont seem to be correct.

i have edited by query. Missed out a join condition on the ID. Try this.
If it doesn't give the required result, post your sample data and the query that you used.


KH
[spoiler]Time is always against us[/spoiler]

Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-14 : 05:51:05
Here is some sample data:

FullName ClockIn Direction
Person A 2011-04-01 07:40:56 IN
Person A 2011-04-01 10:00:00 OUT
Person A 2011-04-01 10:25:56 IN
Person A 2011-04-01 10:54:00 OUT

Here is my query:
; with cte as
(select Emp.Emp_ID, Clock.ClockTime, Dir.Name,
row_no = row_number() over (partition by Emp.Emp_ID order by ClockTime)
from Employee Emp INNER JOIN ClockIns Clock ON
Emp.Emp_Id = Clock.Emp_ID INNER JOIN Direction Dir
ON Clock.Direction_ID = Dir.Direction_ID),
cte2 as
(
select c1.Emp_ID, ClockIn = c1.ClockTime, ClockOut = case when c2.Name = 'OUT' then c2.ClockTime
else dateadd(day, datediff(day, 0, c1.ClockTime), 0) + '17:00' end
from cte c1 left join cte c2 on c1.row_no = c2.row_no - 1
where c1.Name = 'IN')
select Emp_ID, Date = dateadd(day, datediff(day, 0, ClockIn), 0),
Hours_Worked = convert(varchar(10), dateadd(second, datediff(second, ClockIn, ClockOut), 0), 108)
from cte2


For the data displayed above, I am getting several lines of output with values of 6:34:04.
Go to Top of Page

sunitabeck
Master Smack Fu Yak Hacker

5155 Posts

Posted - 2011-04-14 : 07:42:24
In your latest query, you did not follow khtan's advice about adding the employee Id in the join condition in the second CTE.
cte2 as
(
select c1.Emp_ID, ClockIn = c1.ClockTime, ClockOut = case when c2.Name = 'OUT' then c2.ClockTime
else dateadd(day, datediff(day, 0, c1.ClockTime), 0) + '17:00' end
from cte c1 left join cte c2 on c1.row_no = c2.row_no - 1 and c1.Emp_ID = c2.Emp_ID
where c1.Name = 'IN')
Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-14 : 08:01:49
Thank you.

The only problem now, is that if a person clocked in more than once for a day, for eg, he went out during lunch, there would be more than one total hours worked for the day. Is there a way to sum this and produce 1 result per person.

Here is what my result looks like now:

Emp_Id ClockTime Hours_Worked
1 2011-04-01 00:00:00.000 09:03:59
1 2011-04-01 00:00:00.000 00:30:06
2 2011-04-01 00:00:00.000 02:19:04
2 2011-04-01 00:00:00.000 00:28:04
3 2011-04-01 00:00:00.000 08:01:56
4 2011-04-01 00:00:00.000 10:18:57

I need there to be only 1 record for Emp_Id 1, and Emp_Id 2.
Go to Top of Page

sunitabeck
Master Smack Fu Yak Hacker

5155 Posts

Posted - 2011-04-14 : 08:09:05
Sure, that's easy. (Now that khtan and Corey have done all the hard work to help you, it is easy for me to come and add the final touches and take all the credit for having solved the problem [I am typing this and trying to hide an evil grin at the same time)

Just change the last select to sum up the hours for each employee for each day like this:
select   
id,
Date = dateadd(day, datediff(day, 0, ClockIn), 0),
Hours_Worked = convert(varchar(10), dateadd(second, sum(datediff(second, ClockIn, ClockOut)), 0), 108)
FROM
cte2
GROUP BY
id,
dateadd(day, datediff(day, 0, ClockIn), 0)
Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-14 : 08:26:21
I have added that to make my complete query look like this:

; with cte as (select Emp.Emp_ID, Clock.ClockTime, Dir.Name,
row_no = row_number() over (partition by Emp.Emp_ID order by Clock.ClockTime)
from Employee Emp INNER JOIN ClockIns Clock ON Emp.Emp_Id = Clock.Emp_ID
INNER JOIN Direction Dir ON Clock.Direction_ID = Dir.Direction_ID),
cte2 as (select c1.Emp_ID, ClockIn = c1.ClockTime,
ClockOut = case when c2.Name = 'OUT' then c2.ClockTime
else dateadd(day, datediff(day, 0, c1.ClockTime), 0) + '17:00' end
from cte c1 left join cte c2 on c1.row_no = c2.row_no - 1
and c1.Emp_ID = c2.Emp_ID where c1.Name = 'IN')
select Emp_ID, Date = dateadd(day, datediff(day, 0, ClockIn), 0),
Hours_Worked = convert(varchar(10), dateadd(second, datediff(second, ClockIn, ClockOut), 0), 108)
from cte2
GROUP BY Emp_ID, dateadd(day, datediff(day, 0, ClockIn), 0)

I am now getting the following errors:

"Column 'cte2.ClockIn' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
Column 'cte2.ClockOut' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."
Go to Top of Page

sunitabeck
Master Smack Fu Yak Hacker

5155 Posts

Posted - 2011-04-14 : 08:31:24
You left out the MOST vital part
select   
id,
Date = dateadd(day, datediff(day, 0, ClockIn), 0),
Hours_Worked = convert(varchar(10), dateadd(second, sum(datediff(second, ClockIn, ClockOut)), 0), 108)
FROM
cte2
GROUP BY
id,
dateadd(day, datediff(day, 0, ClockIn), 0)

The rule (for the most part) is that when you want to use an aggregate function such as SUM, all other columns in the select list that are outside of the aggregate function must be in the group by clause. So here I am summing the third item in your select list, and so the first two must be in the group by clause.
Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-14 : 08:35:35
Thank you soooo much. Thanks to everyone who helped. :-)

I just have one more question. With this data, I want to find out how many hours the person worked before 08:00:00AM and how many hours the person worked after 16:00:00 each day.

How do I go about doing this?
Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-14 : 09:46:43
Also, I want to change the query to allow for a default if the person only clocked out and not in for a day. What do I need to change in the query to accomodate this? So lets say if there is no IN clock for the day, we must assume that the person started at 08:00AM.

I tried the following but it does not work:

; with cte as (select Emp.Name + ' ' + Emmp.Surname As FullName, Clock.ClockTime, Dir.Name,
row_no = row_number() over (partition by Emp.Name + ' ' + Emp.Surname order by C.ClockTime)
from Employee Emp INNER JOIN ClockIns Clock ON Emp.Emp_Id = Clock.Emp_ID
INNER JOIN Direction Dir ON Clock.Direction_ID = Dir.Direction_ID),
cte2 as ( select c1.FullName,
ClockIn = case when c2.Name = 'IN' then c1.ClockTime
else dateadd(day, datediff(day, 0, c1.ClockTime), 0) + '08:00:00' end,
ClockOut = case when c2.Name = 'OUT' then c2.ClockTime
else dateadd(day, datediff(day, 0, c1.ClockTime), 0) + '17:00:00' end
from cte c1 left join cte c2 on c1.row_no = c2.row_no - 1
and c1.FullName = c2.FullName where c1.Name = 'IN')
select FullName, Date = dateadd(day, datediff(day, 0, ClockIn), 0),
Hours_Worked = convert(varchar(10), dateadd(second, sum(datediff(second, ClockIn, ClockOut)), 0), 108)
FROM
cte2
GROUP BY
FullName, dateadd(day, datediff(day, 0, ClockIn), 0)
Go to Top of Page

sunitabeck
Master Smack Fu Yak Hacker

5155 Posts

Posted - 2011-04-14 : 10:51:01
quote:
Originally posted by Jas

Thank you soooo much. Thanks to everyone who helped. :-)

I just have one more question. With this data, I want to find out how many hours the person worked before 08:00:00AM and how many hours the person worked after 16:00:00 each day.

How do I go about doing this?



Add two more columns to the final select, with limiting the clockout time to 8:00 AM in the first, and clockout time to 4:00 PM in the second. So the final select would be like this:
select   
id,
Date = dateadd(day, datediff(day, 0, ClockIn), 0),
Hours_Worked = convert(varchar(10), dateadd(second, sum(datediff(second, ClockIn, ClockOut)), 0), 108),
Hours_Worked_Before8AM =
convert(varchar(10), dateadd(second, sum(datediff(second, ClockIn,
case
when DATEDIFF(hh,0,ClockIn)%24 < 8 AND DATEDIFF(hh,0,ClockOut)%24 >= 8
THEN DATEADD(hh,(DATEDIFF(hh,0,ClockOut)/24)*24+8,0)
end
)), 0), 108),
Hours_Worked_After4PM =
convert(varchar(10), dateadd(second, sum(datediff(second,
case
when DATEDIFF(hh,0,ClockIn)%24 < 16 AND DATEDIFF(hh,0,ClockOut)%24 >= 16
THEN DATEADD(hh,(DATEDIFF(hh,0,ClockIn)/24)*24+16,0)
END,
ClockOut
)), 0), 108)

FROM
cte2
GROUP BY
id,
dateadd(day, datediff(day, 0, ClockIn), 0)
Go to Top of Page

sunitabeck
Master Smack Fu Yak Hacker

5155 Posts

Posted - 2011-04-14 : 10:52:45
quote:
Originally posted by Jas

Also, I want to change the query to allow for a default if the person only clocked out and not in for a day. What do I need to change in the query to accomodate this? So lets say if there is no IN clock for the day, we must assume that the person started at 08:00AM.

I tried the following but it does not work:

; with cte as (select Emp.Name + ' ' + Emmp.Surname As FullName, Clock.ClockTime, Dir.Name,
row_no = row_number() over (partition by Emp.Name + ' ' + Emp.Surname order by C.ClockTime)
from Employee Emp INNER JOIN ClockIns Clock ON Emp.Emp_Id = Clock.Emp_ID
INNER JOIN Direction Dir ON Clock.Direction_ID = Dir.Direction_ID),
cte2 as ( select c1.FullName,
ClockIn = case when c2.Name = 'IN' then c1.ClockTime
else dateadd(day, datediff(day, 0, c1.ClockTime), 0) + '08:00:00' end,
ClockOut = case when c2.Name = 'OUT' then c2.ClockTime
else dateadd(day, datediff(day, 0, c1.ClockTime), 0) + '17:00:00' end
from cte c1 left join cte c2 on c1.row_no = c2.row_no - 1
and c1.FullName = c2.FullName where c1.Name = 'IN')
select FullName, Date = dateadd(day, datediff(day, 0, ClockIn), 0),
Hours_Worked = convert(varchar(10), dateadd(second, sum(datediff(second, ClockIn, ClockOut)), 0), 108)
FROM
cte2
GROUP BY
FullName, dateadd(day, datediff(day, 0, ClockIn), 0)



I think Corey and khtan's query already does this.
Go to Top of Page

Jas
Yak Posting Veteran

51 Posts

Posted - 2011-04-15 : 03:36:22
Thank you Sunita!!

Can I just ask you to explain what you did in the following lines:

Hours_Worked_BeforeShiftStart = convert(varchar(10), dateadd(second, sum(datediff(second, ClockIn,
case
when DateDiff(hh,0,ClockIn) %24 < 8 AND Datediff(hh, 0, ClockOut)%24 >= 8
then DateAdd(hh,(DateDiff(hh, 0, ClockOut)/24)*24+8, 0) end
)), 0), 108),
Go to Top of Page
    Next Page

- Advertisement -