多打卡记录生成员工班次的SQL查询:解决跨夜班次合并问题
解决跨夜班次的打卡记录合并问题
我来帮你搞定这个跨天班次的打卡合并问题——你的原有查询在处理跨夜班次时失效,核心原因是按打卡日期分组的逻辑不符合实际班次的连续性,下面是具体的解决方案和细节解释:
问题根源分析
你原来的查询语句是按CAST(PUNCHIN AS DATE)分组,这个逻辑在处理不跨天的白班时没问题,但遇到跨夜班次就会“拆碎”同一个班次:比如员工DGHAG从2020-07-10 20:00开始上班,后续的打卡记录(比如2020-07-11 01:45的PUNCHIN)日期是7月11号,会被分到另一个日期组,导致同一个完整班次被拆成多条记录,自然无法得到正确的合并结果。
解决方案:按连续打卡记录分组
我们需要基于「同班次的打卡记录是连续的(上一条的PUNCHOUT等于当前的PUNCHIN)」这个规则来分组,用窗口函数就能实现这个逻辑:
完整SQL查询(适配SQL Server)
WITH ShiftGroups AS ( SELECT EMPLOYEEID, PUNCHIN, PUNCHOUT, -- 标记是否为新班次:如果当前PUNCHIN和上一条的PUNCHOUT不相等,说明是新班次 CASE WHEN LAG(PUNCHOUT) OVER (PARTITION BY EMPLOYEEID ORDER BY PUNCHIN) = PUNCHIN THEN 0 ELSE 1 END AS IsNewShift, -- 累计新班次标记,生成每个班次的唯一分组ID SUM(CASE WHEN LAG(PUNCHOUT) OVER (PARTITION BY EMPLOYEEID ORDER BY PUNCHIN) = PUNCHIN THEN 0 ELSE 1 END) OVER (PARTITION BY EMPLOYEEID ORDER BY PUNCHIN ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS ShiftGroupID FROM EMPSCHED -- 先排除无效的打卡(PUNCHIN=PUNCHOUT),这些是结束标记,聚合时会被自动包含 WHERE PUNCHIN <> PUNCHOUT ) SELECT EMPLOYEEID, MIN(PUNCHIN) AS ShiftStart, -- 班次的首次打卡时间 MAX(PUNCHOUT) AS ShiftEnd -- 班次的末次打卡时间 FROM ShiftGroups GROUP BY EMPLOYEEID, ShiftGroupID ORDER BY EMPLOYEEID, ShiftStart;
逻辑拆解
CTE
ShiftGroups部分:LAG(PUNCHOUT) OVER (...):获取当前员工上一条打卡记录的离开时间,用来判断是否和当前记录属于同一个班次。IsNewShift:如果当前记录的PUNCHIN和上一条的PUNCHOUT不相等,说明这是新班次的开始,标记为1;否则属于同一个班次,标记为0。ShiftGroupID:通过累计IsNewShift的值,为每个连续的班次生成唯一的分组ID——同一个班次的所有记录会拥有相同的ID。
最终聚合部分:
按员工ID和班次分组ID聚合,取分组内最早的PUNCHIN作为班次开始时间,最晚的PUNCHOUT作为班次结束时间,完美合并同班次的所有记录。
示例结果验证
针对你提供的示例数据,运行上述查询后:
- DGHAG的跨夜班次会被正确合并:
- 2020-07-10 20:00:00 → 2020-07-11 06:30:00(合并4条连续记录)
- 2020-07-11 20:00:00 → 2020-07-12 06:30:00(合并4条连续记录)
- DGHFA的白班记录也会合并为每天一条(比如2020-07-08 08:30:00 → 13:30:00)
补充说明
- 如果你需要保留
PUNCHIN=PUNCHOUT的记录(比如作为班次结束的标记),可以去掉WHERE PUNCHIN <> PUNCHOUT,聚合时MAX(PUNCHOUT)会自动取到最后那个结束标记的时间,不影响结果。 - 这个方案适配SQL Server(你的建表语句是SQL Server风格),如果是其他数据库,语法可能略有调整,但核心逻辑完全通用。
内容的提问来源于stack exchange,提问作者rb_fms




