如何获得使用SQL Server的员工的总工作时间

时间:2016-03-10 04:13:13

标签: sql-server

我有一个员工出勤表,如下所示

ID     Employee-ID       Date      Coming-time  Leaving-time   Break(in hours)
1         1             2016-01-01   08:00:00     18:30:00       0,50 
2         1             2016-01-02   20:00:00     08:00:00       1,50
3         1             2016-01-03   18:30:00     06:00:00       1,50
4         1             2016-01-04   08:00:00     18:30:00       0,00

如何在没有休息的情况下计算每个员工的总工作时间.....

问题在于DATEDIFF几小时没有给我半小时的真正价值,例如以下查询:

ID     Employee-ID       Month      TotalHours  WorkingTime   
1        1                12            10          9,5     
2        1                12            12          10,5     
3        1                12            12          10,5     
4        1                12            10          10   

正如您所看到的,TotalHours错误,其值应该是这样的

TotalHours
   10,5
   12
   11,5
   10,5

我正在尝试的查询是这样的:(感谢答案和评论的帮助......)

(SELECT ID, Employee-ID, MONTH(Date) AS Month,
CASE
    WHEN Coming-time > Leaving-time THEN  DATEDIFF(HOUR, Leaving-time, Coming-time) 
    WHEN Coming-time <= Leaving-time THEN   DATEDIFF(HOUR, Coming-time, Leaving-time)  
END AS TotalHours,
CASE
    WHEN Coming-time > Leaving-time THEN  DATEDIFF(HOUR, Leaving-time, Coming-time) - Break 
    WHEN Coming-time <= Leaving-time THEN   DATEDIFF(HOUR, Coming-time, Leaving-time) - Break
END as WorkingTime
FROM      TblEmployee)

如何获得总时数的真实值?

3 个答案:

答案 0 :(得分:1)

#include <iostream>
#include <memory>
#include <vector>

class Item
{
public:
     virtual const char* type() const = 0;
};

class Weapon : public Item
{
public:
      const char* type() const override { return "Weapon"; }
};

class Ranged : public Weapon
{
public:
    const char* type() const override { return "Ranged"; }
};

class Melee : public Weapon
{
public:
    const char* type() const override { return "Melee"; }
};

class Apparel : public Item
{
public:
    const char* type() const override { return "Apparel"; }
};

int main()
{
    std::vector<std::unique_ptr<Item>> items;

    items.emplace_back(new Apparel());
    items.emplace_back(new Ranged());
    items.emplace_back(new Melee());

    std::cout << "Inventory Types:\n";
    for (const auto& it: items)
        std::cout << it->type() << '\n';
}

这个想法是这样的。

答案 1 :(得分:1)

你曾经看过冰山海报,80%是水下海报,所以你不会考虑它。

你的问题非常类似。它看起来很无害,但刚开始一瞥就有一只隐藏的野兽。在这种情况下,该野兽是您必须执行此操作的数据结构,再加上轮班显示为跨越天数的事实。这里的简短故事是你在SQL中的冰山解决方案。长篇故事如下。

enter image description here

SELECT
    base.[Employee-ID],
    DATEPART(year, base.[Date]) AS [Year],
    DATEPART(month, base.[Date]) AS [Month],
    DATEPART(day, base.[Date]) AS [Day],
    SUM(base.HoursWorked) AS TotalHoursAtWork,
    SUM(base.HoursOnBreak) AS TotalBreakHours,
    SUM(base.HoursWorked) - SUM(base.HoursOnBreak) AS TotalHoursWorked
FROM
(
    SELECT
        [Employee-ID],
        CAST(StartDateTime AS DATE) [Date],
        CASE
            WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
                DATEDIFF(minute, StartDateTime, EndDateTime) / 60.0
            ELSE
                DATEDIFF(minute, StartDateTime, DATEADD(day, DATEDIFF(day, 0, StartDateTime), 1)) / 60.0
        END HoursWorked,
        CASE
            WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
                BreakHours
            ELSE
                BreakHours / 2
        END HoursOnBreak
    FROM (
        SELECT
            [ID],
            [Employee-ID],
            [Date],
            [Coming-time],
            [Leaving-time],
            [Break(in hours)] AS BreakHours,
            CAST([Date] AS SMALLDATETIME) + CAST([Coming-time] AS SMALLDATETIME) AS StartDateTime,
            CASE
                WHEN [Coming-time] <= [Leaving-time] THEN 
                    CAST([Date] AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
                ELSE
                    CAST(DATEADD(day, 1, [Date]) AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
            END AS EndDateTime
        FROM
            [sandbox].[dbo].[TblEmployee]
    ) AS firstDay
    UNION ALL
    SELECT
        [Employee-ID],
        CAST(EndDateTime AS DATE) [Date],
        CASE
            WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
                0
            ELSE
                DATEDIFF(minute, DATEADD(day, DATEDIFF(day, 0, EndDateTime), 0), EndDateTime) / 60.0
        END HoursWorked,
        CASE
            WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
                0
            ELSE
                BreakHours / 2
        END HoursOnBreak
    FROM (
        SELECT
            [ID],
            [Employee-ID],
            [Date],
            [Coming-time],
            [Leaving-time],
            [Break(in hours)] AS BreakHours,
            CAST([Date] AS SMALLDATETIME) + CAST([Coming-time] AS SMALLDATETIME) AS StartDateTime,
            CASE
                WHEN [Coming-time] <= [Leaving-time] THEN 
                    CAST([Date] AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
                ELSE
                    CAST(DATEADD(day, 1, [Date]) AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
            END AS EndDateTime
        FROM
            [sandbox].[dbo].[TblEmployee]
    ) AS secondDay
) AS base
GROUP BY
    base.[Employee-ID],
    DATEPART(year, base.[Date]),
    DATEPART(month, base.[Date]),
    DATEPART(day, base.[Date])

那么为什么这些东西呢?问题是时间报告的日期和时间存在根本缺陷。它实际上是关于每天,每个任务,每个项目等工作的小时数。

由于您正在尝试查看每天/每月/每年的工作小时数,因此我们只需将这些数据放入最低公分母,即每天工作小时数。

第1步 - 获取即将离开的日期时间格式。这使得我们获得了相同的数据类型,并且顺便说一下,当出现时间大于离开时会发生什么。

enter image description here

SELECT
    [ID],
    [Employee-ID],
    [Date],
    [Coming-time],
    [Leaving-time],
    [Break(in hours)],
    CAST([Date] AS SMALLDATETIME) + CAST([Coming-time] AS SMALLDATETIME) AS StartDateTime,
    CASE
        WHEN [Coming-time] <= [Leaving-time] THEN 
            CAST([Date] AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
        ELSE
            CAST(DATEADD(day, 1, [Date]) AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
    END AS EndDateTime
FROM
    [sandbox].[dbo].[TblEmployee]

第2步 - 现在我们已经得到了日期时间的真实表示,转换为每天的小时数。问题是有时会跨越天。 ruh roh raggy。

所以让我们把它分开,在多天的时候,shitfs每天休息一半。

警告:假设:

  • 如果到来的时间是'之后'(小时:分钟超过)离开,则假定离开是第二天的时间。
  • shitft绝不会超过24小时。如果某人在早上8点进入并在上午10点离开,则假定为2小时,而不是26岁。如果您没有离开日期,那就是生活。
  • 休息时间并不总是一半左右 那里的变化。

enter image description here

SELECT
    [Employee-ID],
    CAST(StartDateTime AS DATE) FirstDay,
    CASE
        WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
            DATEDIFF(minute, StartDateTime, EndDateTime) / 60.0
        ELSE
            DATEDIFF(minute, StartDateTime, DATEADD(day, DATEDIFF(day, 0, StartDateTime), 1)) / 60.0
    END FirstDayHours,
    CASE
        WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
            BreakHours
        ELSE
            BreakHours / 2
    END FirstDayBreak,
    CAST(EndDateTime AS DATE) SecondDay,
    CASE
        WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
            0
        ELSE
            DATEDIFF(minute, DATEADD(day, DATEDIFF(day, 0, EndDateTime), 0), EndDateTime) / 60.0
    END SecondDayHours,
    CASE
        WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
            0
        ELSE
            BreakHours / 2
    END SecondDayBreak
FROM (
    SELECT
        [ID],
        [Employee-ID],
        [Date],
        [Coming-time],
        [Leaving-time],
        [Break(in hours)] AS BreakHours,
        CAST([Date] AS SMALLDATETIME) + CAST([Coming-time] AS SMALLDATETIME) AS StartDateTime,
        CASE
            WHEN [Coming-time] <= [Leaving-time] THEN 
                CAST([Date] AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
            ELSE
                CAST(DATEADD(day, 1, [Date]) AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
        END AS EndDateTime
    FROM
        [sandbox].[dbo].[TblEmployee]
) AS base

第3步 - 问题是我们需要在一天的列中进行分组。下面的联合只是将第一天和第二天的逻辑分成两个单独的查询。效率=地狱没有,功能性 - 是的。

enter image description here

SELECT
    [Employee-ID],
    CAST(StartDateTime AS DATE) [Date],
    CASE
        WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
            DATEDIFF(minute, StartDateTime, EndDateTime) / 60.0
        ELSE
            DATEDIFF(minute, StartDateTime, DATEADD(day, DATEDIFF(day, 0, StartDateTime), 1)) / 60.0
    END HoursWorked,
    CASE
        WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
            BreakHours
        ELSE
            BreakHours / 2
    END HoursOnBreak
FROM (
    SELECT
        [ID],
        [Employee-ID],
        [Date],
        [Coming-time],
        [Leaving-time],
        [Break(in hours)] AS BreakHours,
        CAST([Date] AS SMALLDATETIME) + CAST([Coming-time] AS SMALLDATETIME) AS StartDateTime,
        CASE
            WHEN [Coming-time] <= [Leaving-time] THEN 
                CAST([Date] AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
            ELSE
                CAST(DATEADD(day, 1, [Date]) AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
        END AS EndDateTime
    FROM
        [sandbox].[dbo].[TblEmployee]
) AS firstDay
UNION ALL
SELECT
    [Employee-ID],
    CAST(EndDateTime AS DATE) [Date],
    CASE
        WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
            0
        ELSE
            DATEDIFF(minute, DATEADD(day, DATEDIFF(day, 0, EndDateTime), 0), EndDateTime) / 60.0
    END HoursWorked,
    CASE
        WHEN DATEDIFF(day, StartDateTime, EndDateTime) = 0 THEN
            0
        ELSE
            BreakHours / 2
    END HoursOnBreak
FROM (
    SELECT
        [ID],
        [Employee-ID],
        [Date],
        [Coming-time],
        [Leaving-time],
        [Break(in hours)] AS BreakHours,
        CAST([Date] AS SMALLDATETIME) + CAST([Coming-time] AS SMALLDATETIME) AS StartDateTime,
        CASE
            WHEN [Coming-time] <= [Leaving-time] THEN 
                CAST([Date] AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
            ELSE
                CAST(DATEADD(day, 1, [Date]) AS SMALLDATETIME) + CAST([Leaving-time] AS SMALLDATETIME)
        END AS EndDateTime
    FROM
        [sandbox].[dbo].[TblEmployee]
) AS secondDay

在此之后你只是打了一些聚合和whamo。想要按不同时间段汇总,只需更改组以符合您的需要。

答案 2 :(得分:0)

SELECT    [Employee-ID] as employee_id,
          MONTH(Date) as month,
          SUM(DATEDIFF(MINUTE, [Coming-time], [Leaving-time]) / 60 - [Break]) as working_time

FROM      table_name

GROUP BY  [Employee-ID],
          MONTH(Date)