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

SQL Server中如何计算员工登录登出时间及班次划分?

解决SQL Server中员工多班次登录登出时长统计问题

嘿,这个多班次考勤统计的问题我之前帮不少人处理过,咱们一步步拆解来搞定它~

首先咱们得先对齐数据模型哈,假设你的考勤表大概是这个结构(如果实际表字段不一样,按需调整就行):

CREATE TABLE EmployeeAttendance (
    EmployeeID INT,
    OperationTime DATETIME,
    OperationType VARCHAR(10) -- 'login' 表示登录,'logout' 表示登出
);

步骤1:给每一次操作标记所属班次

核心逻辑是:同一员工单日里,两次连续操作间隔超过5小时,就视为新班次。咱们用窗口函数LAG()来获取上一次操作的时间和类型,然后生成唯一的班次ID:

WITH AttendanceWithPrev AS (
    SELECT 
        EmployeeID,
        OperationTime,
        OperationType,
        -- 拿到上一次操作的时间,按员工+日期分组排序
        LAG(OperationTime) OVER (
            PARTITION BY EmployeeID, CAST(OperationTime AS DATE) 
            ORDER BY OperationTime
        ) AS PrevOperationTime,
        -- 拿到上一次操作的类型
        LAG(OperationType) OVER (
            PARTITION BY EmployeeID, CAST(OperationTime AS DATE) 
            ORDER BY OperationTime
        ) AS PrevOperationType
    FROM EmployeeAttendance
),
AttendanceWithShift AS (
    SELECT 
        *,
        -- 生成班次ID:第一次操作/间隔超5小时,都算新班次
        SUM(CASE 
            WHEN PrevOperationTime IS NULL THEN 1
            WHEN DATEDIFF(HOUR, PrevOperationTime, OperationTime) > 5 THEN 1
            ELSE 0
        END) OVER (
            PARTITION BY EmployeeID, CAST(OperationTime AS DATE) 
            ORDER BY OperationTime ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS ShiftID
    FROM AttendanceWithPrev
)
SELECT * FROM AttendanceWithShift;

步骤2:配对登录登出,计算单班次在岗时长

接下来要把每个班次的login和logout对应起来(这里默认每个班次都是login开头、logout结尾,如果有异常数据比如只有login没logout,后面可以补规则处理):

WITH AttendanceWithPrev AS (
    SELECT 
        EmployeeID,
        OperationTime,
        OperationType,
        LAG(OperationTime) OVER (
            PARTITION BY EmployeeID, CAST(OperationTime AS DATE) 
            ORDER BY OperationTime
        ) AS PrevOperationTime,
        LAG(OperationType) OVER (
            PARTITION BY EmployeeID, CAST(OperationTime AS DATE) 
            ORDER BY OperationTime
        ) AS PrevOperationType
    FROM EmployeeAttendance
),
AttendanceWithShift AS (
    SELECT 
        *,
        SUM(CASE 
            WHEN PrevOperationTime IS NULL THEN 1
            WHEN DATEDIFF(HOUR, PrevOperationTime, OperationTime) > 5 THEN 1
            ELSE 0
        END) OVER (
            PARTITION BY EmployeeID, CAST(OperationTime AS DATE) 
            ORDER BY OperationTime ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS ShiftID
    FROM AttendanceWithPrev
),
ShiftDurations AS (
    SELECT 
        EmployeeID,
        CAST(OperationTime AS DATE) AS AttendanceDate,
        ShiftID,
        -- 提取当前班次的登录时间
        MAX(CASE WHEN OperationType = 'login' THEN OperationTime END) AS LoginTime,
        -- 提取当前班次的登出时间
        MAX(CASE WHEN OperationType = 'logout' THEN OperationTime END) AS LogoutTime,
        -- 计算单班次在岗时长(这里用分钟,你可以改成小时/秒)
        DATEDIFF(MINUTE, 
            MAX(CASE WHEN OperationType = 'login' THEN OperationTime END),
            MAX(CASE WHEN OperationType = 'logout' THEN OperationTime END)
        ) AS OnFloorMinutes
    FROM AttendanceWithShift
    GROUP BY EmployeeID, CAST(OperationTime AS DATE), ShiftID
)
SELECT * FROM ShiftDurations;

步骤3:统计总在岗&离岗时长

离岗时长要涵盖三个部分:当天0点到第一次登录的时间、班次之间的间隔时间、最后一次登出到当天23:59:59的时间。咱们继续扩展CTE来计算:

WITH AttendanceWithPrev AS (
    SELECT 
        EmployeeID,
        OperationTime,
        OperationType,
        LAG(OperationTime) OVER (
            PARTITION BY EmployeeID, CAST(OperationTime AS DATE) 
            ORDER BY OperationTime
        ) AS PrevOperationTime,
        LAG(OperationType) OVER (
            PARTITION BY EmployeeID, CAST(OperationTime AS DATE) 
            ORDER BY OperationTime
        ) AS PrevOperationType
    FROM EmployeeAttendance
),
AttendanceWithShift AS (
    SELECT 
        *,
        SUM(CASE 
            WHEN PrevOperationTime IS NULL THEN 1
            WHEN DATEDIFF(HOUR, PrevOperationTime, OperationTime) > 5 THEN 1
            ELSE 0
        END) OVER (
            PARTITION BY EmployeeID, CAST(OperationTime AS DATE) 
            ORDER BY OperationTime ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS ShiftID
    FROM AttendanceWithPrev
),
ShiftDurations AS (
    SELECT 
        EmployeeID,
        CAST(OperationTime AS DATE) AS AttendanceDate,
        ShiftID,
        MAX(CASE WHEN OperationType = 'login' THEN OperationTime END) AS LoginTime,
        MAX(CASE WHEN OperationType = 'logout' THEN OperationTime END) AS LogoutTime,
        DATEDIFF(MINUTE, 
            MAX(CASE WHEN OperationType = 'login' THEN OperationTime END),
            MAX(CASE WHEN OperationType = 'logout' THEN OperationTime END)
        ) AS OnFloorMinutes
    FROM AttendanceWithShift
    GROUP BY EmployeeID, CAST(OperationTime AS DATE), ShiftID
),
ShiftWithPrevLogout AS (
    SELECT 
        *,
        LAG(LogoutTime) OVER (
            PARTITION BY EmployeeID, AttendanceDate 
            ORDER BY ShiftID
        ) AS PrevShiftLogoutTime
    FROM ShiftDurations
),
DailyDurations AS (
    SELECT 
        EmployeeID,
        AttendanceDate,
        SUM(OnFloorMinutes) AS TotalOnFloorMinutes,
        -- 计算总离岗时长
        (
            -- 当天开始到第一次登录的时间
            DATEDIFF(MINUTE, CAST(AttendanceDate AS DATETIME), MIN(LoginTime))
            -- 班次之间的间隔时间
            + SUM(CASE 
                WHEN PrevShiftLogoutTime IS NOT NULL THEN DATEDIFF(MINUTE, PrevShiftLogoutTime, LoginTime)
                ELSE 0
            END)
            -- 最后一次登出到当天结束的时间
            + DATEDIFF(MINUTE, MAX(LogoutTime), DATEADD(SECOND, -1, DATEADD(DAY, 1, CAST(AttendanceDate AS DATETIME))))
        ) AS TotalOffFloorMinutes
    FROM ShiftWithPrevLogout
    GROUP BY EmployeeID, AttendanceDate
)
SELECT 
    EmployeeID,
    AttendanceDate,
    TotalOnFloorMinutes AS [总在岗时长(分钟)],
    CONVERT(VARCHAR(10), TotalOnFloorMinutes / 60) + '小时' + CONVERT(VARCHAR(10), TotalOnFloorMinutes % 60) + '分钟' AS [总在岗时长(格式化)],
    TotalOffFloorMinutes AS [总离岗时长(分钟)],
    CONVERT(VARCHAR(10), TotalOffFloorMinutes / 60) + '小时' + CONVERT(VARCHAR(10), TotalOffFloorMinutes % 60) + '分钟' AS [总离岗时长(格式化)]
FROM DailyDurations;

一些额外提示

  • 如果遇到异常数据(比如只有login没logout、或者只有logout没login),可以根据业务规则补处理:比如未登出的记录用当天结束时间补登出,无效的孤立操作直接过滤。
  • 时间单位可以按需调整,比如把分钟改成小时、秒都可以。
  • 要是需要按周/月统计,只需要修改分组维度就行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 10:01:08