修改Eloquent查询使其始终返回空集合且不执行实际SQL的最高效方法?
核心问题解决方案
针对需要让已知无结果的查询直接返回空集合、不执行实际SQL的需求,分版本有两种成熟实现方案:
1. 高版本Laravel(>=8.57.0)直接用内置方法
Laravel 8.57.0版本已原生提供Builder::none()方法,专门适配该场景。调用后查询会被标记为空结果查询,执行时会直接返回空集合,无需发起数据库请求,比手动写where('1','=','2')更规范。
示例改造:
use Illuminate\Database\Eloquent\Builder; public function applyFilter(Builder $query): Builder { $model = $query->getModel(); if (!($model instanceof MyModel)) { throw new \InvalidArgumentException('the given query does not query for <MyModel>'); } $user = Auth::user(); // 判定无权限时直接标记为空查询 if (!$user->hasAnyPermission('view_my_model')) { return $query->none(); } return $query->where(function (Builder $subquery) use ($user) { // 正常添加过滤规则 }); }
2. 低版本Laravel自定义宏实现
如果你的项目Laravel版本低于8.57.0,可以在AppServiceProvider的boot方法中注册自定义宏,实现相同效果:
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; public function boot() { // 注册空查询标记方法 Builder::macro('toNoOp', function () { $this->attributes['is_no_op'] = true; // 兜底加恒假条件,避免自定义逻辑漏判 return $this->whereRaw('1 = 0'); }); // 重写get方法,识别标记直接返回空集合 Builder::macro('get', function ($columns = ['*']) { if (isset($this->attributes['is_no_op']) && $this->attributes['is_no_op']) { return $this->model->newCollection(); } return $this->forwardCallTo($this->query, 'get', [$columns]); }); // 提供空查询判断方法,供后续子查询传播逻辑使用 Builder::macro('isNoOp', function () { return isset($this->attributes['is_no_op']) && $this->attributes['is_no_op'] || collect($this->query->wheres)->contains(fn ($where) => $where['type'] === 'raw' && $where['sql'] === '1 = 0'); }); }
之后你就可以直接调用$query->toNoOp()实现需求。
扩展问题(子查询状态向上传播)解决方案
原生Laravel没有实现子查询空状态自动向上传播的逻辑,你可以通过封装自定义的whereHas方法实现该能力:
// 在AppServiceProvider的boot方法中追加这个宏 Builder::macro('whereHasWithNoOpPropagation', function (string $relation, \Closure $callback = null) { // 先执行传入的回调,判断子查询是否被标记为空 $relationQuery = $this->getRelation($relation)->getQuery(); if ($callback) { $callback($relationQuery); } // 子查询为空的话,外层查询直接标记为空,无需执行后续逻辑 if ($relationQuery->isNoOp()) { return $this->none(); } // 否则走原生whereHas逻辑 return $this->whereHas($relation, $callback); });
使用时把原代码中的whereHas替换为封装好的方法即可:
$relatedModels = MyRelatedModel::query() ->where(...) ->whereHasWithNoOpPropagation('my_model', $q => applyFilter($q)) ->get();
如果applyFilter中判定子查询为空,外层查询会直接被标记为空,调用get时直接返回空集合,不会生成和执行包含EXISTS子句的复杂SQL。
内容的提问来源于stack exchange,提问作者user2690527
相关产品推荐
相关产品推荐

