
本文介绍如何在 laravel 中使用 spatie query builder 实现“仅依据关联模型(如最后一次检查)的最新数据进行精准筛选”,解决 `wherehas` 无法限制子查询为单条记录的常见痛点。
在 Laravel 应用中,当需要根据关联模型的最新一条记录(而非任意匹配)进行筛选时,直接使用 whereHas() 配合 orderBy()->limit(1) 是无效的——因为 whereHas 的闭包内排序和限制对主查询无约束力,实际仍会匹配所有符合条件的关联记录,导致逻辑错误(例如:动物曾患病但最新检查已康复,却被误判为“生病”)。
正确方案需分三步完成:
- 获取每个动物最新检查记录的 ID(即按 animal_id 分组后取 MAX(id) 或 MAX(created_at) 对应的主键);
- 基于这些 ID 查询出真正“当前患病”的检查记录(disease_id IS NOT NULL);
- 提取对应 animal_id 并反向筛选动物主表。
以下是经过优化、可直接使用的生产级实现:
groupBy('animal_id');
// 步骤2:连接最新检查记录,并筛选 disease_id 非空的动物
$sickAnimalIds = Examination::query()
->select('animal_id')
->whereIn('id', $subQuery->pluck('max_id'))
->whereNotNull('disease_id')
->pluck('animal_id');
// 步骤3:主查询只保留这些动物
$query->whereIn('id', $sickAnimalIds);
}
}✅ 关键改进说明:
- 避免了 get()->pluck() 导致的 N+1 和内存膨胀问题,改用原生子查询(whereIn(...) 内嵌 SELECT),性能更优;
- 使用 MAX(id) 前提是 id 严格递增且代表时间顺序(若依赖 created_at,请改用 MAX(created_at) 并配合 JOIN 或窗口函数);
- 显式使用 whereNotNull('disease_id') 替代 '!=', null,语义更准确且兼容所有数据库。
⚠️ 注意事项:
- 若 examinations 表数据量极大,建议为 (animal_id, id) 或 (animal_id, created_at) 添加复合索引;
- Laravel 9+ 可结合 latestOfMany() 关系定义简化逻辑(需模型支持),但自定义 Filter 中仍推荐上述显式子查询方式以保障可控性与兼容性;
- 此方案不依赖 Spatie 的 withMax 辅助方法(该方法仅用于 eager loading,不可用于 where 条件)。
通过该实现,系统将严格依据每只动物最近一次检查结果判断健康状态,确保筛选结果真实反映当前状况,完美契合医疗、IoT 设备状态监控等强时效性业务场景。










