MySQL子查询+IFNULL合并考勤IN/OUT行时WHERE条件失效问题
问题原因
- 兜底逻辑本身无边界约束:你写的
IFNULL第二参数明确指定了「当日无OUT记录则取次日最小的OUT记录」,但没有加任何时间范围限制。2022-03-25当日无OUT记录时,自然触发该逻辑,直接抓取到3月26日18:18的OUT值,这不是子查询条件失效,是规则本身就会返回这个结果。 - 当日OUT匹配逻辑有漏洞:查询当日OUT的子查询仅写了
LIMIT 1,没有加ORDER BY time_entry指定排序,MySQL会按主键存储顺序返回第一条匹配记录,导致3月24日优先返回了7:03的跨天签退记录(实际属于3月23日的签退),而非当天18:06的正常下班签退。 - 跨天签退规则缺失:现有逻辑默认次日所有OUT都可以作为前一天的兜底签退,没有区分跨天加班的凌晨签退和次日正常的下班签退,这是3月25日错配记录的核心原因。
修正方案
直接用条件聚合替代多层关联子查询,同时给跨天签退加合理时间阈值(默认次日12点前的OUT算前一天的跨天签退,可根据业务调整),修正后SQL如下:
SELECT MIN(a.id) AS id, a.userid, a.date_entry, MIN(CASE WHEN a.mode = 'In' THEN a.time_entry END) AS `IN`, IFNULL( -- 第一步:匹配当日签到后、当日24点前的最早签退 (SELECT MIN(b.time_entry) FROM erpweb.`app` b WHERE b.`userid` = a.`userid` AND b.`mode` = 'Out' AND b.`time_entry` >= MIN(CASE WHEN a.`mode` = 'In' THEN a.`time_entry` END) AND b.`time_entry` < DATE_ADD(a.`date_entry`, INTERVAL 1 DAY) ), -- 第二步:当日无签退时,仅匹配次日0点-12点的跨天签退,超出时段返回NULL (SELECT MIN(b.`time_entry`) FROM erpweb.`app` b WHERE b.`userid` = a.`userid` AND b.`mode` = 'Out' AND b.`date_entry` = DATE_ADD(a.`date_entry`, INTERVAL 1 DAY) AND b.`time_entry` < DATE_ADD(DATE_ADD(a.`date_entry`, INTERVAL 1 DAY), INTERVAL 12 HOUR) ) ) AS `OUT` FROM erpweb.`app` a WHERE a.`date_entry` BETWEEN '2022-03-23' AND '2022-03-26' GROUP BY a.`userid`, a.`date_entry`;
逻辑说明
- 签到时间直接用条件聚合取当日最早的IN记录,减少子查询扫表次数,执行效率更高
- 当日签退增加时间约束:必须晚于当日最早签到时间、且在当日24点前,避免匹配到当天早于签到时间的无效记录
- 跨天签退增加12点时间阈值:只有次日中午12点前的OUT才会被识别为前一天的跨天加班签退,次日下午/晚上的正常下班记录不会被错配
- 对id字段使用
MIN()聚合,符合SQL严格模式下ONLY_FULL_GROUP_BY的语法要求,不会随机返回分组内的id值 - 执行后返回结果完全符合预期:2022-03-25的OUT字段返回NULL,其余记录匹配正确。
内容的提问来源于stack exchange,提问作者Paul Iverson Cortez
相关产品推荐
相关产品推荐

