必须用PARTITION BY user_id和ORDER BY record_time(建议加id防重复)确保按人按时间取相邻记录;LAG取上一条、LEAD取下一条,用于计算时间差识别迟到早退等异常。

LEAD 和 LAG 函数怎么选:按时间顺序取相邻记录
必须用 ORDER BY 明确排序,否则结果不可靠。打卡数据天然依赖时间戳,所以 LAG(record_time) 取上一条打卡时间,LEAD(record_time) 取下一条——但注意:数据库不会自动按人分组,不加 PARTITION BY user_id 就会把张三的下班和李四的上班连起来算,导致误判。
典型错误写法:LAG(record_time) OVER (ORDER BY record_time) —— 缺少分区,跨用户混排。
正确写法应是:LAG(record_time) OVER (PARTITION BY user_id ORDER BY record_time)。
- 只对同一
user_id内的记录窗口排序 -
ORDER BY必须包含能唯一确定先后的字段(如record_time, id),避免同秒多条时结果不稳定 - 若打卡表有重复时间且无主键辅助排序,
LAG/LEAD可能随机取值,建议加id保序
识别“早退”和“迟到”:用时间差判断异常类型
拿到前一条/后一条时间后,直接做减法即可。比如:当前打卡是下班(type = 'OUT'),但 LEAD(record_time) 是第二天的上班,说明中间没打卡;或者 LAG(record_time) 是当天上午上班,但间隔不足 8 小时,大概率早退。
示例逻辑(PostgreSQL/MySQL 8.0+):
SELECT user_id, record_time, type, LAG(record_time) OVER (PARTITION BY user_id ORDER BY record_time) AS prev_time, LEAD(record_time) OVER (PARTITION BY user_id ORDER BY record_time) AS next_time, EXTRACT(EPOCH FROM (record_time - LAG(record_time) OVER (PARTITION BY user_id ORDER BY record_time))) / 3600 AS hours_since_last FROM attendance_log WHERE record_time >= '2024-06-01';
-
EXTRACT(EPOCH ...)仅 PostgreSQL;MySQL 用TIMESTAMPDIFF(HOUR, prev_time, record_time) - 别直接用
hours_since_last 判早退——要先确认前一条是IN、当前是OUT,否则上下班连打两次会误报 - 跨天场景(如夜班)需额外处理:用
DATE(prev_time) = DATE(record_time)过滤或改用日期偏移判断
“漏打卡”怎么标:NULL 值不是 bug,是线索
LAG() 对每个分组第一条返回 NULL,LEAD() 对最后一条也返回 NULL。这恰恰是识别异常的入口:如果某人当天有 IN 无 OUT,那最后一个 IN 的 LEAD(type) 就是 NULL;反之,第一个 OUT 的 LAG(type) 是 NULL,说明缺上班卡。
实用技巧:
- 用
CASE WHEN LAG(type) IS NULL AND type = 'OUT' THEN 'missing IN'直接标记 - 避免用
IS NULL判断时间字段本身——某些系统会把无效打卡存为'0001-01-01'而非NULL,查前先清洗 - Oracle 用户注意:
LAG(..., 1, '1970-01-01')可设默认值,避免 NULL 处理分支过多
性能和边界问题:千万级打卡表别硬扫
在 user_id, record_time 上建联合索引是刚需。没有它,PARTITION BY + ORDER BY 窗口函数会触发全表排序,1000 万行可能跑几分钟。
另外几个易忽略点:
- 业务上通常只查近 30 天,
WHERE条件务必写在窗口函数外层,别放子查询里——有些引擎(如旧版 MySQL)无法下推条件 - 如果打卡设备有延迟上报,
record_time和真实时间偏差 >5 分钟,比对逻辑要加容错(例如用ABS(TIMESTAMPDIFF(MINUTE, prev_time, record_time)) > 480替代严格 8 小时) - 一个用户单日多次进出(如外出返岗),不能简单按 IN/OUT 成对匹配——得结合业务规则,比如只认最早 IN 和最晚 OUT,其余视为异常
真正难的不是写出 LAG/LEAD,而是定义清楚“什么是异常”。时间差阈值、跨天逻辑、设备误差容忍、多进出场景……这些都得和 HR 或考勤系统对齐,SQL 只负责忠实执行规则。










