我正在尝试使用 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.7 或 5.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/