
本文讲解如何使用 laravel 的 eloquent 和 query builder 正确实现三表(clients、invoices、tickets)左连接查询,确保即使某客户无发票或无工单也能完整返回,并准确计算各自总额及合计金额。
本文讲解如何使用 laravel 的 eloquent 和 query builder 正确实现三表(clients、invoices、tickets)左连接查询,确保即使某客户无发票或无工单也能完整返回,并准确计算各自总额及合计金额。
在 Laravel 中执行跨三表的聚合统计(如客户总消费 = 发票总额 + 工单总额)时,一个常见误区是将 groupBy 错误地写在 leftJoin 的闭包内——这不仅语法非法(Laravel 不允许在 join 闭包中调用 groupBy),更会导致逻辑错误:数据库会先按条件过滤再连接,从而丢失无关联记录的客户。
正确做法是:仅对主表 clients 使用 leftJoin,且所有 WHERE 条件必须置于 ON 子句中(而非 where),同时将 GROUP BY 移至最终查询层级,并配合 COALESCE 处理 NULL 汇总值。以下是优化后的标准写法:
$invoice_year = 2024; // 示例年份
$clients = Client::select(
'clients.id',
'clients.name',
'clients.nif',
\DB::raw('COALESCE(SUM(invoices.total), 0) as total1'),
\DB::raw('COALESCE(SUM(tickets.total), 0) as total2'),
\DB::raw('COALESCE(SUM(invoices.total), 0) + COALESCE(SUM(tickets.total), 0) as total')
)
->leftJoin('invoices', function ($join) use ($invoice_year) {
$join->on('clients.id', '=', 'invoices.client_id')
->whereYear('invoices.invoice_date', '=', $invoice_year);
})
->leftJoin('tickets', function ($join) use ($invoice_year) {
$join->on('clients.id', '=', 'tickets.client_id')
->whereYear('tickets.created_at', '=', $invoice_year);
})
->groupBy('clients.id', 'clients.name', 'clients.nif')
->havingRaw('COALESCE(SUM(invoices.total), 0) + COALESCE(SUM(tickets.total), 0) > ?', [3005])
->get();✅ 关键修正说明:
- whereYear 移入 on():避免 LEFT JOIN 变成 INNER JOIN 效果,保证无发票/无工单的客户仍被保留;
- 移除 groupBy 在 join 闭包中:该写法在 Laravel 中无效且会报错;
- 使用 COALESCE(..., 0):将 SUM() 对空关联产生的 NULL 转为 0,确保算术运算可靠;
- havingRaw 替代 having('total', >, ...):因 total 是计算字段,需用原始表达式;
⚠️ 注意事项:
- 不要尝试使用 FULL JOIN(MySQL 不原生支持,且语义不符:此处目标是“以 clients 为主”,非全集合并);
- 若需兼容软删除客户,记得添加 ->whereNull('clients.deleted_at');
- 大数据量下建议为 invoices.client_id 和 tickets.client_id 添加索引,提升 JOIN 性能。
此方案可稳定返回全部客户记录,并精确统计各维度金额,彻底解决“客户丢失”问题。


















