
本文介绍在 Laravel 中通过 Eloquent 关系与查询作用域,精准筛选出当月至少有一次考勤记录的员工,并返回其总数或列表,核心依赖 has() 方法结合日期范围约束。
本文介绍在 laravel 中通过 eloquent 关系与查询作用域,精准筛选出当月至少有一次考勤记录的员工,并返回其总数或列表,核心依赖 `has()` 方法结合日期范围约束。
在实际人事考勤系统中,常需统计“某个月份有实际出勤记录的员工人数”,而非简单统计所有员工或所有考勤条目。此时不能仅用 has('Attendance')(它只判断是否存在关联记录,无时间过滤),而需进一步对 attendances.in_time 字段施加月份条件。
✅ 正确实现方式:使用 whereHas() 进行带条件的关系存在性查询
has() 适用于无条件的关联存在判断;但本需求要求“当月至少一条考勤”,必须使用更灵活的 whereHas(),并在闭包中限定 in_time 的月份范围:
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
// 假设输入月份为 '2024-06'(格式:YYYY-MM)
$targetMonth = '2024-06';
$employees = Employee::whereHas('Attendance', function ($query) use ($targetMonth) {
$start = Carbon::createFromFormat('Y-m', $targetMonth)->startOfMonth();
$end = Carbon::createFromFormat('Y-m', $targetMonth)->endOfMonth();
$query->whereBetween('in_time', [$start, $end]);
})->get();
// 获取员工总数(即当月有考勤的员工数)
$employeeCount = $employees->count();
// 若只需数量,可直接用 count() 避免加载模型实例(更高效):
$employeeCount = Employee::whereHas('Attendance', function ($query) use ($targetMonth) {
$start = Carbon::createFromFormat('Y-m', $targetMonth)->startOfMonth();
$end = Carbon::createFromFormat('Y-m', $targetMonth)->endOfMonth();
$query->whereBetween('in_time', [$start, $end]);
})->count();
⚠️ 注意事项与最佳实践
-
数据库时区一致性:确保
in_time字段存储为DATETIME类型,且 PHP、MySQL 时区配置一致(推荐统一为 UTC 或本地标准时区),避免Carbon解析偏差。 -
索引优化:为
attendances.employees_id和attendances.in_time字段建立联合索引,大幅提升whereHas查询性能:CREATE INDEX idx_attendance_emp_time ON attendances(employees_id, in_time);
-
避免 N+1 查询:若后续需展示每位员工当月考勤明细,应使用
with()预加载并约束子查询:$employees = Employee::whereHas('Attendance', /* ... */) ->with(['Attendance' => function ($q) use ($start, $end) { $q->whereBetween('in_time', [$start, $end]); }]) ->get(); -
月份参数校验:生产环境务必验证
$targetMonth格式(如使用filter_var($month, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^\d{4}-\d{2}$/']])),防止 SQL 注入或逻辑错误。
✅ 总结
统计“某月有考勤的员工数”本质是带条件的反向关系存在性查询,应优先选用 whereHas() 而非 has()。配合 Carbon 精确构造月份区间、合理添加数据库索引,并根据业务需要选择 get()(获取对象)或 count()(仅计数),即可高效、安全、可维护地完成该任务。










