【问题标题】:How to Group time segments and check break time如何分组时间段并检查休息时间
【发布时间】:2014-01-06 19:57:16
【问题描述】:

我有一个存储功能,可以提取所有员工的时钟信息。我正在尝试提取异常报告来审核午餐。我当前的查询一次构建所有信息 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)在新列中报告“休息时间”

获得该信息后,我需要检查每个部分的时间并确保发生 2 件事。

1) 如果他们总共工作了 6 个小时,他们有 30 分钟的休息时间吗? 2)如果他们休息了,他们是否休息> 30分钟。

您会看到 Bob 在上午 11:55 打卡并在 3:36 打卡出去吃午饭。他 4 点 10 分吃完午饭回来打卡, 8 点 14 分下班。他总共工作了 7.75 小时,休息了 34 分钟。 他在这里很好。而且我不想报告异常

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

您还会看到 rob 连续工作了 9 个小时。我需要报告“抢劫工作超过 6 小时,没有休息”

我想如果我可以完成对 2 个细分的分组。然后我可以处理报告方面的问题。

*更新**

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

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 显示负分钟数。它也没有结合日子。 Bob 的时间应该在 1 行。并显示 7.75 分钟的休息时间应该是 34 分钟。

【问题讨论】:

  • 首先您使用 datediff(minute,outtime,intime) 来获取更多列。然后再次按用户名对 datediff 组求和。然后使用 case 语句进行备注。以正确的格式显示数据以及列名。
  • 我要合并段...我只想报告那些休息时间少于30分钟的人,包括那些根本没有休息的人。我还需要报告他们在没有充分休息的情况下工作的总小时数

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


【解决方案1】:

我相信,如果您想合并两次,则需要将它们从组中取出并相加。根据结果​​,报告可以检查总小时数和休息时间。如果你想标记它们,你可以添加 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

【讨论】:

  • 这结合了它们,但不修复breaktimeminutes。它仍将休息时间显示为当天工作的总分钟数。例如。第一位员工工作了 7.75 小时。我将 SUM(FTC.TOTALHOURS) 移到了一个有条款顺便说一句。 Breaktime 显示“465”分钟的休息时间...
  • 哦,我知道您需要第一条记录结束时间和下一条记录开始时间之间的时间。
  • 我编辑为使用最大时间和最小超时,如果它们返回为负数,您可以切换我认为的两个参数。
  • 这再次将小时分开,并将休息时间显示为第一段,然后是第二段
  • 如果每个 emp 有超过 2 条记录,但是 breaktimeminutes 将不起作用。
【解决方案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

【讨论】:

  • 非常感谢您的帮助。问题是,我的信息不作为表格存在,所以我无法直接访问它。信息来自一个函数。我需要能够从我的查询中运行您的查询,所有这些都在同一个查询中。我无法执行存储过程,因为数据库已同步,我无权访问已更改的同步信息。
  • 您可以将初始查询用作子查询。我在上面写了“FROM Employee”的地方,说“FROM (your-query-here) as Employee”
  • 对不起,我试过了,并以多种方式改变了它。我无法让查询一起工作。我相信我的 join 和 where 语句可能与您搜索和排序的方式相冲突。我也试图改变它,没有运气。
【解决方案3】:

独立示例:

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

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 子句,我们会看到:

按原样运行(使用 Where 子句),您会看到它正确地过滤掉了结果:

您会注意到我将结果的 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)

【讨论】:

    【解决方案4】:

    试试这个:-

     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)
    

    【讨论】:

    • 这似乎不太对劲。当我尝试更正您的查询时,它会引发多个错误
    【解决方案5】:

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

    ;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.
    

    【讨论】:

      【解决方案6】:

      使用 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(在你的需求中没有使用它,我删除了它)

      【讨论】:

        【解决方案7】:

        确定休息时间的捷径,即最后一次退房和第一次入住,然后你就有了总时间。这与 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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-03-22
          • 2015-02-16
          • 1970-01-01
          • 2017-04-07
          • 1970-01-01
          • 2014-03-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多