为何whereHas条件不满足时PortingItem模型仍返回查询结果?
问题原因分析与解决方案
我来帮你梳理下这个问题的根源——其实是查询逻辑的优先级和分组在搞鬼!
为什么会返回porting为空的记录?
你的原查询结构是:
$result = PortingItem::whereHas(...) ->where(...) ->orWhere(...) ->get();
翻译成SQL的话,等价于:
SELECT * FROM porting_items WHERE (exists (SELECT * FROM portings ...)) AND (phone_number_start BETWEEN ... OR phone_number_end BETWEEN ...) OR (phone_number_start <= ... AND phone_number_end >= ...)
注意这里的OR是和前面的AND同级的!也就是说,只要最后那个orWhere的条件满足,不管前面的whereHas(关联是否存在、状态是否符合)是否成立,这条PortingItem都会被查询出来。这就导致了porting为空的记录也能被返回——因为它们可能满足最后那个号码覆盖的条件。
修正后的查询方案
你的需求应该是要找同时满足两个条件的记录:
- 关联的
Porting存在且处于指定状态 - 号码范围符合要求(要么起始/结束在给定区间,要么覆盖整个区间)
所以需要把所有号码相关的条件都放到同一个逻辑组里,再和whereHas做AND运算。修正后的代码如下:
$result = PortingItem::whereHas('porting', function ($query) { $query->whereIn('status', [ Porting::STATUS_REQUESTED, Porting::STATUS_ACCEPTED, Porting::STATUS_DELAYED, ]); })->where(function ($query) use ($numbers) { // 把所有号码相关的条件都放到这个闭包里,确保是OR关系 $query->whereBetween('phone_number_start', [$numbers[0], $numbers[1]]) ->orWhereBetween('phone_number_end', [$numbers[0], $numbers[1]]) ->orWhere([ ['phone_number_start', '<=', $numbers[0]], ['phone_number_end', '>=', $numbers[1]], ]); })->get();
验证方法
如果你想确认生成的SQL是否符合预期,可以在查询末尾加上->toSql()来查看:
dd(PortingItem::whereHas(...)->where(...)->toSql());
修改后的SQL会变成:
SELECT * FROM porting_items WHERE (exists (SELECT * FROM portings ...)) AND ( phone_number_start BETWEEN ... OR phone_number_end BETWEEN ... OR (phone_number_start <= ... AND phone_number_end >= ...) )
这样就确保了只有当关联的Porting存在且状态符合,同时号码范围满足任一条件时,才会返回结果,彻底排除了porting为空的记录。
内容的提问来源于stack exchange,提问作者Stackoverflower
相关产品推荐
相关产品推荐

