Laravel 7关联查询:避免数据重复,实现单会员嵌套扣款数据
问题:Laravel 7中避免关联查询数据重复,实现会员-付款-嵌套扣款的展示需求
我使用PHP和Laravel 7框架,拥有4张MySQL数据表:
- members(会员表)
- deductions(扣款项目表)
- payments(付款表)
- payment_deductions(付款与扣款的关联中间表)
需求:在单行展示每个会员的信息、其对应的单笔付款及所有扣款项目(假设每个会员仅有一笔付款)。
当前问题:使用原生Join查询时,会员和付款数据会随扣款数量重复出现,查询代码如下:
$payments = Payment::leftJoin('members', 'payments.member_id', '=', 'members.id') ->leftJoin('payment_deductions', 'payments.id', '=', 'payment_deductions.payment_id') ->leftJoin('deductions', 'payment_deductions.deduction_id', '=', 'deductions.id') ->select( 'members.*', 'payment_deductions.*', ) ->orderBy("member_id", "ASC") ->get()->toArray();
希望获取更优实现方式,比如得到每个会员对应嵌套扣款数组的数据结构。
模型定义
Member模型
namespace App; use Illuminate\Database\Eloquent\Model; use Carbon\Carbon; class Member extends Model { protected $fillable = [ 'full_name', 'email', 'created_by', ]; }
Payment模型
namespace App; use Illuminate\Database\Eloquent\Model; class Payment extends Model { protected $fillable = [ 'member_id', 'total_amount', 'payable_amount', 'created_by', ]; public function deductions() { return $this->belongsToMany(Deduction::class,'payment_deductions')->withTimestamps(); } }
Deduction模型
namespace App; use Illuminate\Database\Eloquent\Model; class Deduction extends Model { protected $fillable = [ 'title', 'priority', 'created_by', ]; }
解决方案
利用Laravel Eloquent的关联预加载可以彻底解决数据重复问题,同时得到嵌套的扣款数组结构,具体步骤如下:
1. 完善Member模型的关联
因为每个会员仅有一笔付款,在Member模型中添加hasOne关联:
namespace App; use Illuminate\Database\Eloquent\Model; use Carbon\Carbon; class Member extends Model { protected $fillable = [ 'full_name', 'email', 'created_by', ]; // 会员与付款的一对一关联 public function payment() { return $this->hasOne(Payment::class); } }
2. 预加载关联数据查询
通过with()方法一次性预加载会员、对应的付款以及付款关联的所有扣款,返回的结果是嵌套结构,无重复数据:
$members = Member::with(['payment.deductions']) ->orderBy('id', 'ASC') ->get();
每个Member对象的payment属性是对应的Payment实例,Payment实例的deductions属性是该付款对应的所有Deduction集合,完全符合嵌套数组的需求。
3. 视图中渲染表格(Blade示例)
在视图里循环渲染数据,实现单行展示会员信息、付款及所有扣款:
<table border="1"> <thead> <tr> <th>会员姓名</th> <th>邮箱</th> <th>总金额</th> <th>应付金额</th> <th>扣款项目</th> </tr> </thead> <tbody> @foreach($members as $member) <tr> <td>{{ $member->full_name }}</td> <td>{{ $member->email }}</td> <td>{{ $member->payment?->total_amount ?? '无付款' }}</td> <td>{{ $member->payment?->payable_amount ?? '无付款' }}</td> <td> @if($member->payment?->deductions->isNotEmpty()) @foreach($member->payment->deductions as $deduction) {{ $deduction->title }}@if(!$loop->last), @endif @endforeach @else 无扣款 @endif </td> </tr> @endforeach </tbody> </table>
4. 替代查询方式(从Payment出发)
如果更习惯从Payment模型查询,同样可以用预加载:
$payments = Payment::with(['member', 'deductions']) ->orderBy('member_id', 'ASC') ->get();
返回的每个Payment对象包含关联的Member信息和嵌套的deductions集合,同样不会产生重复数据。
方案优势
- 自动处理关联嵌套,彻底避免Join导致的重复行问题
- 代码简洁易懂,符合Laravel ORM的设计逻辑
- 后续扩展关联或修改需求时更灵活
内容的提问来源于stack exchange,提问作者Rameez Iqbal
相关产品推荐
相关产品推荐

