Laravel 9 多表联查与日统计应按场景区分:展示用模型关联,统计用聚合查询;推荐子查询JOIN对齐日期、手动join+Carbon范围过滤、分表视图合并、空值补0及严格GROUP BY字段一致性。

在 Laravel 9 中处理复杂业务下的多表联查+每日统计数据,关键不是“堆 with()”或“硬写 JOIN”,而是分清场景:是展示用(需关联模型数据),还是统计用(需聚合、过滤、对齐时间维度)。下面直接说实用做法。
按日期对齐的跨表日统计(推荐用子查询 + JOIN)
比如要对比「某用户每天的学习时长」和「全站同日平均学习时长」,两个来源不同(用户表 vs 统计汇总表),且必须按 date 字段严格对齐:
- 用 DB::table() 包裹两个子查询,分别做 GROUP BY date 聚合
- 用 INNER JOIN 关联,确保只返回双方都存在的日期(避免空值干扰曲线图)
- 显式指定别名,如
avg_data.avg_date和user_data.log_date,防止字段冲突 - 绑定参数防注入,
setBindings([Auth::id()])比拼接字符串更安全
带多条件的时间范围统计(Eloquent + Carbon)
当需要统计「某商户最近7天内、状态为 success 的订单总金额」,且涉及 orders、payments、merchants 多张表:
- 不要用
with('payment', 'merchant')再 PHP 层过滤——这会拉取全部记录再筛,慢且内存高 - 改用 join() 手动关联:
Order::join('payments', 'orders.id', '=', 'payments.order_id')->join('merchants', 'orders.merchant_id', '=', 'merchants.id') - 时间用
whereBetween('orders.created_at', [Carbon::now()->subDays(7), now()]) - 聚合前加
selectRaw('DATE(created_at) as day, SUM(payments.amount) as total'),再groupBy('day')
分表场景下的日统计合并(UNION ALL + 视图)
若订单表已按月分表(orders_202601、orders_202602…),仍要查「近30天每日订单数」:
- 建数据库视图
orders_all,定义为SELECT * FROM orders_202609 UNION ALL SELECT * FROM orders_202610等 - Laravel 中新建
OrderAll模型,protected $table = 'orders_all' - 后续查询和单表完全一致:
OrderAll::whereBetween('created_at', [...])->selectRaw('DATE(created_at) day, COUNT(*) cnt')->groupBy('day')->get() - 比在 PHP 里循环查 12 张表快一个数量级,也比手动拼 UNION 更易维护
空值与边界处理(容易踩坑)
统计结果常因 NULL 或无数据导致前端报错或图表断点:
-
sum()返回 NULL 而非 0?统一补?? 0,如$data->sum('amount') ?? 0 - 某天无数据,结果里就缺这一行?后端不补,交给前端用
Array.fill()或 moment.range().map() 补零更灵活 - GROUP BY 时报错 “Expression not in GROUP BY”?检查 SELECT 中是否混入了未聚合也未分组的字段(如写了
select('name', 'created_at')却只groupBy('created_at'))


















