Calculate monthly hours - sql-server

CREATE TABLE EmpAttendance
(
EmpCode INT,
Time_In Time(5),
Time_Out Time(5),
DayDate date
)
INSERT INTO EmpAttendance VALUES (23, '08:30', '13:00', '2018-07-12');
INSERT INTO EmpAttendance VALUES (23, '13:45', '18:30', '2018-07-12');
INSERT INTO EmpAttendance VALUES (23, '09:15', '12:50', '2018-07-13');
INSERT INTO EmpAttendance VALUES (23, '14:02', '18:22', '2018-07-13');
INSERT INTO EmpAttendance VALUES (23, '08:30', '16:00', '2018-07-14');
INSERT INTO EmpAttendance VALUES (23, '08:45', '17:56', '2018-07-15');
INSERT INTO EmpAttendance VALUES (12, '09:15', '12:50', '2018-07-13');
INSERT INTO EmpAttendance VALUES (12, '14:02', '18:22', '2018-07-13');
INSERT INTO EmpAttendance VALUES (12, '08:30', '16:00', '2018-07-14');
I have this table structure that is storing the time in and out of specific employee on a specific day. I want to calculate the total number of hours that an employee was present for a whole month.
The query should accept 2 input date parameters and employee code and give the output as total hours.
I am able to get daily hours but I want to calculate monthly as well and can't figure out how.
SELECT EmpCode, [DayDate],
FirstIN = CAST(MIN(Time_In) AS TIME),
LastOUT = CAST(MAX(Time_Out) AS TIME),
HoursSpent = DATEDIFF(HOUR, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)),
CONVERT(VARCHAR(6), Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME))/3600)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), (Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)) % 3600) / 60), 2)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)) % 60) , 2 )
AS hoursSpent
FROM EmpAttendance
GROUP BY EmpCode, DayDate

just change it to GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, DayDate), 0)
SELECT EmpCode,
DATEADD(MONTH, DATEDIFF(MONTH, 0, DayDate), 0) as MonthDate,
--FirstIN = CAST(MIN(Time_In) AS TIME),
--LastOUT = CAST(MAX(Time_Out) AS TIME),
HoursSpent = DATEDIFF(HOUR, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)),
CONVERT(VARCHAR(6), Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME))/3600)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), (Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)) % 3600) / 60), 2)
+ ':'
+ RIGHT('0' + CONVERT(VARCHAR(2), Datediff(second, CAST(MIN(Time_In) AS TIME), CAST(MAX(Time_Out) AS TIME)) % 60) , 2 )
AS HoursSpent
FROM EmpAttendance
-- add the condition here in where clause
WHERE DayDate >= #StartDate
GROUP BY EmpCode, DATEADD(MONTH, DATEDIFF(MONTH, 0, DayDate), 0)

Assuming your Time_In and Time_Out values always relate to the same day, you can just sum up the datediff in minutes for each day:
declare #t table(EmpCode int
,Time_In time(5)
,Time_Out time(5)
,DayDate date
);
insert into #t values(23, '08:30', '13:00', '2018-07-12'),(23, '13:45', '18:30', '2018-07-12'),(23, '09:15', '12:50', '2018-07-13'),(23, '14:02', '18:22', '2018-07-13'),(23, '08:30', '16:00', '2018-07-14'),(23, '08:45', '17:56', '2018-07-15'),(12, '09:15', '12:50', '2018-07-13'),(12, '14:02', '18:22', '2018-07-13'),(12, '08:30', '16:00', '2018-07-14');
select EmpCode
,dateadd(month,datediff(month,0,DayDate),0) as MonthGroup
,sum(datediff(minute,Time_In,Time_Out))/60 as HoursWorked
,sum(datediff(minute,Time_In,Time_Out))%60 as MinutesWorked
from #t
group by EmpCode
,dateadd(month,datediff(month,0,DayDate),0)
;
Output:
+---------+-------------------------+-------------+---------------+
| EmpCode | MonthGroup | HoursWorked | MinutesWorked |
+---------+-------------------------+-------------+---------------+
| 12 | 2018-07-01 00:00:00.000 | 15 | 25 |
| 23 | 2018-07-01 00:00:00.000 | 33 | 51 |
+---------+-------------------------+-------------+---------------+

Related

Calculate hours spent in office

I have this table structure and some sample data in it. I want to calculate the number of total hours spent by each employee in office at the end of the month but when I run the following query I am getting this error.
Hope someone can help.
CREATE TABLE HOURSSPENT
(
EmpCode INT,
Time_Spent Time(5),
DayDate date
)
INSERT INTO HOURSSPENT VALUES (23, '08:30', '2018-07-12');
INSERT INTO HOURSSPENT VALUES (23, '05:40', '2018-07-13');
INSERT INTO HOURSSPENT VALUES (23, '07:23', '2018-07-16');
INSERT INTO HOURSSPENT VALUES (19, '08:30', '2018-07-12');
INSERT INTO HOURSSPENT VALUES (19, '05:40', '2018-07-13');
INSERT INTO HOURSSPENT VALUES (29, '07:23', '2018-07-12');
SELECT SUM(Time_Spent)
FROM HOURSSPENT
GROUP BY EmpCode
Error:
Msg 8117, Level 16, State 1, Line 1
Operand data type time is invalid for sum operator.
CREATE TABLE HOURSSPENT
(
EmpCode INT,
Time_Spent Time(5),
DayDate date
)
INSERT INTO HOURSSPENT VALUES (23, '08:30', '2018-07-12');
INSERT INTO HOURSSPENT VALUES (23, '05:40', '2018-07-13');
INSERT INTO HOURSSPENT VALUES (23, '07:23', '2018-07-16');
INSERT INTO HOURSSPENT VALUES (19, '08:30', '2018-07-12');
INSERT INTO HOURSSPENT VALUES (19, '05:40', '2018-07-13');
INSERT INTO HOURSSPENT VALUES (29, '07:23', '2018-07-12');
SELECT EMPCODE,CAST(DATEADD(MILLISECOND,SUM(DATEDIFF
(MILLISECOND,0,CAST(TIME_SPENT AS DATETIME))),0) AS TIME) [sum of hrs] FROM HOURSSPENT
GROUP BY EMPCODE
output
EMPCODE sum of hrs
19 14:10:00.0000000
23 21:33:00.0000000
29 07:23:00.0000000
use DATEDIFF() to get the difference in minute and then SUM it up. Divide by 60 to convert back to hour
SELECT EmpCode, SUM(datediff(minute, 0, Time_Spent)) / 60.0
FROM HOURSSPENT
GROUP BY EmpCode
If the total time will not exceed 24 hours, you can just simply do this
SELECT EmpCode,
convert(time, dateadd(minute, SUM(datediff(minute, 0, Time_Spent)), 0))
FROM HOURSSPENT
GROUP BY EmpCode
else, you need to calculate the day, hour, minute separately
day = Total_spent / 60 / 60

How to get difference of two date in same column in SQL Server

How can I get the time difference of the two same date in the same column? And what if there is multiple in/out in the same day?
Table tbl_Employee_MasterList:
id First_Name Last_Name biometric_no
----------------------------------------------
125 ABRAHAM JOSEPH MOQUETE 78
Table officer_timelogs
employee_id record_time Day type
--------------------------------------------------------
125 2018-02-27 18:03:31.000 Tuesday 1
125 2018-02-27 07:54:03.000 Tuesday 0
SQL query:
select
a.employee_id, a.record_time,
--CONVERT(char(10), a.record_time, 103) as [Date],
DATENAME(WEEKDAY, a.record_time) as [Day],
a.[type],
case
when a.[type] = 0
then 'in'
when a.[type] = 1
then 'out'
end as Status
from
officer_timelogs a
left join
tbl_Employee_MasterList b on a.employee_id = b.biometric_no
where
a.employee_id = '125'
order by
a.record_time desc
Output:
employee_id record_time Day type Status
---------------------------------------------------------------
125 2018-02-28 07:47:23.000 Wednesday 0 in
125 2018-02-27 18:03:31.000 Tuesday 1 out
125 2018-02-27 07:54:03.000 Tuesday 0 in
125 2018-02-26 18:01:59.000 Monday 1 out
125 2018-02-26 07:48:25.000 Monday 0 in
125 2018-02-24 12:50:00.000 Saturday 1 out
125 2018-02-24 07:44:16.000 Saturday 0 in
125 2018-02-23 17:02:06.000 Friday 1 out
125 2018-02-23 07:48:26.000 Friday 0 in
125 2018-02-22 18:02:35.000 Thursday 1 out
125 2018-02-22 07:48:41.000 Thursday 0 in
Desired output (I want to retrieve data like this):
employee_id Date Day Hours
-----------------------------------------------------
125 2018-02-27 Tuesday 10:09:28 (h:m:s)
I am using SQL Server 2012.
I just used one table to simplify. I t will not be hard to join and get emplyee names. This query calculates time difference between first in and last out as you asked. And returns '00:00:00' if either in or out is missing
declare #t table (
employee_id int
, record_time datetime
, type int
)
insert into #t
values (125, '20180228 07:47:23.000', 0)
, (125, '20180227 18:03:31.000', 1), (125, '20180227 07:54:03.000', 0)
, (125, '20180226 18:01:59.000', 1), (125, '20180226 07:48:25.000', 0)
, (125, '20180224 12:50:00.000', 1), (125, '20180224 07:44:16.000', 0)
, (125, '20180223 17:02:06.000', 1), (125, '20180223 07:48:26.000', 0)
, (125, '20180222 18:02:35.000', 1), (125, '20180222 07:48:41.000', 0)
select
employee_id, [date]
, [hours] = right(concat('00', diff / 3600), 2) + ':' + right(concat('00', diff % 3600 / 60), 2) + ':' + right(concat('00', diff % 60), 2)
from (
select
employee_id, [date] = cast(record_time as date)
, diff = datediff(ss, min(iif(type = 0, record_time, null)), max(iif(type = 1, record_time, null)))
from
#t
group by employee_id, cast(record_time as date)
) t
But I recommend to look at this query. It calculates each in-out times separately and then gets total sum per day
select
employee_id, [date]
, [hours] = right(concat('00', diff / 3600), 2) + ':' + right(concat('00', diff % 3600 / 60), 2) + ':' + right(concat('00', diff % 60), 2)
from (
select
employee_id, [date] = cast(isnull([in], [out]) as date)
, diff = sum(diff)
from (
select
employee_id, [in] = max(iif(type = 0, record_time, null))
, [out] = max(iif(type = 1, record_time, null))
, diff = datediff(ss, max(iif(type = 0, record_time, null)), max(iif(type = 1, record_time, null)))
from (
select
*, grp = sum(iif(type = 0, 1, 0)) over (partition by employee_id order by record_time)
from
#t
) t
group by employee_id, grp
) t
group by employee_id, cast(isnull([in], [out]) as date)
) t

Calculate salary base on total Hours in SQL Server

How can I calculate salary base on total Hours? I have two tables and a query that will compute the total hours per day.
Table officer_timelogs
employee_id record_time Day type
--------------------------------------------------------
125 2018-02-27 18:03:31.000 Tuesday 1
125 2018-02-27 07:54:03.000 Tuesday 0
Table officer_rate
employee_id designation salary
125 programmer 100 (hour)
SQL Query:
select
employee_id,
[Date], DATENAME(WEEKDAY, date)as [Day],
[Hours] = right(concat('00', diff / 3600), 2) + ' : ' + right(concat('00', diff % 3600 / 60), 2) + ' : ' + right(concat('00', diff % 60), 2)
from (
select
employee_id,
[date] = cast(record_time as date),
diff = datediff(ss, min(iif(type = 0, record_time, null)), max(iif(type = 1, record_time, null)))
from
officer_timelogs
where employee_id = '125'
group by employee_id, cast(record_time as date)
) t
order by date desc
Output:
employee_id Date Day Hours
125 2018-03-02 Friday 09 : 00 : 00
125 2018-03-01 Thursday 10 : 10 : 49
125 2018-02-28 Wednesday 10 : 14 : 11
125 2018-02-27 Tuesday 10 : 09 : 28
125 2018-02-26 Monday 10 : 13 : 34
Desired output (I want to retrieve data like this)
employee_id Date Day Hours Salary
125 2018-03-02 Friday 09 : 00 : 00 900
I think you've done the most complex part.
Since you already have the worked time, you just need to multiply by the salary, like this:
select
t.employee_id,
[Date], DATENAME(WEEKDAY, date)as [Day],
[Hours] = right(concat('00', diff / 3600), 2) + ' : ' + right(concat('00', diff % 3600 / 60), 2) + ' : ' + right(concat('00', diff % 60), 2),
diff * r.salary / 3600 AS Salary
from (
select
employee_id,
[date] = cast(record_time as date),
diff = datediff(ss, min(iif(type = 0, record_time, null)), max(iif(type = 1, record_time, null)))
from
officer_timelogs
where employee_id = '125'
group by employee_id, cast(record_time as date)
) t
INNER JOIN officer_rate r ON t.employee_id = r.employee_id
order by date desc
You can check it live on this SQL Fiddle.
Edit: Code and demo updated with OT rate:
In this case, you need to check if OT should be applied. If the person worked 8h or less, we consider the regular rate. The difference for the 8h is calculated by considering 20% of the hourly salary:
select
t.employee_id,
[Date], DATENAME(WEEKDAY, date)as [Day],
[Hours] = right(concat('00', diff / 3600), 2) + ' : ' + right(concat('00', diff % 3600 / 60), 2) + ' : ' + right(concat('00', diff % 60), 2),
CAST(
CASE WHEN diff <= 3600 * 8
THEN diff * r.salary / 3600
ELSE
(3600 * 8 * r.salary / 3600) -- salary x 8h / work
+ (diff - (3600 * 8)) * r.salary * 0.2 /3600 -- OT work
END AS decimal(10, 2)) AS Salary
from (
select
employee_id,
[date] = cast(record_time as date),
diff = datediff(ss, min(iif(type = 0, record_time, null)), max(iif(type = 1, record_time, null)))
from
officer_timelogs
where employee_id = '125'
group by employee_id, cast(record_time as date)
) t
INNER JOIN officer_rate r ON t.employee_id = r.employee_id
order by date desc;
Demo updated here
Maybe this. I bet you need to convert hours to decimal such that 1 hour and 30 minutes will make 1.5
select
employee_id,
[Date], DATENAME(WEEKDAY, date)as [Day],
[Hours] = right(concat('00', diff / 3600), 2) + ' : ' + right(concat('00', diff % 3600 / 60), 2) + ' : ' + right(concat('00', diff % 60), 2),
Salary = <However you need to get hour(s) as a decimal> * tl2.salary
from (
select
employee_id,
[date] = cast(record_time as date),
diff = datediff(ss, min(iif(type = 0, record_time, null)), max(iif(type = 1, record_time, null)))
from
officer_timelogs
where employee_id = '125'
group by employee_id, cast(record_time as date)
) t
INNER JOIN officer_rate tl2 ON tl2.employee_id=t.employee_id
order by date desc

Find gaps in timesheet data between certain hours

I am trying to find gaps in timesheets between the hours of 8AM and 6PM. I am able to find the gaps for records that are logged, but I cannot figure out how to determine if a record was "missed" - meaning if they started at 8:30 AM, I cannot figure out how to identify the 30 minute gap between 8AM and 8:30 AM (ie. they started work late).
In below example, I can find the two gaps between 12 and 12:30 pm, but not the 8am-8:30am gap and 5:30 to 6pm gap on 5/8, and 8am-8:30am gap on 5/10.
Any ideas to point me in the right direction on how I could approach this?
drop table #time;
create table #time (
TimesheetId int not null
, StartTime datetime not null
, EndTIme datetime not null
);
insert into #time (TimesheetId, StartTime, EndTime)
values (210, '2017-05-08 05:30:00.000', '2017-05-08 06:30:00.000')
, (210, '2017-05-08 06:30:00.000', '2017-05-08 08:30:00.000')
, (210, '2017-05-08 08:30:00.000', '2017-05-08 12:00:00.000')
, (210, '2017-05-08 12:30:00.000', '2017-05-08 18:30:00.000')
, (210, '2017-05-09 08:30:00.000', '2017-05-09 12:00:00.000')
, (210, '2017-05-09 12:30:00.000', '2017-05-09 17:30:00.000')
, (210, '2017-05-09 22:30:00.000', '2017-05-10 05:30:00.000')
, (210, '2017-05-10 08:30:00.000', '2017-05-10 18:00:00.000')
;
; with t1 as (
SELECT TimesheetId
, StartTime
, lag(EndTime) OVER (PARTITION BY TimesheetId ORDER BY StartTime) AS prev_endtime
FROM #time
where datepart(HH, StartTime) <= 18
and datepart(HH, EndTime) >= 8
)
select prev_endtime as gapStart
, StartTime as gapEnd
from t1
where StartTime <> prev_endtime
and cast(prev_endtime as date) = cast(StartTime as date)
;
WITH
a AS(SELECT DATEADD(hh, DATEDIFF(dd, 0, StartTime) * 24 + 8, 0) t,
TimesheetId FROM #time),
b AS(SELECT * FROM #time UNION ALL SELECT TimesheetId, t, t FROM a UNION ALL
SELECT TimesheetId, DATEADD(hh, 10, t), DATEADD(hh, 10, t) FROM a),
c AS(SELECT TimesheetId,
LAG(EndTime) OVER (
PARTITION BY TimesheetId ORDER BY StartTime
) prev_fin,
StartTime
FROM b),
d AS(SELECT *, DATEADD(hh, DATEDIFF(dd, 0, prev_fin) * 24 + 8, 0) beg,
DATEADD(hh, DATEDIFF(dd, 0, prev_fin) * 24 + 18, 0) fin
FROM c)
SELECT TimesheetId, prev_fin, StartTime
FROM d
WHERE prev_fin < StartTime AND
((prev_fin >= beg AND prev_fin < fin) OR
(StartTime > beg AND StartTime <= fin));
Check it on rextester.com.
You can use this to insert a record and then use what you have
Or you could use a UNION
select distinct t1.TimesheetId, dateadd(hh, 8, cast(CONVERT(date, StartTime) as datetime)) as StartTime, dateadd(hh, 8, cast(CONVERT(date, StartTime) as datetime)) as EndTime
from #time t1
where not exists ( select dateadd(hh, 8, cast(CONVERT(date, T2.StartTime) as datetime)), t2.*
from #time T2
where 1 = 1
and t2.TimesheetId = t1.TimesheetId
and CONVERT(date, T2.StartTime) = CONVERT(date, T1.StartTime)
and t2.StartTime = dateadd(hh, 8, cast(CONVERT(date, t2.StartTime) as datetime))
)

Select max date from sql server table saved as varchar data in "mm-yyyy" format

In a table date data is saved as mm-yyyy format and its datatype is varchar.
Now I want to retrieve MAX date.
Date is saved in below format and there are thousands of records:
7-1986
10-2012
6-1989
5-1975
7-1974
7-1961
12-1987
10-1975
6-1959
10-2002
12-1991
11-1961
6-1966
12-1959
10-1956
12-1953
6-1999
2-1989
I tried:
SELECT MAX(CONVERT(DATETIME, '1-'+[Date], 105)) As MAXDate FROM tablename
But it returns 2015-12-01 00:00:00.000, but it should be 2015-01-01 00:00:00.000 , because MAX date is saved as 1-2015.
You could try:
SELECT MAX(CAST(RIGHT(Datestring, 4) + RIGHT('00' + SUBSTRING(DateString, 1, CHARINDEX('-', DateString, 1) - 1), 2) + '01' AS SMALLDATETIME)) FROM [YourTable]
SAMPLE:
CREATE TABLE #Dates(DateString VARCHAR(10))
INSERT INTO #Dates VALUES
('7-1986'), ('10-2012'), ('6-1989'),
('5-1975'), ('7-1974'), ('7-1961'),
('12-1987'), ('10-1975'), ('6-1959'),
('10-2002'), ('12-1991'), ('11-1961'),
('6-1966'), ('12-1959'), ('10-1956'),
('12-1953'), ('6-1999'), ('2-1989');
;WITH CTE AS(
SELECT
DateString,
[Month] = SUBSTRING(DateString, 1, CHARINDEX('-', DateString, 1) - 1),
[Year] = RIGHT(Datestring, 4),
[Date] = CAST(RIGHT(Datestring, 4) + RIGHT('00' + SUBSTRING(DateString, 1, CHARINDEX('-', DateString, 1) - 1), 2) + '01' AS SMALLDATETIME)
FROM #Dates
)
SELECT MAX([Date]) AS MaxDate FROM CTE
DATA
DateString
----------
7-1986
10-2012
6-1989
5-1975
7-1974
7-1961
12-1987
10-1975
6-1959
10-2002
12-1991
11-1961
6-1966
12-1959
10-1956
12-1953
6-1999
2-1989
RESULT
MaxDate
-----------------------
2012-10-01 00:00:00
You can try.
SELECT Max(cast('1-'+Date as DateTime)) As MAXDate FROM tablename
You can use STUFF function to add -1:
SELECT MAX(CONVERT(DATETIME, STUFF([Date], CHARINDEX('-', [Date]), 0, '-1'), 121))

Resources