
本文详解如何在 Laravel 中基于日期范围(from/to)高效统计每个用户的订单数和发货数,通过 withCount + 条件闭包 + whereHas 组合实现精准、可分页的报表查询。
本文详解如何在 laravel 中基于日期范围(from/to)高效统计每个用户的订单数和发货数,通过 `withcount` + 条件闭包 + `wherehas` 组合实现精准、可分页的报表查询。
在构建用户行为分析报表时,常需统计每位用户在指定时间段内的订单量(orders_count)和发货单量(shippings_count)。关键在于:既要确保仅统计符合日期条件的关联记录,又要避免因空日期参数导致 SQL 错误,同时保证分页结果逻辑正确(即只返回至少有一笔订单或发货记录的用户)。
✅ 正确实现方式
核心原则是:所有日期过滤逻辑必须统一应用在 withCount 的闭包中,并配合 whereHas 精确筛选主模型。以下为推荐写法(兼容 $from 或 $to 为空的情况):
$report = User::withCount([
'orders' => function ($query) use ($from, $to) {
if (!empty($from)) {
$query->whereDate('created_at', '>=', $from);
}
if (!empty($to)) {
$query->whereDate('created_at', '<=', $to);
}
},
'shippings' => function ($query) use ($from, $to) {
if (!empty($from)) {
$query->whereDate('created_at', '>=', $from);
}
if (!empty($to)) {
$query->whereDate('created_at', '<=', $to);
}
}
])
->whereHas('shippings', function ($query) use ($from, $to) {
if (!empty($from)) {
$query->whereDate('created_at', '>=', $from);
}
if (!empty($to)) {
$query->whereDate('created_at', '<=', $to);
}
})
->orWhereHas('orders', function ($query) use ($from, $to) {
if (!empty($from)) {
$query->whereDate('created_at', '>=', $from);
}
if (!empty($to)) {
$query->whereDate('created_at', '<=', $to);
}
})
->orderByRaw('orders_count + shippings_count DESC')
->paginate(25);⚠️ 注意:whereHas(...)->orWhereHas(...) 构成 OR 关系,确保返回“有订单 或 有发货”的用户;而 withCount 中的闭包则独立控制各关联表的计数范围,二者逻辑一致,避免数据错位。
Laravel Cloud下载Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
? 进阶优化:封装为本地作用域(推荐)
为提升复用性与可读性,可在 Order 和 Shipping 模型中添加作用域方法:
// App\Models\Order.php & App\Models\Shipping.php
public function scopeWhereDateBetween($builder, $from, $to)
{
if (!empty($from)) {
$builder->whereDate('created_at', '>=', $from);
}
if (!empty($to)) {
$builder->whereDate('created_at', '<=', $to);
}
return $builder;
}调用时更简洁清晰:
$report = User::withCount([
'orders' => fn ($q) => $q->whereDateBetween($from, $to),
'shippings' => fn ($q) => $q->whereDateBetween($from, $to),
])
->whereHas('shippings', fn ($q) => $q->whereDateBetween($from, $to))
->orWhereHas('orders', fn ($q) => $q->whereDateBetween($from, $to))
->orderByRaw('orders_count + shippings_count DESC')
->paginate(25);? 关键注意事项
- ❌ 避免在 whereHas 闭包中调用 get() 或 groupBy() —— 这会中断查询链并引发异常;
- ✅ 使用 whereDate() 而非 whereBetween('created_at', [...]),可忽略时间部分,防止因时区或精度导致漏统计;
- ✅ withCount 的闭包只影响计数逻辑,不影响主查询结果集,因此必须搭配 whereHas/orWhereHas 显式筛选有效用户;
- ✅ 若需包含无任何订单/发货的用户(如显示 0 计数),应移除 whereHas/orWhereHas,仅保留 withCount —— 但此时需注意分页性能(全量用户扫描);
- ✅ 建议为 orders.created_at 和 shippings.created_at 字段添加数据库索引,大幅提升查询效率。
最终生成的 $report 是一个 LengthAwarePaginator 实例,视图中可直接遍历并访问 user->orders_count 与 user->shippings_count 属性,实现清晰、高性能的运营报表功能。


















