Laravel 高级 Eloquent 查询:用 withCount、whereHas 与子查询把统计留在数据库层
test12026-10-173 次阅读
问题
列表页常需要“每个用户有多少订单”“筛选出有未完成订单的用户”。如果在 PHP 里循环查,会写出 N+1 甚至更糟的查询。把这些统计交给数据库,用 Eloquent 的聚合方法解决。
关联计数:withCount
use App\Models\User;
// 每个用户带一个 orders_count 字段,一条 SQL 搞定
$users = User::withCount('orders')->get();
foreach ($users as $user) {
echo $user->name . ':' . $user->orders_count;
}
// 带条件的计数
$users = User::withCount(['orders' => function ($q) {
$q->where('status', 'paid');
}])->get();
withCount 会在主查询里用子查询算出数量,结果作为 orders_count 挂在模型上,不再为每个人单独发 SQL。
用 whereHas 筛选存在性
use App\Models\User;
// 只要“下过订单”的用户
$paidUsers = User::whereHas('orders', function ($q) {
$q->where('status', 'paid');
})->get();
// 反过来:没有任何未完成订单的用户
$clean = User::whereDoesntHave('orders', function ($q) {
$q->where('status', 'pending');
})->get();
子查询:select 里嵌一个聚合
use Illuminate\Support\Facades\DB;
use App\Models\Order;
$latest = Order::select('user_id')
->selectRaw('MAX(created_at) as last_order_at')
->groupBy('user_id');
$users = User::query()
->joinSub($latest, 'latest', function ($join) {
$join->on('users.id', '=', 'latest.user_id');
})
->orderByDesc('latest.last_order_at')
->paginate(20);
工程建议
- 列表统计优先用 withCount / withSum,避免循环里查库。
- whereHas 的闭包里加索引友好的条件,子查询越轻量越好。
- 复杂聚合用 joinSub 把子查询当成临时表,可读性比手写 DB::raw 好。
- 给关联外键和状态字段加索引,否则 join 与 exists 会在数据量大时变慢。
T
test1
文章作者
问题 列表页常需要“每个用户有多少订单”“筛选出有未完成订单的用户”。如果在 PHP 里循环查,会写出 N+1 甚至更...
- 分类
- 技术
- 发布时间
- 2026-10-17
- 字数
- 约 1343 字
- 阅读
- 3 次
