
本文介绍在 laravel 8+ 中,如何对一对多关系(如买家与订单)执行带时间条件的聚合查询,同时获取关联记录数、指定列(如 price 和 charge)的条件求和,并确保代码简洁、可维护且性能优良。
本文介绍在 laravel 8+ 中,如何对一对多关系(如买家与订单)执行带时间条件的聚合查询,同时获取关联记录数、指定列(如 price 和 charge)的条件求和,并确保代码简洁、可维护且性能优良。
在 Laravel 开发中,常需对模型关联数据进行条件聚合——例如:查询所有买家,并附带其在某时间段内订单的总数、订单金额总和(price)及手续费总和(charge)。这类需求若用 N+1 查询或手动遍历处理,将严重损害性能;而借助 Laravel 内置的 withCount 和 withSum 方法,配合闭包约束条件,即可在单次 SQL 查询中高效完成。
✅ 正确实现方式(Laravel 8+)
假设 Buyer 模型已定义 orders() 一对多关系,且 $dateTo 是起始时间(如 Carbon::parse('2024-01-01')),推荐写法如下:
$buyers = Buyer::withCount([
'orders' => fn ($query) => $query->where('created_at', '>=', $dateTo)
])
->withSum([
'orders' => fn ($query) => $query->where('created_at', '>=', $dateTo)
], 'price')
->withSum([
'orders' => fn ($query) => $query->where('created_at', '>=', $dateTo)
], 'charge')
->get();
执行后,每个 $buyer 实例将自动附加以下属性:
- orders_count:该买家在 $dateTo 之后的订单总数;
- orders_price_sum:对应订单的 price 列之和;
- orders_charge_sum:对应订单的 charge 列之和。
? 注意:withSum() 是 Laravel 8.0+ 新增方法,低版本需升级或改用 selectRaw + join 手动构建子查询。
Laravel Cloud下载Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
? 关键要点说明
- 变量传递:闭包中使用外部变量(如 $dateTo)必须通过 fn ($query) => ... 箭头函数(PHP 7.4+)自动绑定,或显式使用 use ($dateTo)(传统匿名函数写法);
- 复用条件:虽然需为 withCount 和每个 withSum 单独传入闭包,但逻辑完全一致,便于统一维护;
- 字段别名:Laravel 自动将 withSum('orders', 'price') 映射为 orders_price_sum 属性名,不可自定义,如需重命名建议使用 selectRaw + as(见下文进阶方案);
- 空值处理:若某买家无符合条件的订单,*_sum 字段值为 null,建议在展示层做 ?? 0 处理。
⚙️ 进阶:自定义别名与多条件聚合(兼容性更强)
若需更灵活控制(如别名定制、复合条件或旧版本支持),可采用 selectSub 或原生聚合子查询:
$buyers = Buyer::select('buyers.*')
->selectSub(
DB::table('orders')
->whereColumn('orders.buyer_id', 'buyers.id')
->where('orders.created_at', '>=', $dateTo)
->selectRaw('COUNT(*)'),
'orders_count'
)
->selectSub(
DB::table('orders')
->whereColumn('orders.buyer_id', 'buyers.id')
->where('orders.created_at', '>=', $dateTo)
->selectRaw('COALESCE(SUM(price), 0)'),
'total_price'
)
->selectSub(
DB::table('orders')
->whereColumn('orders.buyer_id', 'buyers.id')
->where('orders.created_at', '>=', $dateTo)
->selectRaw('COALESCE(SUM(charge), 0)'),
'total_charge'
)
->get();
此方式生成一条含多个标量子查询的 SQL,兼容 Laravel 6.0+,且字段名完全可控,适合复杂场景。
✅ 总结
- 优先使用 withCount() + withSum() 组合,语义清晰、性能优秀、代码简洁;
- 务必确认 Laravel 版本 ≥ 8.0,否则 withSum 不可用;
- 时间条件需在每个聚合闭包中重复声明,但可通过封装为辅助函数提升复用性;
- 生产环境建议为 orders.buyer_id 和 orders.created_at 添加联合索引,显著加速条件聚合查询。
通过合理运用 Laravel 的关系聚合能力,你可以在不牺牲可读性的前提下,写出高性能、易维护的数据统计逻辑。











