如何在 Eloquent 子选择查询中使用自定义查询范围?

Posted

技术标签:

【中文标题】如何在 Eloquent 子选择查询中使用自定义查询范围?【英文标题】:How to use custom query scopes in an Eloquent subselect query? 【发布时间】:2016-02-08 01:46:15 【问题描述】:

这通过手动获取数据作为数组然后重新传递它来工作:

public function scopeWhereWhitelisted($query, $value=true, Tenant $tenant)

    return $query->where(function($query)use($value,$tenant)
    
        $user_id_list = $tenant->getWhiteListedUsersGroup()
                                                ->users()
                                                ->select('users.id')
                                                ->lists('id')
                                                ->all()
        ;

        $query-> $value ? 'whereIn' : 'whereNotIn' ('users.id',$user_id_list);
    );

但我希望它能够工作(注释 // 表示唯一的区别):

public function scopeWhereWhitelisted($query, $value=true, Tenant $tenant)

    return $query->where(function($query)use($value,$tenant)
    
        $user_id_list = $tenant->getWhiteListedUsersGroup()
                                                ->users()
                                                ->select('users.id')
                                                //->lists('id')
                                                //->all()
        ;

        $user_id_list = $tenant->getWhiteListedUsersGroup()->users()->select('users.id');//->lists('id')->all();
        $query-> $value ? 'whereIn' : 'whereNotIn' ('users.id',$user_id_list);
    );

我希望能够创建一个“真正的”子选择,而不必为每个范围创建自定义查询范围和关系查询的重复副本。 $tenant->getWhiteListedUsersGroup()->users() 是多对多的关系

以下是获得真正子选择的示例:

public function scopeWhereWhitelisted($query, $value=true, Tenant $tenant)

    return $query->where(function($query)use($value,$tenant)
    
        $query-> $value ? 'whereIn' : 'whereNotIn' ('users.id',function($query)
        
             $query->from('groups_memberships')
                  //     recreating an existing relationship function
                  ->join('groups','groups.id','group_memberships.group_id')
                  ->select('users.id')
                  //     recreating an already existing query scope
                  ->whereNull('deleted_at')
             ;
        );
    );

这个问题很可能同时适用于 Laravel 4.0+ 和 5.0+ How to do this in Laravel, subquery where in 没有回答这个问题 当我需要第二个重要的子选择时,重构代码以使查询从预期的子查询开始将不起作用。 ->getQuery() 的包含/排除没有影响。 我必须在虚假子选择或非 DRY 自定义查询范围之间进行选择。 似乎主要问题是子选择引擎迫使我使用无法从现有关系初始化的预先存在的 $query 对象。 重新创建软删除 (whereNull('deleted_at')) 是一个简单的示例,但我可能必须重新创建一个可能已经相对复杂的查询范围。

【问题讨论】:

【参考方案1】:

这是你追求的目标吗?

$value; //true or false

$tenant->whereHas('user', function($query) use ($value)
    $query->whereHas('groupMembership', function($query) use ($value)
        $query->whereHas('group', function($query) use ($value)
            if($value) $query->onlyTrashed(); )
        );
    )
)

这假设组关系包括对关系的 withTrashed() 调用

【讨论】:

whereHas 完全不在讨论范围内。每个whereHas 为每个表计算一个临时列。这意味着不能使用索引,并且将进行全表扫描。你有三个嵌套的whereHas,这意味着对于用户表中的每一行,mysql 必须计算“有 groupMembership”tmp 列。为了确定这一点,groupMember 的每次全表扫描都将执行 group 的全表扫描。这本质上是 3 层呈指数增长的全表扫描。 github.com/laravel/framework/issues/3543

以上是关于如何在 Eloquent 子选择查询中使用自定义查询范围?的主要内容,如果未能解决你的问题,请参考以下文章

雄辩地执行子查询?

如何使用 Eloquent ORM 进行多重连接查询

Sequelize JS 自定义查询与子选择

如何在 Eloquent/Laravel 上查询子查询

相关模型 eloquent laravel 中的自定义查询

Laravel:如何设置带有条件的自定义列?