SQL Server 2008 R2,由Department,Employee和Workdate分开常规和加班时间

时间:2015-03-17 20:53:48

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

我正在尝试为人力资源部门编写一份报告,按部门,员工和工作日分割常规和加班时间,我真的不确定如何开始。

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

-- 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 - 每笔交易的总小时数
  7. 现在,我们的一些员工将在一天之内更换部门,当他们从旧部门退出时,然后再回到不同的部门。

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

    由department和work_date总结为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 02/26/2016,或8个常规和1.3加时,这相当于9.6小时。现在我需要将1.3小时的加班时间分配给work_date的第一个或最后一个部门,这个加班分配由班次代码确定,如果是'Day'则将加班分配给最后一个部门,否则分配到第一个部门。那一天。

    这当然很复杂,因为员工可能在A部门工作8.5小时,然后再转到B部门再工作一小时。结果必须是部门A的8小时注册,部门B的加班时间为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
    

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

    我现在可以使用Shift代码来确定是否将OT分配给工作日的第一个或最后一个事务。

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

    是否有更简洁的方法可以根据start_time以及我正在使用的CASE表达式将OT分配到最后一次或第一次交易?

2 个答案:

答案 0 :(得分:1)

丑陋,不会一直有效,但有类似的东西:

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

答案 1 :(得分:1)

如果您不了解分析,我不确定我是否可以向您解释。我对数据库概念非常了解,但是在他们最终开始沉沦之前,我只需要一次又一次地使用分析。我仍然不喜欢使用它们,因为虽然它们通常很方便,但它们需要 sooo

这个想法是,您必须在每个特定日期为每位员工运行总计小时数。然后有一个列忽略每天运行的总计,直到它超过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小时的时间段内。我希望这足以让你开始。