如何计算 sql 中的员工工作时间

How to calculate employees working hours in sql

我公司的营业时间是7:30到18点,13:00到15:00是午餐时间。 重要的部分是关于午餐时间,根本不应该算作员工的工作时间。

我的字段都是 Sql 中的 Time(7) 格式。

我发布这段代码来解决你的问题

TimeSpan? ActualTime = obj.ExitTime - obj.EnterTime;
string[] LunchTime = obj.lunchTimeString.Split('-');
TimeSpan ActualLunchTime = TimeSpan.Parse(LunchTime[1].Trim()) - TimeSpan.Parse(LunchTime[0].Trim());
TimeSpan WorkingHours = TimeSpan.Parse(ActualTime.ToString().Trim()) - TimeSpan.Parse(ActualLunchTime.ToString().Trim());

在这段代码中我所做的是:

  1. 您必须计算包括午餐时间在内的实际时间。
  2. 你的第二个问题是午餐时间,它的格式是 (13:00 - 15:00)。所以,我不能在 sql 中使用 time(7) 所以把它当作 varchar 然后用 '-' 分割它。
  3. 然后用它计算总的午餐时间。
  4. 最后减去实际时间和实际午餐时间就得到你的工作时间。

这是 SQL 服务器中的一种方法。它从根本上将每个人的轮班分成两个 - Pre-Lunch 和 Post-Lunch。当轮班进入(或过去)午餐时,它使用午餐时间作为界限。

我还用位(例如 CTE、sub-query 等)编写了它,因此您可以 运行 单独查看它们的作用。您可能需要为自己的数据库结构等更新它。

-- Data setup

CREATE TABLE #WorkLog (WorkDate date, StartTime time, EndTime time, StartLunch time, EndLunch time)

INSERT INTO #WorkLog (WorkDate, StartTime, EndTime, StartLunch, EndLunch) VALUES
('2020-09-01', '07:30', '18:00', '13:00', '15:00'),
('2020-09-02', '12:00', '15:00', '13:00', '15:00'),
('2020-09-03', '15:00', '18:00', '13:00', '15:00'),
('2020-09-04', '08:30', '15:00', '13:00', '15:00')

SELECT * FROM #WorkLog

------

-- Find times worked

; WITH PreLunchTimes AS
        (SELECT WorkDate,
                StartTime AS StartTime,
                CASE WHEN EndTime < StartLunch THEN EndTime ELSE StartLunch END AS EndTime
        FROM    #WorkLog
        WHERE   StartTime < StartLunch
        ),
    PostLunchTimes AS
        (SELECT WorkDate,
                CASE WHEN StartTime > EndLunch THEN StartTime ELSE EndLunch END AS StartTime,
                EndTime AS EndTime
        FROM    #WorkLog
        WHERE   EndTime > EndLunch
        )
SELECT  WorkDate, SUM(Elapsed_Mins) AS Total_Work_Mins, CAST(DATEADD(minute, SUM(Elapsed_Mins), 0) AS time) AS Total_work_time
FROM   (SELECT  WorkDate, DATEDIFF(minute, StartTime, EndTime) AS Elapsed_Mins
        FROM    PreLunchTimes
            UNION ALL
        SELECT  WorkDate, DATEDIFF(minute, StartTime, EndTime) AS Elapsed_Mins
        FROM    PostLunchTimes
        ) AS A
GROUP BY WorkDate
ORDER BY WorkDate

这是一个db<>fiddle

问题:

  • 如果您的班次超过午夜,您需要添加适当的代码来处理。
  • 如果 all 午餐时间是 13:00 到 15:00,那么您可以将它们设置为变量(例如,@LunchStart 和 @LunchEnd)而不是将它们存储在数据中。

唉,你忘了给我们你的数据库结构 table。您的 EnterTime 和 ExitTime 是完整的 DateTimes,还是您在员工开始和停止工作的那一天有一个 WorkDay 和一个 TimeOfDay?

另外你忘了给我们你的table结构,我不知道你是怎么和你的数据库通信的,是用entity framework,还是传统的SQL,以及你说的 SQL 方言。有些方言可以进行 DateTime 计算,有些则不能。

MadReflection的评论是明智的:如果你可以改变你的数据库,考虑记住午餐前班次的开始和停止DateTime以及午餐后班次的开始/停止DateTime。通过这种方式,您可以为未来的变化做好准备,例如每位员工的不同午餐时间,甚至是员工每天的不同午餐时间。如果你想在DateTime中进入和退出,你甚至可以让一个Employee在除夕开始工作并在不同的年份停止工作。

幸运的是,您不必让数据库计算午餐时间,因为您可以在本地计算午餐时间。这比让数据库管理系统计算它们并将结果从 DBMS 传输到本地进程要快。

在本地计算午餐时间只会减去两个 long,这比传输数据所需的代码要快得多。

所以我们不能给你SQL。您必须自己编写程序来获取数据。

您需要一种方法来获取一段时间内每个员工或所有员工的 EnterTime / ExitTime。结果类似于:

class WorkingTimes
{
     public EmployeeInformation Employee {get; set;}

     public DateTime EnterTime {get; set;}
     public DateTime ExitTime {get; set;}
}

以及为每位员工或所有员工获取此信息的方法:

IEnumerable<WorkingTimes> FetchWorkingTimes(DateTime beginPeriod, DateTime endPeriod)
{
    ...
}

IEnumerable<WorkingTimes> FetchWorkingTimes(int employeeId, 
     DateTime beginPeriod, DateTime endPeriod)
{
    ...
}

要将获取的数据转换为您想要的格式,一个简单的 LINQ 方法即可:

class WorkingTimeExt
{
    // TODO: if needed: add employee information

    public System.DayOfWeek DayOfWeek => this.EnterTime.DayOfWeek;
    public DateTime EnterTime {get; set;}
    public DateTime ExitTime {get; set;}

    public DateTime StartLunch {get; set;}
    public DateTime StopLunch {get; set;}
    public string Lunch => String.Format("...", this.StartLunch, this.StopLunch);

    public TimeSpan WorkingTime => this.StartLunch - this.EnterTime
                                 + this.ExitTime - this.StopLunch;
}

WorkingTime的计算速度很快,就是加了一些longs

TimeSpan defaultLunchStart = TimeSpan.FromHours(13.00);
TimeSpan defaultLunchEnd = TimeSpan.FromHours(15.00);

IEnumerable<WorkingTimes> fetchedWorkingTimes = ...
IEnumerable<WorkingTimesExt> result = fetchedWorkingTimes
    .Select(fetchedTimes => new WorkingTimesExt
    {
         EnterTime = fetchedWorkingTimes.EnterTime,
         ExitTime = fetchedWorkingTimes.ExitTime,
         StartLunchTime = fetchedWorkingTime.EnterTime.Date + defaultLunchStart,
         StopLunchTime = fetchedWorkingTime.ExitTime.Data + defaultLunchStop,
    });

如果您需要定期执行此操作,请考虑为其创建一个扩展方法:

IEnumerable<WorkingTimesExt> result = ToExtendedWorkingTimes(
     this IEnumerable<WorkingTimes> source,
     TimeSpan timeLunchStart,
     TimeSpan timeLunchStop)
{
    return source.Select(fetchedTimes => new WorkingTimesExt
    {
         EnterTime = fetchedWorkingTimes.EnterTime,
         ExitTime = fetchedWorkingTimes.ExitTime,
         StartLunchTime = fetchedWorkingTime.EnterTime.Date + timeLunchStart,
         StopLunchTime = fetchedWorkingTime.ExitTime.Data + timeLunchStop,
    });
}

另一个工作时间转换为HH:MM格式的有效选项:

CREATE TABLE t1 (dow VARCHAR(10), workstart TIME, workend TIME);
INSERT INTO t1 VALUES
('Monday', '7:30', '18:00'),
('Tuesday', '12:00', '15:00'),
('Wednesday', '15:00', '18:00'),
('Thursday', '8:30', '15:00');

WITH 
lunch(lunchstart, lunchend) AS (
SELECT CAST('13:00' AS TIME), CAST('15:00' AS TIME)
),
attendances AS (
SELECT *,
CASE WHEN workstart <= lunchstart THEN
     CASE WHEN workend <= lunchstart THEN
          DATEDIFF(minute, workstart, workend)
     ELSE
          DATEDIFF(minute, workstart, lunchstart)
     END
ELSE 0 END --AS morning_shift
+
CASE WHEN workend >= lunchend THEN
     CASE WHEN workstart >= lunchend THEN
          DATEDIFF(minute, workstart, workend)
     ELSE
          DATEDIFF(minute, lunchend, workend)
     END
ELSE 0 END --AS afternoon_shift 
AS total_mins 
FROM t1, lunch
)
SELECT *, FORMAT(total_mins / 60 * 100 + total_mins % 60, '#:0#') AS hhmm 
FROM attendances;