Laravel Eloquent支持子查询,可通过addSelect获取用户最新订单时间,where条件中嵌套查询筛选订单总额超1000的用户,joinSub将子查询结果作为临时表连接,或使用withSum等快捷方法预加载聚合数据,提升复杂查询可读性与效率。

在 Laravel 中,Eloquent 支持通过子查询(Subquery)来实现更复杂的查询逻辑。你可以将一个查询的结果作为另一个查询的字段或条件使用,尤其适用于获取关联数据中的最新记录、统计值等场景。下面介绍几种常见的使用方式。
在 SELECT 中使用子查询
如果你想在查询结果中包含一个来自子查询的字段,比如获取每个用户的最后一条订单信息,可以这样做:
假设你有 User 模型和 Order 模型,你想查出每个用户的名字和他们最近订单的创建时间。
use Illuminate\Database\Eloquent\Builder;
$users = User::select('users.*')
->addSelect(['last_order_at' => Order::select('created_at')
->whereColumn('user_id', 'users.id')
->orderByDesc('created_at')
->limit(1)
])
->get();
这里使用了 addSelect() 添加一个子查询字段,该子查询获取当前用户最新的订单时间。
在 WHERE 条件中使用子查询
你可以用子查询作为 WHERE 的判断条件。例如:找出订单总额超过 1000 的用户。
$totalQuery = Order::selectRaw('sum(amount)')
->whereColumn('user_id', 'users.id');
$users = User::whereHas('orders')
->where(function (Builder $query) use ($totalQuery) {
$query->select($totalQuery)->from('orders')->limit(1)->shouldAllowSelfJoins()
}, '>', 1000)
->get();
也可以更简洁地写成:
在现实生活中的购物过程,购物者需要先到商场,找到指定的产品柜台下,查看产品实体以及标价信息,如果产品合适,就将该产品放到购物车中,到收款处付款结算。电子商务网站通过虚拟网页的形式在计算机上摸拟了整个过程,首先电子商务设计人员将产品信息分类显示在网页上,用户查看网页上的产品信息,当用户看到了中意的产品后,可以将该产品添加到购物车,最后使用网上支付工具进行结算,而货物将由公司通过快递等方式发送给购物者
$users = User::whereExists(function ($query) {
$query->select(DB::raw(1))
->from('orders')
->whereColumn('orders.user_id', 'users.id')
->havingRaw('sum(amount) > 1000')
->groupBy('orders.user_id');
})->get();
在 JOIN 中使用子查询
有时你需要将子查询作为一个临时表进行 JOIN。例如获取每个用户的最新一条订单。
$latestOrderSubquery = Order::select('user_id', DB::raw('max(created_at) as last_order_at'))
->groupBy('user_id');
$users = User::joinSub($latestOrderSubquery, 'latest_orders', function ($join) {
$join->on('users.id', '=', 'latest_orders.user_id');
})->get();
joinSub() 方法允许你把一个查询当作一个子查询表来 JOIN,非常适用于聚合或去重场景。
使用 withSum、withMax 等快捷方法(Laravel 8+)
Laravel 提供了一些便捷的预加载子查询方法,如 withSum、withMax、withAvg 等。
// 获取用户及其订单总金额
$users = User::withSum('orders as total_spent', 'amount')->get();
foreach ($users as $user) {
echo $user->total_spent; // 直接访问
}
这种方式比手动写子查询更清晰,适合简单的聚合场景。
基本上就这些。Laravel 的 Eloquent 子查询功能强大且灵活,合理使用能让复杂查询变得简洁易懂。关键在于理解何时使用 selectSub、joinSub 或内置的 withXxx 方法。不复杂但容易忽略细节,比如别名定义和作用域绑定。









