MySQL查询:如何添加30天前缺勤记录的WHERE条件及问题修正
搞定MySQL查询30天内无出勤记录学生的问题
先看你给出的原查询,确实踩了几个坑:
- 表别名写错了:
can.can_status = 1里的can应该是你定义的出勤表别名at(attend_track at) - 关联条件完全无效:
st.std_id = st.std_id这是个永远成立的等式,根本没把学生表和出勤表关联起来,直接导致了笛卡尔积,这也是你拿到错误结果(比如CCC、DDD这些近30天有出勤的学生也被查出来)的核心原因之一 - 逻辑跑偏:你要找的是近30天没有出勤记录的学生,但原查询里的
DATEDIFF(NOW(), DATE(st.created)) > 30是在筛选“学生账号创建超过30天”,和缺勤需求完全不搭边
针对你提到的20万+数据量的场景,NOT IN和<>子查询确实容易卡,下面给两个高效的方案,再加上索引优化建议:
方案1:LEFT JOIN + IS NULL(性能最优,大数据量首选)
这种方式比NOT IN靠谱多了,不仅性能更好,还不会因为子查询里的NULL值导致结果异常。
SELECT st.* FROM student st LEFT JOIN attend_track at ON st.std_id = at.std_id AND at.created >= DATE_SUB(NOW(), INTERVAL 30 DAY) WHERE st.can_status = 1 -- 这里默认你是要筛选状态正常的学生,如果can_status是出勤表的字段,记得调整位置 AND at.std_id IS NULL ORDER BY st.created DESC; -- 要是想按出勤相关时间排序,换成at.created就行,不过这里at是NULL,所以按学生表的字段排序更合理
逻辑拆解:
- 用
LEFT JOIN把学生表和近30天内的出勤记录关联起来 at.std_id IS NULL就代表这个学生在近30天里没有任何出勤记录,正好符合你的缺勤需求- 注意:如果
can_status是attend_track的字段,那得把这个条件移到JOIN的ON子句里,不然会过滤掉没有出勤记录的学生
方案2:NOT EXISTS(性能接近LEFT JOIN,代码更直观)
MySQL对NOT EXISTS的优化做得很到位,尤其是当出勤表有合适的索引时,查询速度会非常快。
SELECT st.* FROM student st WHERE st.can_status = 1 AND NOT EXISTS ( SELECT 1 FROM attend_track at WHERE at.std_id = st.std_id AND at.created >= DATE_SUB(NOW(), INTERVAL 30 DAY) ) ORDER BY st.created DESC;
逻辑拆解:
- 子查询专门检查当前学生在近30天内有没有出勤记录
NOT EXISTS会在找到第一条匹配的出勤记录时就停止查询,比NOT IN那种要遍历所有结果的方式高效得多
必做的索引优化(针对20万+数据)
要让上面的查询跑起来不卡,一定要给attend_track表加个复合索引:
CREATE INDEX idx_attend_std_created ON attend_track(std_id, created);
这个索引能让数据库直接定位到某个学生近30天的出勤记录,避免全表扫描,性能提升非常明显。
另外,如果student表的can_status字段经常用来筛选,也可以给它加个单独索引:
CREATE INDEX idx_student_can_status ON student(can_status);
原查询的语法修正(仅作参考,逻辑不对)
如果你只是想先把原查询的语法错误改过来,修正后的语句是这样的,但逻辑还是不符合你的需求(它找的是有30天前出勤记录的学生,不是近30天没出勤的):
SELECT st.* FROM student st JOIN attend_track at ON st.std_id = at.std_id WHERE at.can_status = 1 -- 修正了别名错误 AND DATEDIFF(NOW(), DATE(at.created)) > 30 -- 这里是筛选出勤记录在30天前的 ORDER BY UNIX_TIMESTAMP(at.created) DESC;
内容的提问来源于stack exchange,提问作者Raj Mohan
相关产品推荐
相关产品推荐

