【问题标题】:sql count performance issuesql计数性能问题
【发布时间】:2014-01-19 12:06:56
【问题描述】:

我有两张表用于存储员工考勤详情。

一个表存储 emp Id 以及日期时间信息中的相应时间和时间,第二个表存储其他员工详细信息,如员工 ID、员工姓名等... 我需要生成一份报告,其中显示 emp 每天工作的总小时数,如果总小时数 > 4.5 否则不存在的状态列存储详细信息,例如 Present 还需要统计员工状态为在场的天数,员工总小时数大于6小于8.5的天数。

我已编写查询以获取所有详细信息,但性能无法接受,获取所有详细信息大约需要 30-35 分钟

如果我排除天数计算逻辑,大约需要 1-2 分钟

表结构是

Ist Employee 表 EmployeeID, EmployeeName.....其他细节(暂时不需要)

考勤表

Emp_ID、INOUT_Time

我的查询

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

任何改进性能的建议,尤其是天数计算逻辑

【问题讨论】:

  • 您是否检查过执行计划以验证您使用的索引是否正确?
  • 我试过了,但无法理解执行计划的大部分内容。我的猜测是在计算不同场景的天数逻辑时,我会多次迭代每条记录,这很耗时,因为表包含大量接近 50,000 条的记录
  • 50k 行是简单模式,假设您有正确的索引。分享你的执行计划!
  • 刚才我已经执行了执行计划的查询,一旦执行了查询我将共享相同的
  • 您可以在较小的日期范围内执行它以进行评估,例如一周。

标签: sql sql-server sql-server-2000


【解决方案1】:

快速查看您的查询建议以下索引。

-- 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)

您也可以为@formatTable 创建索引,但这取决于您的查询计划状态以及生成的行数。

【讨论】:

  • index 是问题所在,首先创建了查询计划建议的索引但没有帮助,然后将声明表 @formatTable 更改为 sql 临时表并在它们上创建非聚集索引。现在查询大约需要 30 秒
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-04-13
  • 1970-01-01
  • 1970-01-01
  • 2012-06-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多