
本文详解如何在 laravel 中通过 left join 正确关联 customers、expenses 和 allowances 三张表,并将结果按嵌套 json 结构(含数组形式的 expenses 和 allowances 子集合)组织输出,避免空值遗漏与语法错误。
本文详解如何在 laravel 中通过 left join 正确关联 customers、expenses 和 allowances 三张表,并将结果按嵌套 json 结构(含数组形式的 expenses 和 allowances 子集合)组织输出,避免空值遗漏与语法错误。
在 Laravel 中执行跨表 JOIN 查询时,若目标是获取主表(如 customers)记录及其一对多附属数据(如多个 expenses 和多个 allowances),直接使用原生 SQL 风格的 leftjoin() 链式调用是最简洁可靠的方式——它比手动配置 JoinClause 更安全、更符合 Eloquent 惯例,也避免了因 type 属性误设导致的 JOIN 类型失效问题。
以下为推荐写法(已修正原始代码中的关键错误):
$logs = Customer::select([
'customers.id',
'customers.full_name',
'expenses.name as expenses_name',
'expenses.amount as expenses_amount',
'allowances.name as allowance_name',
'allowances.quantity as allowance_quantity'
])
->leftJoin('expenses', 'customers.id', '=', 'expenses.customer_id')
->leftJoin('allowances', 'customers.id', '=', 'allowances.customer_id')
->get();⚠️ 注意:此查询返回的是「扁平化」的笛卡尔积结果集(即一个客户对应多条记录,每条含一条 expense 或 allowance)。若需转换为题目要求的嵌套结构(如 "expenses": [{}, {}]),必须后处理聚合,Eloquent 原生 JOIN 不会自动分组嵌套。
✅ 正确的完整实现应包含数据重组逻辑:
// 1. 执行 LEFT JOIN 获取所有关联行
$results = Customer::select([
'customers.id',
'customers.full_name',
'expenses.id as expense_id',
'expenses.name as expenses_name',
'expenses.amount as expenses_amount',
'allowances.id as allowance_id',
'allowances.name as allowance_name',
'allowances.quantity as allowance_quantity'
])
->leftJoin('expenses', 'customers.id', '=', 'expenses.customer_id')
->leftJoin('allowances', 'customers.id', '=', 'allowances.customer_id')
->get();
// 2. 按 customer.id 分组并构建嵌套结构
$grouped = $results->groupBy('id')->map(function ($items, $customerId) {
$customer = $items->first();
return [
'id' => (int) $customerId,
'full_name' => $customer->full_name,
'expenses' => $items
->filter(fn($i) => !is_null($i->expense_id))
->map(fn($i) => [
'name' => $i->expenses_name,
'amount' => (float) $i->expenses_amount,
])
->values()
->toArray(),
'allowances' => $items
->filter(fn($i) => !is_null($i->allowance_id))
->map(fn($i) => [
'name' => $i->allowance_name,
'quantity' => (int) $i->allowance_quantity,
])
->values()
->toArray(),
];
})->values();
return response()->json($grouped);? 关键要点总结:
- ✅ 使用 leftJoin() 而非手动修改 JoinClause::type,后者在较新 Laravel 版本中已被弃用且不可靠;
- ✅ 显式指定 ON 条件中的列别名(如 'customers.id'),避免歧义;
- ✅ 若需嵌套数组结构,JOIN 后必须手动 groupBy() + map() 聚合,无法依赖 SQL 一次性生成;
- ❌ 避免 innerJoin:会导致无 expense 或 allowance 的客户被完全排除,违背“LEFT”语义;
- ? 安全建议:对 amount/quantity 等数值字段做类型强转(如 (float)、(int)),防止 JSON 序列化时出现字符串类型偏差。
该方案兼顾查询效率与结构灵活性,适用于中等数据量场景;如数据量极大或需高频嵌套查询,建议改用 Eloquent 关系加载(with(['expenses', 'allowances']))配合 API 资源类(API Resources)进行优雅封装。










