Displaying Monthly Attendance Date Wise - sql

I need to display attendance data in monthly format using PIVOT. But unable to figure out how it should be done.
Below is my table structure
Attendance:
AttendanceID EmployeeID AttendanceDateTime
Employee:
EmployeeID EmployeeName
Holiday:
HolidayID HolidayDate
Leave:
LeaveID EmployeeID LeaveDateTime IsApproved
I want to display result like below from the data provided in above table
EmployeeName 01-09-2016 02-09-2016 03-09-2016 04-09-2016
A Present Absent Holiday Leave

So the first thing you probably want to do before creating a pivot on dates is figure out what dates you want to pivot. For the purposes of an example, I've just included a way you could get every date within a certain range in this answer but it really depends on what you're looking for.
SELECT *
FROM (
SELECT E.EmployeeID, E.EmployeeName, T.DateToCheck, COALESCE(H.val, A.val, L.val, 'Absent') val
FROM tblEmployee E
CROSS JOIN (
SELECT CAST(DATEADD(DAY, number, '2016-09-01') AS DATE)
FROM master..spt_values
WHERE type = 'P'
AND number <= 3) T(DateToCheck)
LEFT JOIN (SELECT 'Holiday' val, HolidayDate FROM tblHoliday) H ON H.HolidayDate = T.DateToCheck
LEFT JOIN (SELECT 'Present' val, AttendanceDateTime, EmployeeID FROM tblAttendance) A ON CAST(A.AttendanceDateTime AS DATE) = T.DateToCheck AND A.EmployeeID = E.EmployeeID
LEFT JOIN (SELECT 'Leave' val, LeaveDateTime, EmployeeID FROM tblLeave) L ON CAST(L.LeaveDateTime AS DATE) = T.DateToCheck AND L.EmployeeID = E.EmployeeID) T
PIVOT (MAX(val) FOR DateToCheck IN ([2016-09-01], [2016-09-02], [2016-09-03], [2016-09-04])) P;
The basic logic here is that you want to produce your dates to check, compare those dates to each of the different tables (using left joins or outer applies), get only one result (the logic in this answer uses a coalesce to decide which value it'll show), then pivot the result.
EDIT: If you require a PIVOT for a dynamic set of column names (e.g. a dynamic date range), you're going to need to use dynamic SQL. Here's one way you could do it:
DECLARE #SQL VARCHAR(MAX) = '', #dateRange VARCHAR(MAX) = '', #startDate DATE = '2016-09-01', #endDate DATE = '2016-09-05';
SELECT #dateRange += ',' + QUOTENAME(DATEADD(DAY, number, #startDate))
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY, number, #startDate) <= #endDate;
SELECT #dateRange = STUFF(#dateRange, 1, 1, '');
SELECT #SQL = 'SELECT *
FROM (
SELECT E.EmployeeID, E.EmployeeName, T.DateToCheck, COALESCE(H.val, A.val, L.val, ''Absent'') val
FROM tblEmployee E
CROSS JOIN (
SELECT CAST(DATEADD(DAY, number, ''' + CAST(#startDate AS CHAR(10)) + ''') AS DATE)
FROM master..spt_values
WHERE type = ''P''
AND DATEADD(DAY, number, ''' + CAST(#startDate AS CHAR(10)) + ''') <= ''' + CAST(#endDate AS CHAR(10)) + ''') T(DateToCheck)
LEFT JOIN (SELECT ''Holiday'' val, HolidayDate FROM tblHoliday) H ON H.HolidayDate = T.DateToCheck
LEFT JOIN (SELECT ''Present'' val, AttendanceDateTime, EmployeeID FROM tblAttendance) A ON CAST(A.AttendanceDateTime AS DATE) = T.DateToCheck AND A.EmployeeID = E.EmployeeID
LEFT JOIN (SELECT ''Leave'' val, LeaveDateTime, EmployeeID FROM tblLeave) L ON CAST(L.LeaveDateTime AS DATE) = T.DateToCheck AND L.EmployeeID = E.EmployeeID) T
PIVOT (MAX(val) FOR DateToCheck IN (' + #dateRange + ')) P;';
PRINT #SQL;
EXEC(#SQL);
Changing the start and end dates will change the output. You can use the PRINT #SQL to see the actual query.

Related

Keep only Day from Date in column name

i have created a stored procedure by online help to generate a monthly attendance report
USE [Attendace]
GO
/****** Object: StoredProcedure [dbo].[PerDayAttendance] Script Date: 04/11/2018 20:16:05 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[PerDayAttendance]
#STARTDATE DATE,
#ENDDATE DATE
AS BEGIN
WITH DATERANGE AS
(
SELECT DT =DATEADD(DD,0, #STARTDATE)
WHERE DATEADD(DD, 1, #STARTDATE) <= #ENDDATE
UNION ALL
SELECT DATEADD(DD, 1, DT)
FROM DATERANGE
WHERE DATEADD(DD, 1, DT) <= #ENDDATE
) SELECT * INTO cte_DATES
FROM DATERANGE
DECLARE #COLUMN varchar(max)
SELECT #COLUMN=ISNULL(#COLUMN+',','')+ '['+ CAST(CONVERT(DATE , T.DT) AS varchar) + ']' FROM cte_DATES T
DECLARE #Columns2 varchar(max)
SET #Columns2 = SUBSTRING((SELECT DISTINCT ',ISNULL(['+ CAST(CONVERT(DATE , DT)
as varchar )+'],'''') AS ['+ CAST(CONVERT(DATE , DT) as varchar )+']'
FROM cte_DATES GROUP BY dt FOR XML PATH('')),2,8000)
DECLARE #QUERY varchar(MAX)
SET #QUERY = 'SELECT P.EID, ENAME, ' + #Columns2 +', Wdays, Holidays, K.Present, (Wdays-(Holidays + K.Present))as Absent, (cast(((s.Salary/Wdays)*(k.present+Holidays)) as numeric(36,0))) as Salary FROM
(
SELECT A.EID, A.ENAME , B.DT AS DATE, (Case when cast(A.WorkTime as time) >
''00:00:00'' then ''P'' else ''Abs'' end) as worktime FROM TblAttendnce A
RIGHT OUTER JOIN cte_DATES B
ON A.EDATE=B.DT
) X
PIVOT
(
MIN([Worktime])
FOR [DATE] IN (' + #COLUMN + ')
) P
Cross apply (select Wdays, Holidays from dbo.fn_Fn1(''' + CAST(#STARTDATE AS VARCHAR(50)) + ''','''+ CAST(#ENDDATE as Varchar(50))+'''))H
Right Outer Join (select eid, COUNT(present) as present from Attendace.dbo.vwPayroll
where Edate between ''' + CAST(#STARTDATE AS VARCHAR(50)) + '''and'''+ CAST(#ENDDATE as Varchar(50))+'''
group by eid) as K on K.eid=p.EID
Right Outer Join ((select eid, salary from dbo.employeeMast))as s on S.eid=p.eid
WHERE ISNULL(ENAME,'''')<>''''
'
Exec (#QUERY)
DROP TABLE cte_DATES
END
Now the outcome is like this
i need to prepare a crystal report from this procedure, but due to dynamic column header name i am unable to do this.
My Query is how to make column name as 01, 02, 03 instead of 2018-04-01, 2018-04-02, 2018-04-03
i mean i want to rename column name as DD of dd/mm/yyyy
by this way i may able to reflect it in crystal report.
When I am developing a Crystal Report or training a client; I teach them either to rename to the data field in the SQL or stored procedure such date as 3 or Insert a Duplicate Detail line and use the OLD line for Development. Making the NEW Line a Comment Line.

Need monthly report where I need summation of Qty for each day

I have one table STOCK_REGISTER where I have columns Id, ItmId, Qty, CreatedDate. And there are daily transaction data like item inward, outward and all.
Now I am developing one Monthly Report where I want to display Day wise summation of item qty for whole month. I have written following query to do so.
SELECT CONVERT(Date,CreatedDate) AS CreatedDate,ItmId, SUM(Qty)
FROM STOCK_REGISTER
GROUP BY CONVERT(Date,CreatedDate),ItmId
This query returns correct data but if I am asking for April-2017 data and STOCK_REGISTER don't have any record on 5th April then it's not displaying 5th April at all where I need 0 value in qty for that item in 5th April.
Can you help me out to get this type of data?
Edit :
I have created query which gives all days of any particular month and have applied left join with STOCK_REGISTER, but still not solving my issue.
declare #month int, #year int
set #month = 4
set #year = 2017
SELECT CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) + A.Number AS STKFG_CREATED_DATE,
B.STKFG_ITM_ID,
0 AS OPENING_STOCK,
SUM(STKFG_QTY)
FROM master..spt_values A
LEFT JOIN STOCK_REGISTER_FG B
ON CONVERT(DATE,CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) + A.Number) = CONVERT(DATE,B.STKFG_CREATED_DATE)
AND C.ITM_ID = B.STKFG_ITM_ID
WHERE type = 'P'
AND (CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) + A.Number ) <
DATEADD(mm,1,CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) )
AND STKFG_TRAT_ID = 6
GROUP BY A.Number, B.STKFG_ITM_ID
ORDER BY B.STKFG_ITM_ID,CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) + A.Number
Thank you for your comments.
I have found one solution which works fine for me.
Here #TEMP_TABLE contains all dates.
SELECT A.CREATED_DATE,B.ITM_NAME,ISNULL(C.STKFG_QTY,0)
FROM #TEMP_TABLE A
CROSS APPLY ITEM_MASTER B
LEFT JOIN ( SELECT CONVERT(DATE,STKFG_CREATED_DATE) AS STKFG_CREATED_DATE, STKFG_ITM_ID, SUM(STKFG_QTY) AS STKFG_QTY
FROM STOCK_REGISTER_FG GROUP BY CONVERT(DATE,STKFG_CREATED_DATE),STKFG_ITM_ID) C
ON CONVERT(DATE,A.CREATED_DATE) = CONVERT(DATE,C.STKFG_CREATED_DATE)
AND B.ITM_ID = C.STKFG_ITM_ID
ORDER BY B.ITM_NAME,A.CREATED_DATE
Try below script. Here #tblData is your actual table name.
declare #tblData table
(id int,datecolumn date)
INSERT INTO #tblData
(id,datecolumn)
SELECT 1,'2010-01-01' UNION ALL
SELECT 3,'2010-01-01' UNION ALL
SELECT 1,'2010-01-03' UNION ALL
SELECT 2,'2010-01-04' UNION ALL
SELECT 1,'2010-01-05' UNION ALL
SELECT 3,'2010-01-06' UNION ALL
SELECT 1,'2010-01-10' UNION ALL
SELECT 1,'2010-01-10' UNION ALL
SELECT 2,'2010-01-11' UNION ALL
SELECT 1,'2010-01-11' UNION ALL
SELECT 2,'2010-01-11' UNION ALL
SELECT 1,'2010-01-12' UNION ALL
SELECT 3,'2010-01-13'
DECLARE #MaxDate DATE,
#MinDate DATE,
#iDate DATE
DECLARE #DateSequence TABLE(
DATE1 DATE
)
SELECT #MaxDate = Convert(DATE,Max(datecolumn)),
#MinDate = Convert(DATE,Min(datecolumn))
FROM #tblData -- Put your month condition
SET #iDate = #MinDate
WHILE (#iDate <= #MaxDate)
BEGIN
INSERT #DateSequence
SELECT #iDate
SET #iDate = Convert(DATE,Dateadd(DAY,1,#iDate))
END
SELECT a.DATE1 ,isnull(sum(b.id),0) as total
FROM #DateSequence a left join #tblData b on a.DATE1=b.datecolumn
group by a.DATE1
Thanks,

SQL Server : duplicate counts

I have the following. But in the final result some of the employee ID's are counted twice. My goal is to only count distinct employeeID for the [UniqueEmployees] column... Can someone please help me?
This is the code here:
IF OBJECT_ID(N'tempdb..#GG') IS NOT NULL
DROP TABLE #GG
SELECT DISTINCT
[month], vv.Hiremonth,
LoanNumber, vv.EmployeeId,
agentname,
vv.YearsOfService, vv.MonthsofService,
vv.TenureGrouping, vv.TenureMonthGrouping,
manager,
SUM([Call Counts]) as Calls,
SUM(opportunities) as Opportunities,
SUM([Discussed w/Customer]) as [Discussed w/Customer],
SUM(DidNotDiscuss) as [DidNotDiscuss],
SUM(CustomerInterested) as CustomerInterested,
(SELECT COUNT(DISTINCT MGR.EmployeeId)
FROM #MANAGERS MGR
WHERE --EmployeeId = EmployeeId
--and
CAST(CONVERT(datetime, RIGHT(MGR.HireMonth, 4) + LEFT(MGR.HireMonth, 2) + '01') as DATE) <= CAST(CONVERT(datetime, right([Month], 4) + left([Month], 2) + '01') as DATE)
--and MonthsOfService = MonthsOfService
--and YearsOfService = YearsOfService
) as UniqueEmployees
INTO
#GG
FROM
#FINALtemp2b VV
--left join
--(select distinct Employeeid
--from #FINALtemp2b) CC
--on CC.EmployeeId = VV.EmployeeId
GROUP BY
[month], vv.Hiremonth, LoanNumber, vv.EmployeeId,
agentname, vv.YearsOfService, vv.MonthsofService,
vv.TenureGrouping, vv.TenureMonthGrouping, manager
ORDER BY
[month]
Try removing the first 'distinct' and the excess 'select'.
IF OBJECT_ID(N'tempdb..#GG') is not null Drop Table #GG
select *
into #GG
from (
select
[month],
vv.Hiremonth,
LoanNumber,
vv.EmployeeId,
agentname,
vv.YearsOfService,
vv.MonthsofService,
vv.TenureGrouping,
vv.TenureMonthGrouping,
manager,
SUM([Call Counts]) as Calls,
sum(opportunities) as Opportunities,
sum([Discussed w/Customer]) as [Discussed w/Customer],
sum(DidNotDiscuss) as [DidNotDiscuss],
sum(CustomerInterested) as CustomerInterested,
count(distinct (MGR.EmployeeId))
from #MANAGERS MGR
where cast(convert(datetime,right(MGR.HireMonth,4) + left(MGR.HireMonth,2) + '01') as DATE) <= cast(convert(datetime,right([Month],4) + left([Month],2) + '01') as DATE)
group by
[month],
vv.Hiremonth,
LoanNumber,
vv.EmployeeId,
agentname,
vv.YearsOfService,
vv.MonthsofService,
vv.TenureGrouping,
vv.TenureMonthGrouping,
manager
) as UniqueEmployees

how to get the list from calendar table and merge to attendance table and leave table

I am using SQL Server 2008 R2. I have a problem in merging tables. We have 500+ employees
I have the following tables:
Calendar table - holds the dates from 1/1/2005 to 12/31/2016
Attendance table - for the attendance of employees
LeaveHistory table - for the leave history
LeaveBreakDown table -for leave break down
Holiday table - for holidays
Our goal, with date range from calendar (11/1/2015 - 11/30/2015)
we want to show the complete days even if the attendance is not equal to the total numbers of days.
Here's my first solution but too slow and without calendar table
FETCH NEXT FROM Employees INTO #EmployeeID,#BranchCode,#IsOfficer, #FirstName, #MiddleName, #LastName, #RankCode;
WHILE ##FETCH_STATUS = 0
BEGIN
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #tblData(ActualDate,EmployeeID,BranchCode,IsOfficer, FirstName, MiddleName, LastName, RankCode, LeaveCode, Gender, ShiftCode,ShiftIn,ShiftOut,IsRestDay)
SELECT
#StartDate
, #EmployeeID
, #BranchCode
, #IsOfficer
, #FirstName
, #MiddleName
, #LastName
, #RankCode
, LB.LeaveBreakDownCode
, E.Gender
,ShiftCode = SS.ShiftCode
,ShiftIn = CONVERT(DATETIME, CONVERT(VARCHAR(20), DATEADD(day,0,#StartDate), 101) + ' ' + CONVERT(VARCHAR(20), SS.ShiftIn,108))
,ShiftOut = CASE
WHEN SS.ShiftOut < SS.ShiftIn THEN CONVERT(DATETIME, CONVERT(VARCHAR(20), DATEADD(day,1,#StartDate), 101) + ' ' + CONVERT(VARCHAR(20), SS.ShiftOut ,108))
ELSE CONVERT(DATETIME, CONVERT(VARCHAR(20), DATEADD(day,0,#StartDate), 101) + ' ' + CONVERT(VARCHAR(20), SS.ShiftOut ,108))
END
,IsRestDay = CASE
WHEN SS.Sunday = 1 AND DATEPART(weekday, #StartDate) = 1 THEN 1
WHEN SS.Monday = 1 AND DATEPART(weekday, #StartDate) = 2 THEN 1
WHEN SS.Tuesday = 1 AND DATEPART(weekday, #StartDate) = 3 THEN 1
WHEN SS.Wednesday = 1 AND DATEPART(weekday, #StartDate) = 4 THEN 1
WHEN SS.Thursday = 1 AND DATEPART(weekday, #StartDate) = 5 THEN 1
WHEN SS.Friday = 1 AND DATEPART(weekday, #StartDate) = 6 THEN 1
WHEN SS.Saturday = 1 AND DATEPART(weekday, #StartDate) = 7 THEN 1
ELSE 0
END
FROM Employees E
LEFT JOIN (
SELECT
LB.LeaveBreakDownCode
, LB.EmployeeID
FROM LeaveBreakDown LB
INNER JOIN LeaveHistory LH ON LH.LeaveHistoryCode = LB.LeaveHistoryCode AND LB.DateLeave = #StartDate AND LH.Status IN ('0','1')
WHERE LB.EmployeeID = #EmployeeID
) LB ON LB.EmployeeID = E.EmployeeID
LEFT JOIN ShiftSchedule SS ON SS.EmployeeID = E.EmployeeID AND #StartDate BETWEEN SS.EffectivityDate AND SS.EndDate
WHERE E.Status='1' AND E.ResignedDate IS NULL AND E.EmployeeID = #EmployeeID
SET #StartDate = DATEADD(day,1,#StartDate)
END
SET #StartDate = #InitialDate -- Reinitialize Start Date
FETCH NEXT FROM Employees INTO #EmployeeID, #BranchCode,#IsOfficer,#FirstName, #MiddleName, #LastName, #RankCode;
END;
CLOSE Employees;
DEALLOCATE Employees;
With this solution, if we are going to run the script. it took 3 minutes and sometimes 6minutes.
Might be the structure
Dates ('11/1/2015' - '11/30/2015')
-> Attendance
-> LeaveHistory
All dates from Dates table in date range will be filled with values from different table.
Don't use cursor.In fact it can be done without using RBAR/cursor.
You need to explain little more your table structure.Or start slowly with fewer column and table and ask where you are struck.
Calender Table is not require.What is the purpose of "ShiftSchedule" ?
You can do so by using recursive CTE.
Tell us more then we help you accordingly.
Recursive CTE sample.you can implemented your example here with fewer table and column then gradually increase.
DECLARE #StartDate DATETIME = '11/1/2015'
DECLARE #EndDate DATETIME = '11/30/2015';
WITH CTE
AS (
SELECT #StartDate DT
UNION ALL
SELECT DATEADD(day, 1, DT)
FROM CTE
WHERE DT < #EndDate
)
SELECT *
FROM CTE

Get total working hours from SQL table

I have an attendance SQL table that stores the start and end day's punch of employee. Each punch (punch in and punch out) is in a separate record.
I want to calculate the total working hour of each employee for a requested month.
I tried to make a scalar function that takes two dates and employee ID and return the calculation of the above task, but it calculate only the difference of one date between all dates.
The data is like this:
000781 2015-08-14 08:37:00 AM EMPIN 539309898
000781 2015-08-14 08:09:48 PM EMPOUT 539309886
My code is:
#FromDate NVARCHAR(10)
,#ToDate NVARCHAR(10)
,#EmpID NVARCHAR(6)
CONVERT(NVARCHAR,DATEDIFF(HOUR
,(SELECT Time from PERS_Attendance att where attt.date between convert(date,#fromDate) AND CONVERT(Date,#toDate)
AND (EmpID= #EmpID OR ISNULL(#EmpID, '') = '') AND Funckey = 'EMPIN')
,(SELECT Time from PERS_Attendance att where attt.date between convert(date,#fromDate) AND CONVERT(Date,#toDate)
AND (EmpID= #EmpID OR ISNULL(#EmpID, '') = '') AND Funckey = 'EMPOUT') ))
FROM PERS_Attendance attt
One more approach that I think is simple and efficient.
It doesn't require modern functions like LEAD
it works correctly if the same person goes in and out several times during the same day
it works correctly if the person stays in over the midnight or even for several days in a row
it works correctly if the period when person is "in" overlaps the start OR end date-time.
it does assume that data is correct, i.e. each "in" is matched by "out", except possibly the last one.
Here is an illustration of a time-line. Note that start time happens when a person was "in" and end time also happens when a person was still "in":
All we need to do it calculate a plain sum of time differences between each event (both in and out) and start time, then do the same for end time. If event is in, the added duration should have a positive sign, if event is out, the added duration should have a negative sign. The final result is a difference between sum for end time and sum for start time.
summing for start:
|---| +
|----------| -
|-----------------| +
|--------------------------| -
|-------------------------------| +
--|====|--------|======|------|===|=====|---|==|---|===|====|----|=====|--- time
in out in out in start out in out in end out in out
summing for end:
|---| +
|-------| -
|----------| +
|--------------| -
|------------------------| +
|-------------------------------| -
|--------------------------------------| +
|-----------------------------------------------| -
|----------------------------------------------------| +
I would recommend to calculate durations in minutes and then divide result by 60 to get hours, but it really depends on your requirements. By the way, it is a bad idea to store dates as NVARCHAR.
DECLARE #StartDate datetime = '2015-08-01 00:00:00';
DECLARE #EndDate datetime = '2015-09-01 00:00:00';
DECLARE #EmpID nvarchar(6) = NULL;
WITH
CTE_Start
AS
(
SELECT
EmpID
,SUM(DATEDIFF(minute, (CAST(att.[date] AS datetime) + att.[Time]), #StartDate)
* CASE WHEN Funckey = 'EMPIN' THEN +1 ELSE -1 END) AS SumStart
FROM
PERS_Attendance AS att
WHERE
(EmpID = #EmpID OR #EmpID IS NULL)
AND att.[date] < #StartDate
GROUP BY EmpID
)
,CTE_End
AS
(
SELECT
EmpID
,SUM(DATEDIFF(minute, (CAST(att.[date] AS datetime) + att.[Time]), #StartDate)
* CASE WHEN Funckey = 'EMPIN' THEN +1 ELSE -1 END) AS SumEnd
FROM
PERS_Attendance AS att
WHERE
(EmpID = #EmpID OR #EmpID IS NULL)
AND att.[date] < #EndDate
GROUP BY EmpID
)
SELECT
CTE_End.EmpID
,(SumEnd - ISNULL(SumStart, 0)) / 60.0 AS SumHours
FROM
CTE_End
LEFT JOIN CTE_Start ON CTE_Start.EmpID = CTE_End.EmpID
OPTION(RECOMPILE);
There is LEFT JOIN between sums for end and start times, because there can be EmpID that has no records before the start time.
OPTION(RECOMPILE) is useful when you use Dynamic Search Conditions in T‑SQL. If #EmpID is NULL, you'll get results for all people, if it is not NULL, you'll get result just for one person.
If you need just one number (a grand total) for all people, then wrap the calculation in the last SELECT into SUM(). If you always want a grand total for all people, then remove #EmpID parameter altogether.
It would be a good idea to have an index on (EmpID,date).
My approach would be as follows:
CREATE FUNCTION [dbo].[MonthlyHoursByEmpID]
(
#StartDate Date,
#EndDate Date,
#Employee NVARCHAR(6)
)
RETURNS FLOAT
AS
BEGIN
DECLARE #TotalHours FLOAT
DECLARE #In TABLE ([Date] Date, [Time] Time)
DECLARE #Out TABLE ([Date] Date, [Time] Time)
INSERT INTO #In([Date], [Time])
SELECT [Date], [Time]
FROM PERS_Attendance
WHERE [EmpID] = #Employee AND [Funckey] = 'EMPIN' AND ([Date] > #StartDate AND [Date] < #EndDate)
INSERT INTO #Out([Date], [Time])
SELECT [Date], [Time]
FROM PERS_Attendance
WHERE [EmpID] = #Employee AND [Funckey] = 'EMPOUT' AND ([Date] > #StartDate AND [Date] < #EndDate)
SET #TotalHours = (SELECT SUM(CONVERT([float],datediff(minute,I.[Time], O.[Time]))/(60))
FROM #in I
INNER JOIN #Out O
ON I.[Date] = O.[Date])
RETURN #TotalHours
END
Assuming the entries are properly paired (in -> out -> in -> out -> in etc).
SQL Server 2012 and later:
DECLARE #Year int = 2015
DECLARE #Month int = 8
;WITH
cte AS (
SELECT EmpID,
InDate = LAG([Date], 1) OVER (PARTITION BY EmpID ORDER BY [Date]),
OutDate = [Date],
HoursWorked = DATEDIFF(hour, LAG([Date], 1) OVER (PARTITION BY EmpID ORDER BY [Date]), [Date]),
Funckey
FROM PERS_Attendance
)
SELECT EmpID,
TotalHours = SUM(HoursWorked)
FROM cte
WHERE Funckey = 'EMPOUT'
AND YEAR(InDate) = #Year
AND MONTH(InDate) = #Month
GROUP BY EmpID
SQL Server 2005 and later:
;WITH
cte1 AS (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY EmpID ORDER BY [Date])
FROM PERS_Attendance
),
cte2 AS (
SELECT a.EmpID, b.[Date] As InDate, a.[Date] AS OutDate,
HoursWorked = DATEDIFF(hour, b.[Date], a.[Date])
FROM cte1 a
LEFT JOIN cte1 b ON a.EmpID = b.EmpID and a.rn = b.rn + 1
WHERE a.Funckey = 'EMPOUT'
)
SELECT EmpID,
TotalHours = SUM(HoursWorked)
FROM cte2
WHERE YEAR(InDate) = #Year
AND MONTH(InDate) = #Month
GROUP BY EmpID