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') 会加载这些父模型下的全部订单。
备注:内容仅供参考。
