php - Laravel Eloquent 内部连接自引用表

标签 php laravel eloquent laravel-5.7

我正在尝试使用 Eloquent 模型将用户表内部连接到其自身。我已经到处寻找,但似乎无法找到解决方案而不创建两个查询,这就是我目前正在做的事情。

用户表本身通过数据透视 friend 具有多对多关系

我尝试了内部连接但失败了 Users::class对自己。我能得到的最好的内部连接是通过运行两个查询并查看是否存在重叠。因此,一个人已经联系了另一个人,反之亦然。

friends   | users
----------|------
send_id   | id
receive_id| name
is_blocked|

样本数据和预期结果

users.id | name
---------|------
1        | foo
2        | bar
3        | baz

friends
send_id | receive_id | is_blocked
--------|------------|-----------
1       |    2       |  0
2       |    1       |  0
1       |    3       |  0
3       |    1       |  1
2       |    3       |  0

用户应该有一种称为 friend 的良好关系。它应该是您所期望的 requestedFriends 的结果或receivedFriends刚刚加入。

foo->friends
returns `baz`
bar->friends
returns `foo`
baz->friends
returns empty collection

当前使用

// User.php
public function requestedFriends()
{
    $left = $this->belongsToMany(User::class, 'friends','send_id','receive_id')
        ->withPivot('is_blocked')
        ->wherePivot('is_blocked','=', 0)
        ->withTimestamps();
    return $left;
}

public function receivedFriends()
{
    $right = $this->belongsToMany(User::class, 'friends','receive_id','send_id')
        ->withPivot('is_blocked')
        ->wherePivot('is_blocked','=', 0)
        ->withTimestamps();

    return $right;
}

public function friends()
{
    $reqFriends = $this->requestedFriends()->get();
    $recFriends = $this->receivedFriends()->get();
    $req = explode(",",$recFriends->implode('id', ', '));
    $intersect = $reqFriends->whereIn('id', $req);
    return $intersect;
}

迄今为止的研究

Laravel Many to many self referencing table only works one way -> 老问题,但仍然相关

https://github.com/laravel/framework/issues/441#issuecomment-14213883 -> 是的,它有效……但只有一种方式。

https://laravel.com/docs/5.8/collections#method-wherein 目前,这是我发现用 eloquent 做到这一点的唯一方法。

https://laravel.com/docs/5.7/queries#joins -> 理想情况我会找到一个使用内部联接的解决方案,但无论我以哪种方式放置 id,我都无法找到可行的解决方案。

解决方案将

解决方案将在 laravel 5.75.8 中使用 eloquent 内部连接自引用表,其中仅在 send_id 时才存在关系。 & receive_id存在于好友表中的多行中。

以某种方式让社区知道这是不可能完成的。

提前致谢!

最佳答案

我还没有详细检查这个解决方案,但我已经编写了一个“ManyToMany”类,扩展了 laravel 附带的“BelongsToMany”类,这似乎可以工作。 该类基本上只是重写“get”方法,复制原始查询,“反转”它,然后对原始查询执行“并集”。

<?php

namespace App\Database\Eloquent\Relations;

use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class ManyToMany extends BelongsToMany
{

    /**
     * Execute the query as a "select" statement.
     *
     * @param  array  $columns
     * @return \Illuminate\Database\Eloquent\Collection
     */
    public function get($columns = ['*'])
    {
        // duplicated from "BelongsToMany"
        $builder = $this->query->applyScopes();

        $columns = $builder->getQuery()->columns ? [] : $columns;

        // Adjustments for "Many to Many on self": do not get the resulting models here directly, but rather
        // just set the columns to select and do some adjustments to also select the "inverse" records
        $builder->addSelect(
            $this->shouldSelect($columns)
        );

        // backup order directives
        $orders = $builder->getQuery()->orders;
        $builder->getQuery()->orders = [];

        // clone the original query
        $query2 = clone($this->query);

        // determine the columns to select - same as in original query, but with inverted pivot key names
        $query2->select(
            $this->shouldSelectInverse( $columns )
        );
        // remove the inner join and build a new one, this time using the "foreign" pivot key
        $query2->getQuery()->joins = array();

        $baseTable = $this->related->getTable();
        $key = $baseTable.'.'.$this->relatedKey;
        $query2->join($this->table, $key, '=', $this->getQualifiedForeignPivotKeyName());

        // go through all where conditions and "invert" the one relevant for the inner join
        foreach( $query2->getQuery()->wheres as &$where ) {
            if(
                $where['type'] == 'Basic'
                && $where['column'] == $this->getQualifiedForeignPivotKeyName()
                && $where['operator'] == '='
                && $where['value'] == $this->parent->{$this->parentKey}
            ) {
                $where['column'] = $this->getQualifiedRelatedPivotKeyName();
                break;
            }
        }

        // add the duplicated and modified and adjusted query to the original query with union
        $builder->getQuery()->union($query2);

        // reapply orderings so that they are used for the "union" rather than just the individual queries
        foreach($orders as $ord)
            $builder->getQuery()->orderBy($ord['column'], $ord['direction']);

        // back to "normal" - get the models
        $models = $builder->getModels();
        $this->hydratePivotRelation($models);

        // If we actually found models we will also eager load any relationships that
        // have been specified as needing to be eager loaded. This will solve the
        // n + 1 query problem for the developer and also increase performance.
        if (count($models) > 0) {
            $models = $builder->eagerLoadRelations($models);
        }

        return $this->related->newCollection($models);
    }


    /**
     * Get the select columns for the relation query.
     *
     * @param  array  $columns
     * @return array
     */
    protected function shouldSelectInverse(array $columns = ['*'])
    {
        if ($columns == ['*']) {
            $columns = [$this->related->getTable().'.*'];
        }

        return array_merge($columns, $this->aliasedPivotColumnsInverse());
    }

    /**
     * Get the pivot columns for the relation.
     *
     * "pivot_" is prefixed ot each column for easy removal later.
     *
     * @return array
     */
    protected function aliasedPivotColumnsInverse()
    {
        $collection = collect( $this->pivotColumns )->map(function ($column) {
            return $this->table.'.'.$column.' as pivot_'.$column;
        });
        $collection->prepend(
            $this->table.'.'.$this->relatedPivotKey.' as pivot_'.$this->foreignPivotKey
        );
        $collection->prepend(
            $this->table.'.'.$this->foreignPivotKey.' as pivot_'.$this->relatedPivotKey
        );

        return $collection->unique()->all();
    }

}

关于php - Laravel Eloquent 内部连接自引用表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55113114/

相关文章:

javascript - Laravel 和 Ajax 加载数据而不刷新页面

mysql - Laravel Eloquent 查询加入类似

javascript - 选择下拉菜单项后获取输入值

php - PDO php 两个语句在一个函数中,如果都为真,则提交,否则回滚

php - 表单自动提交ajax不工作

php - php将数据插入mysql数据库,显示错误

php - Laravel 表单验证器错误

php - View 中表的输出值

php - laravel 外键迁移错误

php - 3个表之间的Laravel模型关系