PAYATHON 2026

Laravel 中如何移除 related collection 为空的父模型

支付阿杰

结论

可以。在父模型查询中使用 whereHas(),只返回至少有一条 orders 记录符合条件的 DeliveryPartner。

with() 只用于预加载关联数据,不会根据关联查询的结果过滤父模型。即使 orders 为空,父模型仍会返回。

推荐写法

把相同的关联条件同时传给 whereHas() 和 with():

use Illuminate\Database\Eloquent\Builder;

$ordersFilter = function (Builder $query) {
    $query
        ->where(function (Builder $query) {
            $query
                ->where('delivery_partner_invoice_id', '')
                ->orWhereNull('delivery_partner_invoice_id');
        })
        ->whereIn('status', [
            'payment-accepted',
            'completed',
            'full-refund',
            'partial-refund',
        ]);
};

$data = DeliveryPartner::query()
    ->when($filter, function (Builder $query) use ($request) {
        // 父模型的其他过滤条件
    })
    ->whereHas('orders', $ordersFilter)
    ->with([
        'orders' => $ordersFilter,
    ])
    ->get();

whereHas() 会排除没有符合条件订单的 DeliveryPartner,with() 则只预加载符合相同条件的订单。整个过滤过程都在数据库查询阶段完成,不需要再遍历 Collection。

注意条件分组

发票条件需要放在嵌套的 where() 中:

$query->where(function (Builder $query) {
    $query
        ->where('delivery_partner_invoice_id', '')
        ->orWhereNull('delivery_partner_invoice_id');
});

对应的逻辑是:

(delivery_partner_invoice_id = '' OR delivery_partner_invoice_id IS NULL)
AND status IN (...)

如果不分组,数据库会按照 AND 优先于 OR 的规则解释这些条件,得到的逻辑可能类似:

delivery_partner_invoice_id = ''
OR (
    delivery_partner_invoice_id IS NULL
    AND status IN (...)
)

这样,发票字段为空字符串的订单可能不会受到 status 条件的限制。

使用 withWhereHas()

如果当前 Laravel 版本支持 withWhereHas(),可以用一个方法同时过滤父模型并预加载关联:

$data = DeliveryPartner::query()
    ->when($filter, function (Builder $query) use ($request) {
        // 父模型的其他过滤条件
    })
    ->withWhereHas('orders', function (Builder $query) {
        $query
            ->where(function (Builder $query) {
                $query
                    ->where('delivery_partner_invoice_id', '')
                    ->orWhereNull('delivery_partner_invoice_id');
            })
            ->whereIn('status', [
                'payment-accepted',
                'completed',
                'full-refund',
                'partial-refund',
            ]);
    })
    ->get();

如果项目使用的版本没有这个方法,继续使用前面的 whereHas() 加 with() 即可。

如果需要加载全部订单

如果指定条件只用于判断是否保留父模型,而保留后需要加载该合作方的全部订单,就不要给 with() 添加过滤条件:

$data = DeliveryPartner::query()
    ->whereHas('orders', function (Builder $query) {
        $query
            ->where(function (Builder $query) {
                $query
                    ->where('delivery_partner_invoice_id', '')
                    ->orWhereNull('delivery_partner_invoice_id');
            })
            ->whereIn('status', [
                'payment-accepted',
                'completed',
                'full-refund',
                'partial-refund',
            ]);
    })
    ->with('orders')
    ->get();

这里由 whereHas() 决定返回哪些 DeliveryPartner,而 with('orders') 会加载这些父模型下的全部订单。

备注:内容仅供参考。