timediff()是最常用且稳妥的计算打卡时间差方式,可正确处理跨天情况并返回hh:mm:ss格式;参数须为time或datetime类型,跨类型需用time()提取;结果为负说明签退早于签到,属异常应过滤;转小数小时需用time_to_sec()/3600.0,缺卡返回null建议coalesce补零;分组须按emp_id和date(check_in_time),夜班需结合班次表判断;多卡场景应先聚合取min/max或条件聚合规整数据,避免错配。

MySQL中用TIMEDIFF计算两次打卡时间差
直接用 TIMEDIFF() 是最常用也最稳妥的方式,它能正确处理跨天情况(比如晚班02:00打卡),返回 HH:MM:SS 格式的时间差。注意:两个参数必须都是 TIME 或 DATETIME 类型,不能是字符串。
- 如果字段是
DATETIME(如check_in_time、check_out_time),先用TIME()提取时间部分:TIMEDIFF(TIME(check_out_time), TIME(check_in_time)) - 如果字段本身就是
TIME类型(推荐存储方式),直接用:TIMEDIFF(check_out_time, check_in_time) - 结果为负数?说明签退早于签到——这是异常数据,建议在 WHERE 中过滤:
WHERE check_out_time > check_in_time
把工时差转成小数小时便于统计(如8.5小时)
TIMEDIFF 返回的是时间类型,没法直接 SUM 或 AVG。必须转成十进制小时,核心是用 TIME_TO_SEC() 拆解秒数再除以 3600。
- 标准转换写法:
TIME_TO_SEC(TIMEDIFF(check_out_time, check_in_time)) / 3600.0 - 结果保留一位小数可加
ROUND(..., 1),但注意:不要在聚合前四舍五入,否则影响总工时精度 - 警惕零值:若某天只打了上班卡没打下班卡,
TIMEDIFF会返回NULL,整个表达式也为NULL—— 建议用COALESCE(..., 0)显式补零或单独标记缺卡
按员工+日期分组计算每日工时(关键分组逻辑)
考勤必须按「人 + 天」粒度算,不能只靠 DATE(check_in_time),因为晚班可能跨日(如22:00打卡→次日06:00打卡)。实际工作日应以「班次定义」或「签到日期」为准,多数系统采用签到日期作为工作日基准。
- 基础分组语句:
GROUP BY emp_id, DATE(check_in_time) - 若需支持夜班(如班次表有
shift_start和shift_end),得关联班次表并用逻辑判断归属日,不能硬套DATE() - 一个常见坑:同一天多次打卡(如加班再打卡),此时需先取最早签到、最晚签退,用
MIN(check_in_time)和MAX(check_out_time)聚合,而不是直接TIMEDIFF每条记录
处理异常打卡场景:缺卡、反向打卡、多卡混排
真实考勤数据脏,不能假设每人都有且仅有两条记录。直接 JOIN 上下班卡或 LAG/LAG 窗口函数容易出错,建议用条件聚合先规整数据。
- 用
MAX(CASE WHEN type = 'in' THEN time END)和MAX(CASE WHEN type = 'out' THEN time END)分别提取当日有效进出时间 - 避免用
LEAD()按时间排序后配对——万一中间夹了请假打卡或测试卡,就会错配 - MySQL 8.0+ 可用
ROW_NUMBER() OVER (PARTITION BY emp_id, DATE(time) ORDER BY time)标序号,再筛选序号为1和末位的记录,但需额外处理单卡情况











