Laravel - 模型自引用belongsToMany与其他关系和具有多个限制的查询

Posted

技术标签:

【中文标题】Laravel - 模型自引用belongsToMany与其他关系和具有多个限制的查询【英文标题】:Laravel - model self reference belongsToMany with other relation and query with multiple restrictions 【发布时间】:2015-03-29 16:53:56 【问题描述】:

我有 3 张桌子:

建筑物:

+-----+-------------+-----------+--------------------------+------------+---------------+----------------------+---------------------+
| id  | order_flag  |   type    |           key            | unit_cost  | unit_produce  |     created_at       |     updated_at      |
+-----+-------------+-----------+--------------------------+------------+---------------+----------------------+---------------------+
|  1  |         10  | resource  | building1                |        10  |            0  | 2015-01-30 08:54:44  | 2015-01-30 08:54:44 |
|  2  |         20  | resource  | building2                |         5  |            0  | 2015-01-30 08:54:44  | 2015-01-30 08:54:44 |
|  3  |         30  | resource  | building3                |        10  |            0  | 2015-01-30 08:54:44  | 2015-01-30 08:54:44 |
|  4  |         10  | basic     | building4                |         0  |           25  | 2015-01-30 08:54:44  | 2015-01-30 08:54:44 |
+-----+-------------+-----------+--------------------------+------------+---------------+----------------------+---------------------+

建筑用户:

+-----+--------------+----------+--------+----------------------+---------------------+
| id  | building_id  | user_id  | level  |     created_at       |     updated_at      |
+-----+--------------+----------+--------+----------------------+---------------------+
|  1  |           2  |      12  |     3  | 2015-01-30 08:52:57  | 2015-01-30 08:55:37 |
|  2  |           4  |      12  |     1  | 2015-01-30 08:53:53  | 2015-01-30 08:53:53 |
|  3  |           1  |      12  |     2  | 2015-01-30 08:54:08  | 2015-01-30 08:55:10 |
+-----+--------------+----------+--------+----------------------+---------------------+

建筑要求:

+-----+--------------+-------------+--------+----------------------+---------------------+
| id  | building_id  | require_id  | level  |     created_at       |     updated_at      |
+-----+--------------+-------------+--------+----------------------+---------------------+
|  1  |           1  |          4  |     1  | 2015-01-30 08:54:44  | 2015-01-30 08:54:44 |
|  2  |           3  |          1  |     5  | 2015-01-30 08:54:44  | 2015-01-30 08:54:44 |
+-----+--------------+-------------+--------+----------------------+---------------------+

我在建筑模型中写下这个关系:

    # Building Require another Building(s)
    public function requires() 
        return $this->belongsToMany('Building', 'building_require', 'building_id', 'require_id')->withPivot(array(
            'level',
            'updated_at',
            'created_at'
        ));
    

    public function users() 
        return $this->belongsToMany('User')->withPivot(array('level'));
    

现在我想从桌子建筑物中选择所有建筑物...... 以下限制:

    如果 building_require 表中没有来自该建筑物的条目,则该建筑物可以在结果中。

    如果该建筑物在 building_require 表中有条目,则必须检查 building_user 表中是否有条目,其中 building_id 与 building_required 表中的 required_id 相同。

(例如 building_id 1 需要 building_id 4,而 building_id 3 需要 building_id 1 的条目。)

除此之外(这是困难的部分),必须检查 building_user 表中的条目是否具有 level 值 >= 作为 building_require 表中定义的级别。

总结一下:

在 这种情况 building_id 1 需要在 building_user 中的条目 building_id 为 4 并且级别至少为 1。 building_id 3 需要一个条目 building_user 其中 building_id 为 1 且级别至少为 5。因此 - building_id 1 和 3 具有所需的建筑物。建筑与 id 2 和 4 在结果中没有限制。 在这种情况下,只有 building_id 3 是不允许的。因为 building_user 表中 building_id 1 的级别只有 2 而不是至少 5。

对我来说这很困难,我认为这不是最好的解决方案,遗憾的是没有检查级别值:

# all buildings there has a required building:
$allRequiredBuildingIds = DB::table('building_require')->lists('building_id');

# if user has buildings, this buildings must remove from $allRequiredBuildingIds
if(Sentry::getUser()->buildings()->count() > 0) 
    # list all building_id 's of the user_building table
    $userBuildingIds = Sentry::getUser()->buildings()->lists('building_id');
    # list all building_id's of the buildings there are required and already stored in the building_user table
    $userBuildingRequiredIds = DB::table('building_require')->whereIn('require_id', $userBuildingIds)->lists('building_id');
    # there are all the building_id's which are not allowed to display
    $allRequiredBuildingIds = array_values(array_diff($allRequiredBuildingIds,$userBuildingRequiredIds));


# if there are buildings there are not allowed to display?
if(count($allRequiredBuildingIds) > 0) 
    $buildings = Building::whereType($type)->whereNotIn('id', $allRequiredBuildingIds)->paginate(10);
else
    $buildings = Building::whereType($type)->paginate(10);

有人可以帮我优化它并集成级别检查吗? 我的头在怦怦直跳。

【问题讨论】:

【参考方案1】:

我有一个理论认为这可能有效,但我尚未对其进行测试。我认为关键是只在 whereHas 闭包中使用 Join ,从而让 Eloquent 构建器保持活跃。

Building::doesntHave('requires')
    ->orWhereHas('users', function($q) 
        $q->join('building_require', 'building_user.building_id', '=', 'building_require.required_id')
          ->where('building_user.level', '>=', 'building_require.level');
    )->get();

它会生成这样的查询:

select * from `buildings` where (
  select count(*) from `building_require` as `x` where `x`.`building_id` = `buildings`.`id`
) < 1 or (
  select count(*) from `users` 
  inner join `building_user` on `users`.`id` = `building_user`.`user_id` 
  inner join `building_require` on `building_user`.`building_id` = `building_require`.`required_id` 
  where `building_user`.`building_id` = `buildings`.`id` 
  and `building_user`.`level` >= building_require.level
) >= 1

不幸的是,这并不能解决问题。它将返回 building_require.required_id 建筑物而不是 building_require.building_id 建筑物。我设法在原始 SQL 中找出正确的查询,但我不确定如何在 Eloquent 中修复它:

SQLFiddle

select * from `buildings` where (
  select count(*) from `building_require` as `x` where `x`.`building_id` = `buildings`.`id`
) < 1 or (
  select count(*) from `users` 
  inner join `building_user` on `users`.`id` = `building_user`.`user_id` 
  inner join `building_require` on `building_user`.`building_id` = `building_require`.`required_id` 
  where building_require.building_id = buildings.id
  and `building_user`.`level` >= building_require.level
) >= 1;

【讨论】:

感谢您的回答。但是如果我使用您的代码,我得到一个错误: SQLSTATE[HY000]: General error: 2031 (SQL: select * from buildings where (select count() from building_require as self_8ebed67ebfeab2e90676c66005049e5a where self_8ebed67ebfeab2e90676c66005049e5a .building_id = buildings.id) ) from users inner join building_user on users.id = building_user.user_idbuilding_user.building_id 上内连接building_require = ``.require_idbuilding_user.level >= ? 其中building_user.building_id = buildings.@987654344344 1) @goldlife 我想我修好了,你能再试一次吗? no sry... 也不行。在您的 where 语句之后,我还使用语法错误更新了您的查询: ->where('building_user.level', '>=', 'building_require.level'); ); - 你错过了一个“)”。它是 required_id 而不是 require_id。但没有任何效果。它给了我mysql的一般错误。但感谢您的回答... ;) 运气不好。我可以将您的代码修复为一个有效的查询:$buildings = Building::doesntHave('requires') -&gt;orWhereHas('users', function($q) $q-&gt;join('building_require', 'building_user.building_id', '=', 'building_require.require_id') -&gt;where('building_user.level', '&gt;=', 'building_require.level'); )-&gt;get(); 但是 - 我把所有的建筑物都拿回来了。也没有检查 building_user 中的 user_id。如果我在连接的位置添加 user_id 检查,无论 building_user 表中存在的级别是什么,我都只会返回一个构建。 @goldlife hm...我想我通过对 laravel 生成的 SQL 语句(“where”语句)进行微小更改,为它找到了一个有效的 SQL 语句。不幸的是,不确定如何让 Laravel 正确生成该部分语句。我已将其附加到答案中。如果我找出正确的方法,我会为它做一个新的答案。

以上是关于Laravel - 模型自引用belongsToMany与其他关系和具有多个限制的查询的主要内容,如果未能解决你的问题,请参考以下文章

Laravel - 自引用关系不起作用

Laravel 迁移自引用外键问题

如何在 Laravel 迁移中创建自引用关系(外键)?

我应该如何为需要引用数据透视表的表配置 Laravel / OctoberCMS 模型?

Laravel 8 - 当工厂中的自引用关系时,该过程已用信号“11”发出信号

Laravel:保存/附加/同步自定义枢轴模型(belongsToMany)