如何分组时间段并检查休息时间

时间:2013-12-18 15:02:28

标签: sql sql-server sql-server-2008 tsql sql-server-2005

我有一个存储功能,可以提取所有员工的时钟信息。我正试图提出一份例外报告来审核午餐。我当前的查询一次构建所有信息1段。

SELECT        ftc.lEmployeeID, ftc.sFirstName, ftc.sLastName, ftc.dtTimeIn,
              ftc.dtTimeOut, ftc.TotalHours, ftc.PunchedIn, ftc.Edited
FROM          dbo.fTimeCard(@StartDate, @EndDate, @DeptList,
                            @iActive, @EmployeeList) AS ftc
              LEFT OUTER JOIN Employees AS e ON ftc.lEmployeeID = e.lEmployeeID
WHERE        (ftc.TotalHours >= 0) AND (ftc.DID IS NOT NULL) OR
                         (ftc.DID IS NOT NULL) AND (ftc.dtTimeOut IS NULL)

此输出如下所示:

24  Bob bibby   8/2/2013 11:55:23 AM    8/2/2013 3:36:44 PM 3.68
24  bob bibby   8/2/2013 4:10:46 PM 8/2/2013 8:14:30 PM 4.07
39  rob blah    8/2/2013 8:01:57 AM 8/2/2013 5:01:40 PM 9.01
41  john    doe 8/2/2013 10:09:58 AM    8/2/2013 1:33:38 PM 3.4 
41  john    doe 8/2/2013 1:55:56 PM 8/2/2013 6:10:15 PM 4.25

我需要查询才能做两件事。

1)每天将这些细分组合在一起。 2)在新列中报告“休息时间”

获得该信息后,我需要检查每个细分的小时数,并确保发生两件事。

1)如果他们共工作了6个小时,他们会休息30分钟吗? 2)如果他们休息一下,他们会休息一下吗? 30分钟。

你看到鲍勃在上午11点55分打了一拳,并在3点36分打了一顿午餐。他于4点10分午餐时冲了上来,并于8点14分拳打脚踢。他共工作了7.75小时,并且休息了34分钟。 他在这里很好。我不想报告例外

约翰总共工作了7.65个小时。然而,当他拳打脚踢时,他只吃了22分钟的午餐。我需要报告“吉姆只吃了22分钟的午餐”

你也会看到抢劫工作了9个小时,没有休息。我需要报告“抢劫工作超过6小时并且没有休息”

我认为如果我可以完成对两个细分的分组。然后我可以处理报告方面。

*更新**

我更改了查询以尝试完成此操作。以下是我目前的疑问:

SELECT        ftc.lEmployeeID, ftc.sFirstName, ftc.sLastName, ftc.TotalHours, DATEDIFF(mi, MIN(ftc.dtTimeOut), MAX(ftc.dtTimeIn)) AS Break_Time_Minutes
FROM            dbo.fTimeCard(@StartDate, @EndDate, @DeptList, @iActive, @EmployeeList) AS ftc LEFT OUTER JOIN
                         Employees AS e ON ftc.lEmployeeID = e.lEmployeeID
WHERE        (ftc.TotalHours >= 0) AND (ftc.DID IS NOT NULL) OR
                         (ftc.DID IS NOT NULL) AND (ftc.dtTimeOut IS NULL)
GROUP BY ftc.lEmployeeID, ftc.sFirstName, ftc.sLastName, ftc.TotalHours

我的输出目前看起来像这样:

24  Bob bibby   3.68    -221
24  bob bibby   4.07    -244
39  rob blah    0.05    -3
39  rob blah    2.63    -158
41  john    doe 3.4 -204
41  john    doe     4.25    -255

正如您所看到的那样,它并没有按日期组合细分,而Break_time显示的是负分钟。它也没有结合日子。鲍勃的时间应该在1行。并且显示7.75分钟的休息时间应该是34分钟。

7 个答案:

答案 0 :(得分:4)

我相信如果你想要将两个时间结合起来,你需要将它们从组中取出并加上它们。根据结果​​,报告可以检查总小时数和休息时间。如果要标记它们,可以添加case语句。

SELECT  ftc.lEmployeeID
       ,ftc.sFirstName
       ,ftc.sLastName
       ,SUM(ftc.TotalHours) AS TotalHours
       ,DATEDIFF(mi, MIN(ftc.dtTimeOut), MAX(ftc.dtTimeIn)) AS BreakTimeMinutes
FROM dbo.fTimeCard(@StartDate, @EndDate,
                   @DeptList, @iActive,@ EmployeeList) AS ftc
WHERE SUM(ftc.TotalHours) >= 0 AND (ftc.DID IS NOT NULL) OR
                     (ftc.DID IS NOT NULL) AND (ftc.dtTimeOut IS NULL)
GROUP BY ftc.lEmployeeID, ftc.sFirstName, ftc.sLastName

我在sql中进行了这个快速测试,它似乎按照你想要的方式工作。你有没有给小组添加一些东西?

declare @table table (emp_id int,name varchar(4), tin time,tout time);

insert into @table
VALUES (1,'d','8:30:00','11:35:00'),
    (1,'d','13:00:00','17:00:00');


SELECT t.emp_id
      ,t.name
      ,SUM(DATEDIFF(mi, tin,tout))/60 as hours
      ,DATEDIFF(mi, MIN(tout), MAX(tin)) AS BreakTimeMinutes
FROM @table t

GROUP BY t.emp_id, t.name

答案 1 :(得分:2)

使用示例SQL的相关部分,我创建了一个SQL Fiddle,显示了如何完成此操作。您可以在此处查看:http://sqlfiddle.com/#!6/f05ce/3

SELECT EmployeeId, Num_Hours, 
 CASE WHEN tmp.Break_Time_Minutes < 0 Then 0 Else Break_Time_Minutes END As Break_Time_Minutes, 
 CASE WHEN tmp.Break_Time_Minutes < 0 Then 1 Else 0 END As SkippedBreak
 FROM (
 SELECT EmployeeId, 
 Round(SUM(DATEDIFF(second, TimeIn, TimeOut) / 60.0 / 60.0),1) As NUM_Hours,
 DateDiff(mi, Min(TimeOut), Max(TimeIn)) As Break_Time_Minutes  FROM Employee 
  GROUP BY EmployeeId, CAST(TimeIn As Date)
) as tmp WHERE tmp.Num_Hours > 6 AND Break_Time_Minutes < 30

答案 2 :(得分:2)

自包含示例:

这是一个如何工作的例子。
我使用的独立逻辑不依赖于您自定义的功能,因为此处的社区无法访问它(包括它使用的数据)。
相反,我建立了你设法提供的“输出”的答案 这将自包含,因为没有对象依赖。

DECLARE @EmpClock Table
(
    EmployeeID Int,
    FirstName  VarChar(50),
    LastName   VarChar(50),
    PunchIn    DateTime,
    PunchOut   DateTime
)
INSERT INTO @EmpClock
    SELECT 24,'Bob','bibby','8/2/2013 11:55:23 AM','8/2/2013 3:36:44 PM' UNION
    SELECT 24,'bob','bibby','8/2/2013 4:10:46 PM','8/2/2013 8:14:30 PM' UNION
    SELECT 39,'rob','blah','8/2/2013 8:01:57 AM','8/2/2013 5:01:40 PM' UNION
    SELECT 41,'john','doe','8/2/2013 10:09:58 AM','8/2/2013 1:33:38 PM' UNION
    SELECT 41,'john','doe','8/2/2013 1:55:56 PM','8/2/2013 6:10:15 PM' UNION
    SELECT 1,'Mike','TeeVee','8/2/2013 12:05:30 PM','8/2/2013 2:15:45 PM' UNION
    SELECT 1,'Mike','TeeVee','8/2/2013 2:25:05 PM','8/2/2013 3:35:25 PM' UNION
    SELECT 1,'Mike','TeeVee','8/2/2013 3:50:15 PM','8/2/2013 5:30:55 PM' UNION
    SELECT 1,'Mike','TeeVee','8/2/2013 5:40:35 PM','8/2/2013 6:50:20 PM'
SELECT *,
       DATEDIFF(SECOND, '', EC.WorkedTotal)/60.0/60.0[WorkedHours],
       DATEDIFF(SECOND, '', EC.BreakTotal )/60.0     [BreakMinutes]
  FROM
  (
    SELECT EC.*,
           DATEADD(SECOND, DATEDIFF(SECOND, WorkedTotal, WorkPeriod), CAST('' as Time(0)))[BreakTotal]
      FROM
      (
        SELECT EC.EmployeeID, EC.EmployeeName, EC.Day,
               DATEADD(SECOND, DATEDIFF(SECOND, EC.FirstPunchIn, EC.LastPunchOut), CAST('' as Time(0)))[WorkPeriod],
               DATEADD(SECOND, EC.Worked, CAST('' as Time(0)))[WorkedTotal]
          FROM
          (
            SELECT EC.EmployeeID,
                   (EC.FirstName + ' ' + EC.LastName)[EmployeeName],
                   --"Day" Assumes Punches do not span across midnight.
                   --  If any do, then the day of the Punch-In will be used.
                   CAST(EC.PunchIn as Date)[Day],
                   SUM(DATEDIFF(SECOND, EC.PunchIn, EC.PunchOut))[Worked],
                   MIN(EC.PunchIn)[FirstPunchIn],
                   MAX(EC.PunchOut)[LastPunchOut]
              FROM @EmpClock as EC
             GROUP BY EC.EmployeeID, (EC.FirstName + ' ' + EC.LastName), CAST(EC.PunchIn as Date)
          ) AS EC
      ) AS EC
  ) AS EC
 WHERE EC.BreakTotal  > DATEADD(MINUTE, 30, CAST('' as Time(0)))
   AND EC.WorkedTotal > DATEADD(HOUR,    6, CAST('' as Time(0)))

结果:

注意我为名为“ Mike TeeVee ”的虚构员工添加了更多数据。
我是这样做的,以防你有一个员工休息休息或因任何原因需要紧急休息 这允许我们测试逻辑如何处理这些情况。

如果没有Where-Clause,我们会看到: enter image description here

按原样运行(使用Where-Clause),您将看到它正确过滤掉结果: enter image description here

您会注意到我将结果的Time-DataType格式显示为“ WorkedTotal ”和“ BreakTotal ”。我更喜欢这个用于显示目的,因为我们通常不会将小时视为整体100%的分数,而是将小时和剩余分数视为分钟。

我继续将分数小时和分数分钟分别包括为“ WorkedHours ”和“ BreakMinutes ”,以防您的要求需要以此格式进一步计算

WorkPeriod ”表示班次的持续时间(包括休息时间)。
我确定你不需要这些信息,但我把它包括在内是为了完整。


使用您自己的逻辑:

从这里的其他答案来看,我发现你在将你的逻辑融入他们的答案时遇到了问题,所以我也为你做了这个。
以下脚本只能在您的环境中运行:

SELECT *,
       DATEDIFF(SECOND, '', EC.WorkedTotal)/60.0/60.0[WorkedHours],
       DATEDIFF(SECOND, '', EC.BreakTotal )/60.0     [BreakMinutes]
  FROM
  (
    SELECT EC.*,
           DATEADD(SECOND, DATEDIFF(SECOND, WorkedTotal, WorkPeriod), CAST('' as Time(0)))[BreakTotal]
      FROM
      (
        SELECT EC.EmployeeID, EC.EmployeeName, EC.Day,
               DATEADD(SECOND, DATEDIFF(SECOND, EC.FirstPunchIn, EC.LastPunchOut), CAST('' as Time(0)))[WorkPeriod],
               DATEADD(SECOND, EC.Worked, CAST('' as Time(0)))[WorkedTotal]
          FROM
          (
            SELECT EC.EmployeeID,
                   (EC.FirstName + ' ' + EC.LastName)[EmployeeName],
                   --"Day" Assumes Punches do not span across midnight.
                   CAST(EC.PunchIn as Date)[Day],
                   SUM(DATEDIFF(SECOND, EC.PunchIn, EC.PunchOut))[Worked],
                   MIN(EC.PunchIn)[FirstPunchIn],
                   MAX(EC.PunchOut)[LastPunchOut]
              FROM
              ( --I replaced my table variable @EmpClock with a call to your own logic.
                SELECT EC.lEmployeeID[EmployeeID], EC.sFirstName[FirstName], EC.sLastName[LastName],
                       EC.dtTimeIn[PunchIn], EC.dtTimeOut[PunchOut]
                       --You have these in your original query, but the values are missing in your "output".
                       --,EC.TotalHours, EC.PunchedIn, EC.Edited
                  FROM dbo.fTimeCard(@StartDate, @EndDate, @DeptList, @iActive, @EmployeeList) as EC
                  LEFT JOIN Employees as E
                    ON EC.lEmployeeID = E.lEmployeeID
              ) AS EC
             GROUP BY EC.EmployeeID, (EC.FirstName + ' ' + EC.LastName), CAST(EC.PunchIn as Date)
          ) AS EC
      ) AS EC
  ) AS EC
 WHERE EC.BreakTotal  > DATEADD(MINUTE, 30, CAST('' as Time(0)))
   AND EC.WorkedTotal > DATEADD(HOUR,    6, CAST('' as Time(0)))


可能的错误:

我还注意到你的where子句有问题:

 WHERE (ftc.TotalHours >= 0) AND (ftc.DID IS NOT NULL) OR
       (ftc.DID IS NOT NULL) AND (ftc.dtTimeOut IS NULL)

目前还不清楚你在这里尝试“ OR ”是什么,并且你有两次列出“ ftc.DID IS NOT NULL ”。
您可能希望查看该逻辑,并在使用OR时考虑正确使用括号 由于这种混淆,我在上面的例子中省略了这个逻辑。

你偶然的意思是这个吗?:

 WHERE ftc.DID IS NOT NULL
   AND (ftc.TotalHours >= 0 OR ftc.dtTimeOut IS NULL)

答案 3 :(得分:1)

试试这个: -

 SELECT ftc.lEmployeeID,sum(ftc.TotalHours)as TotalHours,
 ABS(DATEDIFF(mi, MIN(convert(datetime,ftc.dtTimeOut,9)), MAX(convert(datetime,ftc.dtTimeIn,9)))) AS Break_Time_Minutes

   FROM dbo.fTimeCard(@StartDate, @EndDate, @DeptList, @iActive, @EmployeeList) AS ftc 
   LEFT OUTER JOIN
    Employees AS e ON ftc.lEmployeeID = e.lEmployeeID
   WHERE (ftc.TotalHours >= 0) AND (ftc.DID IS NOT NULL) OR
      (ftc.DID IS NOT NULL) AND (ftc.dtTimeOut IS NULL)


group by ftc.EmployeeiD,DATE(ftc.dtTimeIn)

答案 4 :(得分:1)

很抱歉写在这里。内容可能很长。 你的问题仍然没有解决,但它似乎很容易,因为你强加给我们 dbo.fTimeCard(@StartDate,@ EndDate,@ DeptList,@ iActive,@ EmployeeList)。 我们不知道ftc.TotalHours是否已经是某事物的总和或它是什么。 你要做的只是在表变量中显示表结构和一些数据。你的解释是很好的enuf。 其次我想指出的是,你永远不会通过id,firstname,lastname 来做分组。 group by id绰绰有余。所以在你的情况下你需要CTE.i不能写整个查询因为某些东西是表格而且数据不清楚。

;with CTE as
(SELECT ftc.lEmployeeID, ftc.sFirstName, ftc.sLastName, ftc.dtTimeIn, ftc.dtTimeOut, ftc.TotalHours, ftc.PunchedIn, ftc.Edited
FROM    dbo.fTimeCard(@StartDate, @EndDate, @DeptList, @iActive, @EmployeeList) AS ftc LEFT OUTER JOIN
    Employees AS e ON ftc.lEmployeeID = e.lEmployeeID
WHERE   (ftc.TotalHours >= 0) AND (ftc.DID IS NOT NULL) OR
    (ftc.DID IS NOT NULL) AND (ftc.dtTimeOut IS NULL)
)
,CTE1 as
(
SELECT        ftc.lEmployeeID

          ,SUM(ftc.TotalHours) AS TotalHours
           MIN(ftc.dtTimeOut) MindtTimeOut, MAX(ftc.dtTimeIn) AS MAXdtTimeIn
FROM dbo.fTimeCard(@StartDate, @EndDate, @DeptList, @iActive, @EmployeeList) AS ftc

WHERE SUM(ftc.TotalHours) >= 0 AND (ftc.DID IS NOT NULL) OR
                     (ftc.DID IS NOT NULL) AND (ftc.dtTimeOut IS NULL)
GROUP BY ftc.lEmployeeID
)
,CTE2 as
(
 join cte and cte1 on employeeid--well it depend
)
select * from cte2--this is just indicative.

答案 5 :(得分:1)

使用CTE对分段进行分组,您可以查询此CTE以检查中断持续时间。 在最终的CASE WHEN语句中添加其他检查:

; with
DailyRecords as 
(   -- add a rownumber to each entry by employee/day
    select ROW_NUMBER() over (partition by ftc.lEmployeeId, cast(ftc.dtTimeIn as date)
                            order by ftc.lEmployeeId, ftc.dtTimeIn ) as rownum,
        cast(ftc.dtTimeIn as date) as [Day],
        ftc.lEmployeeID, ftc.sFirstName, ftc.sLastName, ftc.dtTimeIn, ftc.dtTimeOut, ftc.TotalHours
    from fTimeCard(@StartDate, @EndDate, @DeptList, @iActive, @EmployeeList) ftc
),
DailyRequest as
(   -- group 2 segments together (rownum 1 and 2), and report break time in a new column
    select Segment1.lEmployeeId, Segment1.sFirstName, Segment1.sLastName, 
        Segment1.[Day], 
        coalesce(round(datediff(mi,Segment1.dtTimeIn, Segment1.dtTimeOut) / 60.0, 2),0) as Duration1,
        coalesce(round(datediff(mi,Segment2.dtTimeIn, Segment2.dtTimeOut) / 60.0, 2), 0) as Duration2,
        coalesce(round(datediff(mi,Segment1.dtTimeOut,Segment2.dtTimeIn),2),0)  as BreakDuration
    from DailyRecords Segment1
    left join DailyRecords Segment2 
        on segment1.lEmployeeID = Segment2.lEmployeeID
        and Segment1.[Day] = Segment2.[Day]
        and Segment2.rownum = 2
    where Segment1.rowNum= 1
)
    -- make report from DailyRequest with remarks
select lEmployeeId, sFirstName, sLastName, [Day], Duration1, Duration2, BreakDuration,
    case 
    when Duration1+Duration2 >= 6 and BreakDuration = 0 then 'No Break'
    when Duration1+Duration2 >= 6 and BreakDuration < 30 then ltrim(str(coalesce(BreakDuration, 0))) +' mn break'
    when Duration1+Duration2 >= 6 and BreakDuration > 35 then ltrim(str(coalesce(BreakDuration, 0))) +' mn break'
    end as Remarks  
from DailyRequest D

如果需要,添加LEFT OUTER JOIN Employee(在您的要求中没有使用它,我将其删除)

答案 6 :(得分:0)

确定休息时间的快捷方式,是最后一次退房和第一次办理登机手续,然后您有时间总计。它与ftc.TotalHours之间的差异是所用的休息时间。然后你可以添加一些代码来报告异常。 如果选择了多天,则下面的代码将不起作用,但是我们需要的只是一个日期变量,并将日期变量添加到GROUP BY子句中。

SELECT out.lEmployeeID, out.sFirstName, out.sLastName, 
CASE WHEN (out.TotalHours > 6 AND out.Break_Time_Minutes < 30) THEN 
'report exception' ELSE 0 END AS Exception_Status
FROM
( SELECT ftc.lEmployeeID, ftc.sFirstName, ftc.sLastName, 
ABS(DATEDIFF(mi, MAX(ftc.dtTimeOut), MIN(ftc.dtTimeIn))) - SUM(ftc.TotalHours) * 60
AS Break_Time_Minutes, SUM(ftc.TotalHours) AS TotalHours
FROM dbo.fTimeCard(@StartDate, @EndDate, @DeptList, @iActive, @EmployeeList) AS ftc 
LEFT OUTER JOIN Employees AS e ON ftc.lEmployeeID = e.lEmployeeID
WHERE        (ftc.TotalHours >= 0) AND (ftc.DID IS NOT NULL) OR
(ftc.DID IS NOT NULL) AND (ftc.dtTimeOut IS NULL) 
GROUP BY ftc.lEmployeeID) as out