从SQL表中获取总工作时间

时间:2015-08-20 15:37:06

标签: sql sql-server tsql

我有一个出勤SQL表,用于存储员工的开始和结束日。每次打卡(打卡和打卡)都在一个单独的记录中。

我想计算每个员工在一个请求月份的总工作时间。

我尝试制作一个标量函数,它接受两个日期和员工ID并返回上述任务的计算,但它只计算所有日期之间一个日期的差异。

数据是这样的:

000781  2015-08-14  08:37:00 AM     EMPIN    539309898   
000781  2015-08-14  08:09:48 PM     EMPOUT   539309886    

我的代码是:

@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

3 个答案:

答案 0 :(得分:2)

我认为另一种方法简单而有效。

  • 它不需要像LEAD
  • 这样的现代功能
  • 如果同一个人在同一天进出几次,它可以正常工作
  • 如果这个人在午夜或连续几天都呆在那里,它可以正常工作
  • 如果人在“in”的时段与开始或结束日期时间重叠,则它可以正常工作。
  • 它确实假设数据正确,即每个“in”与“out”匹配,除了可能是最后一个。

以下是时间线的说明。请注意,start时间发生在一个人“进入”时,end时间也发生在一个人还在“进入”时:

我们需要做的就是计算每个事件(inout)和start时间之间的时间差的简单总和,然后为{{1}执行相同的操作} 时间。如果事件为end,则添加的持续时间应为正号,如果事件为in,则添加的持续时间应为负号。最终结果是结束时间的总和与开始时间的总和之间的差异。

out

我建议以分钟计算持续时间,然后将结果除以60得到小时数,但这实际上取决于您的要求。顺便说一句,将日期存储为summing for start: |---| + |----------| - |-----------------| + |--------------------------| - |-------------------------------| + --|====|--------|======|------|===|=====|---|==|---|===|====|----|=====|--- time in out in out in start out in out in end out in out summing for end: |---| + |-------| - |----------| + |--------------| - |------------------------| + |-------------------------------| - |--------------------------------------| + |-----------------------------------------------| - |----------------------------------------------------| + 是个坏主意。

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); ,因为在开始时间之前可能有LEFT JOIN没有记录。

使用Dynamic Search Conditions in T‑SQL时,

EmpID非常有用。如果OPTION(RECOMPILE)@EmpID,您将获得所有人的结果,如果不是NULL,您只会获得一个人的结果。

如果所有人只需要一个号码(总计),请将计算结果包裹在NULL SELECT中。如果您总是想要所有人的总计,那么请完全删除SUM()参数。

@EmpID上建立索引是个好主意。

答案 1 :(得分:1)

我的方法如下:

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

答案 2 :(得分:0)

假设条目已正确配对(in -> out -> in -> out -> in等)。

SQL Server 2012及更高版本:

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及更高版本:

;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