You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 10:05:23