Laravel查询:主表字段为null时取关联表字段排序
Eloquent 考勤记录自定义排序实现
核心问题原因
MySQL 默认ASC升序排序时,NULL值会被判定为最小值,因此time字段为空的记录会排在结果集最顶部,不符合业务排序预期。
实现方案
提供两种可落地的实现方式,按需选择即可:
方案1:数据库层排序(性能优先,推荐数据量较大场景使用)
通过左连取每个考勤记录对应的最新有效修正数据,用orderByRaw写自定义排序规则,排序逻辑在SQL执行阶段完成,性能更高。
替换原有循环内的查询代码如下:
foreach ($employee_calendars as $key => $employee_calendar) { $employee_calendar->attendance = Attendance::where("employee_id", $employee->id) ->whereDate("date", "=", $employee_calendar->date) // 左连子查询:取每个考勤ID对应的最新一条修正记录,可按需加修正状态筛选条件 ->leftJoinSub( AttendanceCorrection::select('attendance_id', 'correct_time') ->whereIn('id', function ($query) { $query->selectRaw('MAX(id)') ->from('attendance_correction') ->groupBy('attendance_id'); // 如果有修正审核状态字段,可在这里加筛选,例:->where('audit_status', 1) }), 'latest_correction', 'latest_correction.attendance_id', '=', 'attendance.id' ) // 自定义排序规则 ->orderByRaw("CASE WHEN attendance.time IS NOT NULL THEN attendance.time WHEN latest_correction.correct_time IS NOT NULL THEN latest_correction.correct_time ELSE '9999-12-31 23:59:59' END ASC") ->with("requests") // 指定查询字段,避免join后修正表同名字段覆盖考勤表原字段 ->select('attendance.*') ->get(); }
方案2:集合层排序(写法简洁,推荐小数据量场景使用)
先按原逻辑取数,拿到Eloquent集合后通过sortBy方法自定义排序逻辑,不需要写原生SQL,代码可读性更高。
替换原有循环内的查询代码如下:
foreach ($employee_calendars as $key => $employee_calendar) { $employee_calendar->attendance = Attendance::where("employee_id", $employee->id) ->whereDate("date", "=", $employee_calendar->date) ->with("requests") ->get() ->sortBy(function ($attendanceItem) { // 原始打卡时间存在,直接用原始时间排序 if (!empty($attendanceItem->time)) { return $attendanceItem->time; } // 原始时间为空,取最新有效修正记录的correct_time排序 $validCorrection = $attendanceItem->requests->sortByDesc('id')->first(); if ($validCorrection && !empty($validCorrection->correct_time)) { return $validCorrection->correct_time; } // 无原始时间也无有效修正时间的异常记录,排到列表最后 return '9999-12-31 23:59:59'; }) ->values(); // 重置集合索引,避免前端遍历出现非连续键名问题 }
注意事项
- 两个方案默认都取每个考勤记录最新的一条修正数据参与排序,如果业务上修正记录需要审核通过才生效,在对应位置加状态筛选条件即可
- 无有效时间的异常记录统一用极大时间值占位,会自动排到当日考勤列表末尾,不会出现在顶部
- 如果使用方案1,必须加
select('attendance.*'),避免左连时修正表的id、created_at等同名字段覆盖考勤表原字段值
内容的提问来源于stack exchange,提问作者Taha Malik
相关产品推荐
相关产品推荐

