Eloquent关联空子查询报错问题及最优实现方案咨询
问题描述
有一张名为activities的表,包含id、sport_id、date等字段。需求是按sport_id分组,获取各运动项目最近一次记录的全部字段。
原生MySQL查询可实现该需求,且空表时正常运行:
SELECT * FROM activities AS a LEFT JOIN ( SELECT sport_id, MAX(date) AS date FROM activities GROUP BY sport_id ) AS r ON r.sport_id=a.sport_id WHERE r.date=a.date
但转换为Eloquent代码后,空表场景下会抛出**"A subquery must be a query builder instance, a Closure, or a string."**错误。尝试的Eloquent代码如下:
$recent = Activity::orderBy('date', 'desc')->groupBy('sport_id')->select('sport_id', \DB::raw('MAX(date) as date'))->get(); $activity = Activity::joinSub($recent, 'recent', function ($join) { $join->on('recent.sport_id', '=', 'activity.sport_id') ->where('recent.date', '=', 'activity.date'); })->get();
需要在Eloquent中优雅解决该问题,最终用于生成各运动项目最近参与时间的列表,也欢迎提供更优实现思路。
解决方案
1. 修复原代码的核心问题
原代码报错的原因是$recent调用了get(),返回的是Collection实例,而joinSub()要求传入查询构建器(Query Builder)对象。空表时get()返回空集合,无法作为子查询参数,导致报错。
修改方法:去掉get(),保留查询构建器实例:
// 保留查询构建器,不执行查询 $recentQuery = Activity::groupBy('sport_id') ->select('sport_id', \DB::raw('MAX(date) as date')); $activities = Activity::joinSub($recentQuery, 'r', function ($join) { $join->on('r.sport_id', '=', 'activities.sport_id') ->on('r.date', '=', 'activities.date'); // 用on替代where,贴合原生SQL的JOIN逻辑 })->get();
无论表是否为空,$recentQuery都是合法的查询构建器对象,不会触发报错。
2. 更简洁的实现:使用窗口函数(MySQL 8.0+)
利用MySQL的窗口函数ROW_NUMBER(),可以直接筛选每个sport_id下最新的记录,无需JOIN操作:
$activities = Activity::select('*') ->fromRaw('(SELECT *, ROW_NUMBER() OVER (PARTITION BY sport_id ORDER BY date DESC) AS rn FROM activities) AS a') ->where('rn', 1) ->get();
这种写法逻辑直观,空表时返回空集合,符合预期。
3. 基于Eloquent关联的实现(适合有Sport模型场景)
如果已定义Sport模型并与Activity建立一对多关联,可以直接通过关联获取最新记录:
// 在Sport模型中定义关联 public function latestActivity() { return $this->hasOne(Activity::class)->latest('date'); } // 查询时直接获取关联数据 $sportsWithLatestActivity = Sport::with('latestActivity')->get();
这种方式符合ORM设计思想,代码可读性高,空表时对应的latestActivity字段为null。
内容的提问来源于stack exchange,提问作者maganthro
相关产品推荐
相关产品推荐

