Question

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

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top