【问题标题】:SQL Server 2008 R2, separate regular and overtime hours by Department, Employee and WorkdateSQL Server 2008 R2,按部门、员工和工作日期分开正常和加班时间
【发布时间】:2015-03-17 20:53:47
【问题描述】:

我正在尝试为 HR 编写一份报告,按部门、员工和工作日期划分正常和加班时间,但我真的不知道如何开始。

以下创建一个临时表,其中包含一系列示例数据,这些数据反映了我将使用的实际数据。

-- Create Temp Tables --
CREATE TABLE #Employee_Hours
(
        emp_num     nvarchar(20)
        ,dept       nvarchar(20)
        ,shift      nvarchar(3)
        ,work_date  Date
        ,start_time DateTime
        ,hrs        Decimal(19,8)
);

-- Populate #Employee_Hours with sample hours
INSERT #Employee_Hours(emp_num, dept, shift, work_date, start_time, hrs)
VALUES ('W-C0001', 'Admin', 'Day', '2015-02-22', '2015-02-22 06:30:00.000', 7.9)
      ,('W-C0001', 'Admin', 'Day', '2015-02-24', '2015-02-24 06:45:00.000', 8.6)
      ,('W-C0001', 'Admin', 'Day', '2015-02-26', '2015-02-26 08:00:00.000', 2.0)
      ,('W-C0001', 'Admin', 'Day', '2015-02-26', '2015-02-26 10:00:00.000', 0.5)
      ,('W-C0001', 'Asmb', 'Day', '2015-02-26', '2015-02-26 11:00:00.000', 1.33333)
      ,('W-C0001', 'Shop A', 'Day', '2015-02-26', '2015-02-26 11:30:00.000', 5.5)
      ,('G-C0000', 'Admin', 'Day', '2015-02-22', '2015-02-22 08:00:00.000', 4.5)
      ,('G-C0000', 'Admin', 'Day', '2015-02-22', '2015-02-22 12:30:00.000', 4.0)
      ,('G-C0000', 'Admin', 'Day', '2015-02-23', '2015-02-23 07:30:00.000', 8.25)
      ,('G-C0000', 'Admin', 'Day', '2015-02-24', '2015-02-24 08:30:00.000', 8.9)
      ,('W-R0000', 'Eng', 'Day', '2015-02-22', '2015-02-22 08:30:00.000', 8.02)
      ,('W-R0000', 'Eng', 'Day', '2015-02-24', '2015-02-24 09:30:00.000', 8.75)
GO

    SELECT * FROM #Employee_Hours

    DROP TABLE #Employee_Hours

数据表有五列:

  1. emp_num --员工编号
  2. dept --交易部
  3. shift --事务转移
  4. work_date --交易工作日期
  5. start_time --事务开始时间
  6. hrs --每笔交易的总小时数

现在,我们的一些员工会在一天中更换部门,他们会在原来的部门打卡,然后在另一个部门打卡。

请注意 emp_num 'W-C0001' 的交易,该员工在 work_date 02/26/2015 有四笔交易。我们的加班时间超过每个工作日 8 小时都是加班,这就是我的问题。

按部门和工作日期对 emp_num 'W-C0001' 进行汇总,示例数据如下所示:

 work_date  |  dept  | hrs
 02/26/2015   Admin    2.5
 02/26/2015   Asmb     1.3
 02/26/2015   Shop A   5.5

这在 work_date 2016 年 2 月 26 日加起来最多 9.6 小时,或 8 个正常加班和 1.3 个加班。现在我需要将 1.3 小时的加班分配给 work_date 的第一个或最后一个部门,这个加班分配由班次代码决定,如果是“天”,则将加班分配给最后一个部门,否则分配给第一个部门那天。

这当然很复杂,因为员工可能在 A 部门工作 8.5 小时,然后转到 B 部门再工作一个小时。结果必须是 A 部门 8 小时 reg,A 部门 0.5 小时加班和 B 部门 1 小时加班。

使用上述示例,我需要的最终选择语句的结果是:

  work_date   |  dept  |  hrs_reg  |  hrs_ot
 02/26/2015      Admin      2.5         0
 02/26/2015      Asmb       1.3         0
 02/26/2015      Shop A     4.2        1.3

更新 我想感谢@TommCatt。我正在使用start_time 列来帮助确定哪个部门得到加班。我将它添加到我的临时表中的示例数据中。

我稍微修改了他的答案,它似乎给了我想要的东西。我包含了start_time 列,并在CASE 表达式中使用它来将加班分配给MAX(start_time)

WITH EmpOvertime
(
    emp_num
    ,dept
    ,shift
    ,work_date
    ,start_time
    ,hrs
    ,DailyTotal
    ,OTHours
)
AS
(
    SELECT h.emp_num
        ,h.dept
        ,h.shift
        ,h.work_date
        ,h.start_time
        ,h.hrs
        ,SUM(h.hrs) OVER(PARTITION BY h.emp_num, h.work_date)
        ,CASE 
            WHEN Sum(h.hrs) OVER(PARTITION BY h.emp_num, h.work_date) - 8 > 0
            THEN Sum(h.hrs) OVER(PARTITION BY h.emp_num, h.work_date) - 8
            ELSE 0
         END
    FROM #Employee_Hours AS h
)
SELECT emp_num
    ,dept
    ,shift
    ,work_date
    ,hrs
    ,DailyTotal
    ,CASE -- Only subract overtime from the last entry for the work day
        WHEN start_time = (SELECT MAX(h2.start_time)
                                FROM #Employee_Hours AS h2
                                WHERE h2.emp_num = ot.emp_num
                                    AND h2.work_date = ot.work_date)
        THEN hrs - OTHours
        ELSE hrs
    END AS RegHours
    ,CASE -- Only allocate overtime to the last entry for the work day
        WHEN start_time = (SELECT MAX(h2.start_time)
                                FROM #Employee_Hours AS h2
                                WHERE h2.emp_num = ot.emp_num
                                    AND h2.work_date = ot.work_date)
        THEN OTHours
        ELSE 0
    END AS OTHours
FROM EmpOvertime AS ot
ORDER BY ot.emp_num, ot.work_date;

结果如下:

emp_num dept    shift work_date     hrs     DailyTotal  RegHours    OTHours
------- -----   ----  ----------    ---     ---         ---         ---
G-C0000 Admin   Day   2015-02-22    4.5     8.5         4.5         0.0
G-C0000 Admin   Day   2015-02-22    4.0     8.5         3.5         0.5
W-C0001 Admin   Day   2015-02-22    7.9     7.9         7.9         0.0
W-C0001 Admin   Day   2015-02-24    8.6     8.6         8.0         0.6
W-C0001 Admin   Day   2015-02-26    2.0     9.3         2.0         0.0
W-C0001 Admin   Day   2015-02-26    0.5     9.3         0.5         0.0
W-C0001 Asmb    Day   2015-02-26    1.3     9.3         1.3         0.0
W-C0001 Shop A  Day   2015-02-26    5.5     9.3         4.1         1.3

请注意,work_date '2015-02-26' 的员工 'W-C0001'RegHours 列现在如何显示所有行的完整小时数在那个 work_date 中,除了减去 OT 的最后一个条目。与OTHours 列相同,该work_date 中的行为零,最后一行除外。

我现在可以使用 Shift 代码来确定是将 OT 分配到工作日的第一笔交易还是最后一笔交易。

直到现在我还没有使用过 OVER(PARTITION BY ),但能够从 TomCatt 的回答中弄清楚它们是如何工作的。

是否有一种更简洁的方法可以根据start_time 然后是我使用的CASE 表达式将 OT 分配给最后一个或第一个事务?

【问题讨论】:

  • 您需要在查询中执行此操作吗?我确信可以做到,但在代码中似乎会更容易。
  • 您如何确定哪个部门在当天排在第一位?是否存在您未包含的 ID,这意味着较早的 ID 较低?
  • @regeter,是的,对不起,我忘记了。有一个名为 start_time 的 DateTime 列可用于确定工作日中的第一个或最后一个部门。
  • @Jen R 是的,查询将提供 SSRS 报告。
  • @Paul Griffin 我已经尝试了一些事情,一个查询,我将员工、部门和工作日期的小时数汇总到一个临时表中。但是,当一名员工在不同部门进行多项交易时,我不知道如何将正常和加班与工作日分开。

标签: sql sql-server sql-server-2008-r2 sum


【解决方案1】:

如果您不了解分析,我不确定能否向您解释。我对数据库概念非常精通,但我不得不一遍又一遍地使用分析,然后才开始深入了解它们。我仍然不喜欢使用它们,因为虽然它们通常很方便,但它们需要 sooo em> 进行大量测试以确保您得到正确的答案。

我们的想法是,您必须为每位员工在每一天计算总工作时数。然后有一列忽略每天运行的总计,直到超过 8 小时。将其输入到另一个查询中,该查询将时间分为常规时间和加班时间。

SQLFiddle

with
EmpOvertime( emp_num, dept, shift, work_date, hrs, RunningHrs, OTHours )as(
    select  h.emp_num, h.dept, h.shift, h.work_date, h.hrs,
            Sum( h.hrs )over( partition by h.emp_num, h.work_date order by h.hrs ),
            case when Sum( h.hrs )over( partition by h.emp_num, h.work_date order by h.hrs ) - 8 > 0
                then Sum( h.hrs )over( partition by h.emp_num, h.work_date order by h.hrs ) - 8
                else 0
            end
    from    Employee_Hours  h
)
select  emp_num, dept, shift, work_date, hrs ClockedHrs, RunningHrs, hrs - OTHours RegHours, OTHours
from    EmpOvertime ot
order by ot.emp_num, ot.work_date;

我已将 CTE 中 Sum 的原始总数公开为 RunningHrs,因此您可以直接看到正在生成的值。一旦您对查询感到满意,就可以将其删除,因为它不会直接用于最终查询。

这不是您所要求的,但您没有显示任何方法来指定轮班的第一个或最后一个时期。没有办法将其用于查询。因此,加班时间只显示在每日总计开始超过 8 小时的时段。我希望这足以让你开始。

【讨论】:

  • 谢谢!我稍微修改了您的代码,我想我现在可以正常工作了。我已经用修改后的代码更新了我的问题。打算做更多测试,如果遇到问题,我会更新这篇文章。
【解决方案2】:

丑陋,不会总是有效,但类似的东西:

SELECT work_date,
dept,
CASE WHEN ot.tot IS NOT NULL  THEN total - ot.tot ELSE total END AS s,
ot.tot

FROM  (
SELECT eh.work_date, 
eh.dept, 
eo.d,
SUM(eh.hrs) total,
eo.t
FROM #Employee_Hours eh
JOIN (
SELECT work_date,  SUM(hrs) t, MAX(dept) d
FROM #Employee_Hours
GROUP BY work_date
) eo
ON eo.work_date = eh.work_date
WHERE eh.work_date = '2015-02-26'
GROUP BY eh.work_date, eh.dept, eo.d, eo.t
) a
CROSS APPLY (SELECT  CASE WHEN t <= 8 THEN 0 ELSE CASE WHEN d = dept THEN t-8 END END tot) AS ot

【讨论】:

  • 它可以工作,但它有点超出我对 SQL 的理解,所以我不知道它是如何工作的以及什么条件会导致它失败。我将不得不看看我是否能弄清楚它是如何工作的,你能解释一下你的解决方案吗?特别是第一个 FROM 和 CROSS APPLY 中的两个 select 语句,我也从未使用过。
  • CROSS APPLY 仅用作“快捷方式”,如果您将 ot.tot 替换为 CASE WHEN t
猜你喜欢
  • 1970-01-01
  • 2012-07-04
  • 2013-01-13
  • 1970-01-01
  • 2016-10-24
  • 2013-04-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多