I have two tables used for storing employee attendance details.
one table stores emp Id and respective in time and out time in date time info second table stores the other employee details as employee id, employee name etc... I have a requirement to generate a report which shows total hours worked by emp per day, a status column storing details like Present if total hours > 4.5 else absent also need to count the number of days an employee have status as present, number of days for which an employee's total hour is greater than 6 less than 8.5.
I have written the query to fetch every details but the performance is unacceptable it takes around 30-35 miniutes to fetch all the details
if I exclude the days counting logic it takes around 1-2 minutes
the table structure is
Ist Employee table EmployeeID, EmployeeName.....other details(not necessary at this moment)
Attendance table
Emp_ID, INOUT_Time
My query
DECLARE @currStartDate DATETIME
DECLARE @currEndDate DATETIME
declare @startDate datetime;
declare @endDate datetime;
set @startDate = CONVERT(Datetime, '12/16/2013');
set @endDate = CONVERT(Datetime, '01/16/2014');
SET @currStartDate=@startDate
SET @currEndDate=dateAdd(day,1,@startDate)
DECLARE @formatTable TABLE
(
EmployeeCode varchar(10),
EmployeeName varchar(100),
[Date] Datetime,
InTime datetime,
OutTime datetime,
TotalHrs varchar(10),
[Status] varchar(10)
)
WHILE @currEndDate <= @endDate
BEGIN
--get the day by day attendance Range
INSERT INTO @formatTable
(
EmployeeCode,
EmployeeName,
[Date],
InTime,
OutTime
)
SELECT
E.EmployeeID,
ISNULL(LTRIM(RTRIM(E.FirstName)),'') +' '+ISNULL(LTRIM(RTRIM(E.LastName)),'') AS EmployeeName,
@currStartDate,
MIN(AD.INOUT_Time) as INTIME,
CASE WHEN MAX(AD.INOUT_Time)=MIN(AD.INOUT_Time) THEN NULL ELSE MAX(AD.INOUT_Time) END as OUTTIME
FROM employees E WITH(NOLOCK)
LEFT OUTER JOIN Attendance AD
ON E.EmployeeID = AD.Emp_ID
AND INOUT_Time BETWEEN @currStartDate AND @currEndDate
GROUP BY E.EmployeeID,DATEADD(dd, 0, DATEDIFF(dd, 0, INOUT_Time ))
UPDATE @formatTable
SET TotalHrs=Convert(varchar(20),DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60))
,[Status] =(CASE WHEN DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60) >= 4.5
THEN 'P'
ELSE 'Abs' END )
,HoursStatus = (CASE WHEN DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60) >= 8.5
THEN 'Greater Than 8.5'
WHEN DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60) BETWEEN 6 AND 8.49
THEN '6-8.49'
WHEN DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60) BETWEEN 4.5 AND 5.99
THEN '4.5-5.99' end)
WHERE [Date]=@currStartDate
-- moving to nextday
SELECT @currStartDate=DATEADD(DAY,1,@currStartDate)
SELECT @currEndDate=DATEADD(DAY,1,@currEndDate)
END
IF OBJECT_ID('tempdb..##output') IS NOT NULL
DROP TABLE ##output
SELECT EmployeeCode,EmployeeName,[Date],Convert(varchar(10),INTime,108) INTime,Convert(varchar(10),Outtime,108) Outtime,TotalHrs,[Status],Convert(varchar,@startDate,105) as StartDate,Convert(varchar,@endDate,105) as EndDate, (SELECT COUNT(*) FROM @formatTable counter
WHERE ft.EmployeeCode = counter.EmployeeCode AND counter.[Status] = 'P' ) AS TotalPresent, (SELECT COUNT(*) FROM @formatTable counter
WHERE ft.EmployeeCode = counter.EmployeeCode AND counter.HoursStatus = 'Greater Than 8.5' ) as gt8point5,(SELECT COUNT(*) FROM @formatTable counter
WHERE ft.EmployeeCode = counter.EmployeeCode AND counter.HoursStatus = '6-8.49' ) as gt6lessthan8,(SELECT COUNT(*) FROM @formatTable counter
WHERE ft.EmployeeCode = counter.EmployeeCode AND counter.HoursStatus = '4.5-5.99' ) as gt4point5lessthan6
INTO ##output FROM @formatTable as ft
GROUP BY EmployeeCode,EmployeeName,[Date],TotalHrs,INTime,Outtime,[Status]
SELECT * FROM ##output AS AttendanceReport
Any suggestion for improving the performance, especially the day counting logic
A quick look at your query suggest the following indexes.
-- Assuming you don't have EmployeeID as a clustered index
CREATE INDEX IX_Employees_EmployeeID
ON Employees (EmployeeID)
INCLUDE (FirstName, LastName)
CREATE INDEX IX_Attendance_EmployeeID_INOUTTime
ON Attendance (EmployeeID, INOUT_Time)
You could also create an index for @formatTable
, but that depends what your query plan states and how many rows are generated.