You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何计算员工班次间隔休息时长?存储过程优化咨询

员工班次休息时长计算与存储过程优化

需求说明

需要计算员工班次之间的休息时长:从数据库的考勤表(LogData)和工作任务表(BookingSessions)中,找出员工每日最早的考勤或工作任务开始时间(即当日班次开始时间),并计算该时间与前一日最晚的考勤或工作任务结束时间(即前一班次结束时间)的间隔小时数。

当前存储过程

PROCEDURE [dbo].[ReportEmployeeRestHours] 
    -- Add the parameters for the stored procedure here
    @dtFrom DATETIME,
    @dtTo DATETIME
AS
BEGIN
    SELECT
    EmployeeName,
    WorkerReferenceID,
    WorkDate,
    MIN(BookingStart) AS MinBookingStartTime,
    MAX(BookingEnd) AS MaxBookingEndTime,
    MIN(LogStart) AS MinLogStartTime,
    MAX(LogEnd) AS MaxLogEndTime,
    ISNULL(
        CASE
            WHEN MIN(BookingStart) IS NOT NULL AND (MIN(LogStart) IS NULL OR MIN(BookingStart) < MIN(LogStart)) THEN MIN(BookingStart)
            ELSE MIN(LogStart)
        END, MIN(BookingStart)
    ) AS MinStartTime,
    ISNULL(
        CASE
            WHEN MAX(BookingEnd) IS NOT NULL AND (MAX(LogEnd) IS NULL OR MAX(BookingEnd) > MAX(LogEnd)) THEN MAX(BookingEnd)
            ELSE MAX(LogEnd)
        END, MAX(BookingEnd)
    ) AS MaxEndTime
FROM (
    
    SELECT
            Employees.Forename + ' ' + Employees.Surname AS EmployeeName,
            Employees.WorkerReferenceID,
            BookingSessions.JobStartDT AS WorkDate,
            -- Gets the date from the Job Start. Gets the time from the employee start time/ end time. Combines them.
            CONVERT(datetime, BookingSessions.JobStartDT + ' ' + CONVERT(varchar, BookingSessionEmployees.StartTime, 108)) AS BookingStart,
            CONVERT(datetime, BookingSessions.JobStartDT + ' ' + CONVERT(varchar, BookingSessionEmployees.EndTime, 108)) AS BookingEnd,
            NULL AS LogStart,
            NULL AS LogEnd
        FROM Employees
        INNER JOIN BookingSessionEmployees ON Employees.EmployeeID = BookingSessionEmployees.EmployeeID
        INNER JOIN BookingSessionLocationTasks ON BookingSessionEmployees.BookingSessionLocationTaskID = BookingSessionLocationTasks.BookingSessionLocationTaskID
        INNER JOIN BookingSessions ON BookingSessionLocationTasks.BookingSessionLocationID = BookingSessions.BookingSessionID
        WHERE BookingSessions.JobStartDT BETWEEN @dtFrom AND @dtTo
              AND BookingSessionEmployees.StartTime IS NOT NULL
              AND BookingSessionEmployees.EndTime IS NOT NULL

    UNION

    SELECT
    Employees.Forename + ' ' + Employees.Surname AS EmployeeName,
    Employees.WorkerReferenceID,
    LogData.DateOfWork AS WorkDate,
    NULL AS BookingStart,
    NULL AS BookingEnd,
    -- Gets the date from the date of work. Gets the time from the start time. Combines them. 
    CONVERT(datetime, LogData.DateOfWork + ' ' + CONVERT(varchar, LogData.StartTime, 108)) AS LogStart,
    CONVERT(datetime, LogData.DateOfWork + ' ' + CONVERT(varchar, LogData.EndTime, 108)) AS LogEnd
    FROM Employees
    INNER JOIN LogData ON Employees.EmployeeID = LogData.EmployeeID
    WHERE LogData.DateOfWork BETWEEN @dtFrom AND @dtTo
          AND LogData.StartTime IS NOT NULL
          AND LogData.EndTime IS NOT NULL
          AND LogData.StartTime <> LogData.EndTime -- Exclude entries where StartTime equals EndTime because these are all bonus Timesheets
) AS CombinedData
GROUP BY EmployeeName, WorkerReferenceID, WorkDate;
END

当前问题

无法筛选出每日的最早开始时间,会返回当日多条记录;且存储过程逻辑复杂,可读性和效率有待提升。

优化思路与方案

1. 提前聚合数据,减少计算量

先分别对BookingSessions和LogData按员工+日期聚合,得到每日的最早/最晚时间,再合并数据集,避免UNION后大量数据的重复计算。

2. 简化起止时间判断逻辑

用更简洁的条件判断替代嵌套的ISNULL+CASE,提升代码可读性;SQL Server 2022及以上版本可直接使用LEAST/GREATEST函数,低版本用CASE简化。

3. 用窗口函数计算跨日休息时长

使用LAG()窗口函数按员工分组、日期排序,直接获取前一日的最晚结束时间,再通过DATEDIFF计算间隔小时数。

优化后的存储过程

PROCEDURE [dbo].[ReportEmployeeRestHours] 
    @dtFrom DATETIME,
    @dtTo DATETIME
AS
BEGIN
    SET NOCOUNT ON;

    -- 聚合工作任务的每日起止时间
    WITH BookingDaily AS (
        SELECT
            e.Forename + ' ' + e.Surname AS EmployeeName,
            e.WorkerReferenceID,
            bs.JobStartDT AS WorkDate,
            MIN(CONVERT(DATETIME, bs.JobStartDT + ' ' + CONVERT(VARCHAR, bse.StartTime, 108))) AS BookingMinStart,
            MAX(CONVERT(DATETIME, bs.JobStartDT + ' ' + CONVERT(VARCHAR, bse.EndTime, 108))) AS BookingMaxEnd
        FROM Employees e
        JOIN BookingSessionEmployees bse ON e.EmployeeID = bse.EmployeeID
        JOIN BookingSessionLocationTasks bslt ON bse.BookingSessionLocationTaskID = bslt.BookingSessionLocationTaskID
        JOIN BookingSessions bs ON bslt.BookingSessionLocationID = bs.BookingSessionID
        WHERE bs.JobStartDT BETWEEN @dtFrom AND @dtTo
          AND bse.StartTime IS NOT NULL
          AND bse.EndTime IS NOT NULL
        GROUP BY e.Forename + ' ' + e.Surname, e.WorkerReferenceID, bs.JobStartDT
    ),
    -- 聚合考勤数据的每日起止时间
    LogDaily AS (
        SELECT
            e.Forename + ' ' + e.Surname AS EmployeeName,
            e.WorkerReferenceID,
            ld.DateOfWork AS WorkDate,
            MIN(CONVERT(DATETIME, ld.DateOfWork + ' ' + CONVERT(VARCHAR, ld.StartTime, 108))) AS LogMinStart,
            MAX(CONVERT(DATETIME, ld.DateOfWork + ' ' + CONVERT(VARCHAR, ld.EndTime, 108))) AS LogMaxEnd
        FROM Employees e
        JOIN LogData ld ON e.EmployeeID = ld.EmployeeID
        WHERE ld.DateOfWork BETWEEN @dtFrom AND @dtTo
          AND ld.StartTime IS NOT NULL
          AND ld.EndTime IS NOT NULL
          AND ld.StartTime <> ld.EndTime
        GROUP BY e.Forename + ' ' + e.Surname, e.WorkerReferenceID, ld.DateOfWork
    ),
    -- 合并数据并计算当日最早/最晚时间
    DailyAgg AS (
        SELECT
            COALESCE(b.EmployeeName, l.EmployeeName) AS EmployeeName,
            COALESCE(b.WorkerReferenceID, l.WorkerReferenceID) AS WorkerReferenceID,
            COALESCE(b.WorkDate, l.WorkDate) AS WorkDate,
            -- 当日最早开始时间
            COALESCE(
                CASE 
                    WHEN b.BookingMinStart < l.LogMinStart THEN b.BookingMinStart 
                    ELSE l.LogMinStart 
                END,
                b.BookingMinStart,
                l.LogMinStart
            ) AS DailyMinStart,
            -- 当日最晚结束时间
            COALESCE(
                CASE 
                    WHEN b.BookingMaxEnd > l.LogMaxEnd THEN b.BookingMaxEnd 
                    ELSE l.LogMaxEnd 
                END,
                b.BookingMaxEnd,
                l.LogMaxEnd
            ) AS DailyMaxEnd
        FROM BookingDaily b
        FULL JOIN LogDaily l 
            ON b.WorkerReferenceID = l.WorkerReferenceID 
            AND b.WorkDate = l.WorkDate
    )
    -- 最终结果,计算跨日休息时长
    SELECT
        EmployeeName,
        WorkerReferenceID,
        WorkDate,
        DailyMinStart,
        DailyMaxEnd,
        DATEDIFF(HOUR, LAG(DailyMaxEnd) OVER (PARTITION BY WorkerReferenceID ORDER BY WorkDate), DailyMinStart) AS RestHours
    FROM DailyAgg
    ORDER BY WorkerReferenceID, WorkDate;
END

优化效果说明

  • 提前聚合数据:减少中间数据集的大小,提升查询效率
  • FULL JOIN确保不会遗漏只有工作任务或只有考勤记录的日期
  • 窗口函数LAG()简化跨日数据关联,直接获取前一日的结束时间
  • 逻辑分层清晰,代码可读性大幅提升

内容的提问来源于stack exchange,提问作者William Sewell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 15:08:12