
本文详解如何在 Laravel 中构建用户维度的订单与发货数量统计报表,支持自定义起止日期筛选,通过 withCount 与条件关联查询精准获取每位用户在指定周期内的订单数和发货数。
本文详解如何在 laravel 中构建用户维度的订单与发货数量统计报表,支持自定义起止日期筛选,通过 `withcount` 与条件关联查询精准获取每位用户在指定周期内的订单数和发货数。
在开发后台报表功能时,常需统计每位用户在某段时间内的业务行为数据,例如订单提交量与发货请求量。本教程将基于你已定义的 Eloquent 关系(User ↔ Order、User ↔ Shipping),提供健壮、高效且可维护的查询实现方案。
✅ 正确做法:统一时间条件 + 条件聚合
你原代码中存在几个关键问题:
- whereHas() 内部误调用 get(),破坏了链式查询;
- orWhereHas() 未包裹在闭包中,导致 SQL 逻辑错误(OR 优先级干扰主 WHERE);
- groupBy('created_at') 在子查询中无意义,且会阻碍计数聚合;
- orderByRaw('orders_count + shippings_count DESC') 依赖 withCount 生成的字段,但原始查询未确保所有用户都满足时间条件(可能导致部分用户计数为 0 却被排除)。
✅ 推荐写法(无 scope 版):
$report = User::withCount([
'orders' => function ($q) use ($from, $to) {
if (!empty($from)) {
$q->whereDate('created_at', '>=', $from);
}
if (!empty($to)) {
$q->whereDate('created_at', '<=', $to);
}
},
'shippings' => function ($q) use ($from, $to) {
if (!empty($from)) {
$q->whereDate('created_at', '>=', $from);
}
if (!empty($to)) {
$q->whereDate('created_at', '<=', $to);
}
}
])
->whereHas('shippings', function ($q) use ($from, $to) {
if (!empty($from)) {
$q->whereDate('created_at', '>=', $from);
}
if (!empty($to)) {
$q->whereDate('created_at', '<=', $to);
}
})
->orWhereHas('orders', function ($q) use ($from, $to) {
if (!empty($from)) {
$q->whereDate('created_at', '>=', $from);
}
if (!empty($to)) {
$q->whereDate('created_at', '<=', $to);
}
})
->orderByRaw('orders_count + shippings_count DESC')
->paginate(25);⚠️ 注意:whereHas(...)->orWhereHas(...) 的组合默认会生成 (has_shippings AND has_orders) OR ... 的歧义逻辑。为确保语义清晰(即「有该时间段内订单 或 发货的用户」),建议外层包裹 where(function () { ... }):
$report = User::withCount([
'orders' => fn($q) => $this->applyDateFilter($q, $from, $to),
'shippings' => fn($q) => $this->applyDateFilter($q, $from, $to),
])
->where(function ($q) use ($from, $to) {
$q->whereHas('shippings', fn($sub) => $this->applyDateFilter($sub, $from, $to))
->orWhereHas('orders', fn($sub) => $this->applyDateFilter($sub, $from, $to));
})
->orderByRaw('orders_count + shippings_count DESC')
->paginate(25);并在 Controller 或 Trait 中定义复用方法:
protected function applyDateFilter($query, $from, $to)
{
if ($from) $query->whereDate('created_at', '>=', $from);
if ($to) $query->whereDate('created_at', '<=', $to);
return $query;
}✅ 进阶优化:添加全局作用域(推荐)
为提升可读性与复用性,可在 Order 和 Shipping 模型中添加本地作用域:
// App\Models\Order.php
public function scopeWhereDateBetween($builder, $from = null, $to = null)
{
if ($from) {
$builder->whereDate('created_at', '>=', $from);
}
if ($to) {
$builder->whereDate('created_at', '<=', $to);
}
return $builder;
}同理为 Shipping 模型添加相同作用域。之后查询可大幅简化:
$report = User::withCount([
'orders' => fn($q) => $q->whereDateBetween($from, $to),
'shippings' => fn($q) => $q->whereDateBetween($from, $to),
])
->where(function ($q) use ($from, $to) {
$q->whereHas('shippings', fn($sub) => $sub->whereDateBetween($from, $to))
->orWhereHas('orders', fn($sub) => $sub->whereDateBetween($from, $to));
})
->orderByDesc(DB::raw('orders_count + shippings_count'))
->paginate(25);? 使用说明与注意事项
- 日期格式:确保 $from 和 $to 是 Y-m-d 格式字符串(如 '2024-01-01'),whereDate() 会自动忽略时间部分,避免时区偏差;
- 空值处理:若仅选开始日期或结束日期,上述逻辑仍能正确生效;
- 性能提示:为 created_at 字段添加数据库索引(尤其在大数据量场景下);
- 前端校验:建议在表单侧限制 $to >= $from,并传递 ISO 标准日期;
- 空结果处理:分页结果中,orders_count 和 shippings_count 均为整数(含 0),无需额外判空。
最终,你可在 Blade 模板中直接访问:
@foreach($report as $user)
<tr>
<td>{{ $user->name }}</td>
<td>{{ $user->orders_count }}</td>
<td>{{ $user->shippings_count }}</td>
<td>{{ $user->orders_count + $user->shippings_count }}</td>
</tr>
@endforeach此方案兼顾准确性、可维护性与扩展性,是 Laravel 报表类查询的最佳实践之一。


















