Laravel 多对多(在同一用户上 table/Model):查询范围以包括指定用户的相关内容
Laravel Many-to-Many (on the same users table/Model): Query scopes to include related for the specified user
用户可以互相拉黑。一个用户可以屏蔽很多(其他)用户,一个用户可以被很多(其他)用户屏蔽。
在 User
模型中,我有这些 多对多 关系:
/**
* Get the users that are blocked by $this user.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function blockedUsers()
{
return $this->belongsToMany(User::class, 'ignore_lists', 'user_id', 'blocked_user_id');
}
/**
* Get the users that blocked $this user.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function blockedByUsers()
{
return $this->belongsToMany(User::class, 'ignore_lists', 'blocked_user_id', 'user_id');
}
(ignore_lists
是枢轴 table,它有 id
、user_id
、'blocked_user_id'
列)
我想创建以下查询范围:
1) 要包含被指定用户 ($id
) 阻止的 的用户:
/**
* Scope a query to only include users that are blocked by the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreBlockedBy($query, $id)
{
// How to do this? :)
}
用法示例: User::areBlockedBy(auth()->id())->where('verified', 1)->get();
2) 要包含被指定用户 ($id
) 未 阻止的用户:
/**
* Scope a query to only include users that are not blocked by the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreNotBlockedBy($query, $id)
{
// How to do this? :)
}
用法示例: User::areNotBlockedBy(auth()->id())->where('verified', 1)->get();
3) 要包含 阻止了 指定用户 ($id
) 的用户:
/**
* Scope a query to only include users that blocked the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoBlocked($query, $id)
{
// How to do this? :)
}
用法示例: User::whoBlocked(auth()->id())->where('verified', 1)->get();
4) 要包含 未阻止 指定用户 ($id
) 的用户:
/**
* Scope a query to only include users that did not block the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoDidNotBlock($query, $id)
{
// How to do this? :)
}
用法示例: User::whoDidNotBlock(auth()->id())->where('verified', 1)->get();
你会怎么做?
我在 Laravel docs 中没有找到关于此的任何内容(也许我错过了)。
(我正在使用 Laravel 6.x)
我不确定,但我认为这可以通过两种方式完成:使用 Left Join 或使用 raw queries in whereIn...我可能错了,但我认为就性能而言 "left join" 解决方案会更好,对吗? (对此不确定,也许我完全错了)。
使用join(inner join)
性能优于whereIn
子查询。
在 MySQL 中,IN 子句中的子查询对外部查询中的每一行重新执行,从而创建 O(n^2)
。
我认为使用 whereHas
和 whereDoesntHave
进行查询会更具可读性。
1)关系方式blockedUsers()
已经收录了被指定user ($id)
屏蔽的用户,可以直接使用此方式:
User::where('id', $id)->first()->blockedUsers();
考虑先应用where('verified', 1)
,所以可以使用User::where('verified', 1)->areBlockedBy(auth()->id())
这样的查询,范围可以是这样:
public function scopeAreBlockedBy($query, $id)
{
return $query->whereHas('blockedByUsers', function($users) use($id) {
$users->where('ignore_lists.user_id', $id);
});
}
// better performance: however, when you apply another where condition, you need to specify the table name ->where('users.verified', 1)
public function scopeAreBlockedBy($query, $id)
{
return $query->join('ignore_lists', function($q) use ($id) {
$q->on('ignore_lists.blocked_user_id', '=', 'users.id')
->where('ignore_lists.user_id', $id);
})->select('users.*')->distinct();
}
我们对第二个查询使用 join
,这将提高性能,因为它不需要使用 where exists
。
用户中超过 300,000 条记录的示例 table:
解释扫描 301119+1+1
行并获取 575ms
的第一个查询 whereHas
:
解释第二个查询 join
,它扫描 3+1
行并获取 10.1ms
:
2) 要包含 未被 指定 user ($id)
阻止的用户,您可以使用 whereDoesntHave
闭包,如下所示:
public function scopeNotBlockedUsers($query, $id)
{
return $query->whereDoesntHave('blockedByUsers', function($users) use ($id){
$users->where('ignore_lists.user_id', $id);
});
}
我更喜欢在这里使用 whereDoesntHave
而不是 leftJoin
。因为当你像下面这样使用 leftjoin
时:
User::leftjoin('ignore_lists', function($q) use ($id) {
$q->on('ignore_lists.blocked_user_id', '=', 'users.id')
->where('ignore_lists.user_id', $id);
})->whereNull('ignore_lists.id')->select('users.*')->distinct()->get();
Mysql 需要创建一个临时 table 来存储 所有用户的记录 并合并一些 ignore_lists
。然后扫描这些记录并找出没有ignore_lists
的记录。 whereDosentHave
也会扫描 所有用户 。对于我的 mysql 服务器,where not exists
比 left join
快一点。它的执行计划看起来不错。这两个查询的性能差别不大。
因为 whereDoesntHave
更具可读性。我会选择whereDoesntHave
。
3) 要包括 阻止了指定的 user ($id)
的用户,要使用 whereHas
blockedUsers 像这样:
public function scopeWhoBlocked($query, $id)
{
return $query->whereHas('blockedUsers', function($q) use ($id) {
$q->where('ignore_lists.blocked_user_id', $id);
});
}
// better performance: however, when you apply another where condition, you need to specify the table name ->where('users.verified', 1)
public function scopeWhoBlocked($query, $id)
{
return $query->join('ignore_lists', function($q) use ($id) {
$q->on('ignore_lists.user_id', '=', 'users.id')
->where('ignore_lists.blocked_user_id', $id);
})->select('users.*')->distinct();
}
4) 要包含 未阻止 指定 user ($id)
的用户,请对 blockedByUsers 使用 whereDoesntHave
:
public function scopeWhoDidNotBlock($query, $id)
{
return $query->whereDoesntHave('blockedUsers', function($q) use ($id) {
$q->where('ignore_lists.blocked_user_id', $id);
});
}
PS: 记得在 foreign_key
上为 ignore_lists
添加索引 table.
您可以使用 Querying Relationship Existence whereHas
and Querying Relationship Absence whereDoesntHave
查询构建器函数来构建结果查询。
我已经包含了每个查询生成的 SQL 代码和查询时间(以毫秒为单位),这些查询时间是在 table 有 1000 个用户的双 Xeon 专用服务器上测试的。
我们不希望在使用areNotBlockedBy
和whoDidNotBlock
查询时在结果中获取当前用户,因此这些函数将排除使用$id
.[=32=的用户]
要包括 被指定用户 ($id
) 阻止的用户:
/**
* Scope a query to only include users that are blocked by the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreBlockedBy($query, $id)
{
return User::whereHas('blockedByUsers', function($q) use($id) {
$q->where('user_id', $id);
});
}
正在执行:
User::areBlockedBy(auth()->id())->where('verified', 1)->get();
将生成以下内容 SQL:
-- Showing rows 0 - 3 (4 total, Query took 0.0006 seconds.)
select * from `users` where exists (select * from `users` as `laravel_reserved_9` inner join `ignore_lists` on `laravel_reserved_9`.`id` = `ignore_lists`.`user_id` where `users`.`id` = `ignore_lists`.`blocked_user_id` and `user_id` = ?) and `verified` = ?
要包括 未 被指定用户 ($id
) 阻止的用户:
/**
* Scope a query to only include users that are not blocked by the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreNotBlockedBy($query, $id)
{
// It will exclude the user with $id
return User::where('id', '!=', $id)
->whereDoesntHave('blockedByUsers', function($q) use($id) {
$q->where('user_id', $id);
});
}
正在执行:
User::areNotBlockedBy(auth()->id())->where('verified', 1)->get();
将生成以下内容 SQL:
-- Showing rows 0 - 24 (990 total, Query took 0.0005 seconds.)
select * from `users` where `id` != ? and not exists (select * from `users` as `laravel_reserved_0` inner join `ignore_lists` on `laravel_reserved_0`.`id` = `ignore_lists`.`user_id` where `users`.`id` = `ignore_lists`.`blocked_user_id` and `user_id` = ?) and `verified` = ?
要包括 阻止 指定用户 ($id
) 的用户:
/**
* Scope a query to only include users that blocked the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoBlocked($query, $id)
{
return User::whereHas('blockedUsers', function($q) use($id) {
$q->where('blocked_user_id', $id);
});
}
正在执行:
User::whoBlocked(auth()->id())->where('verified', 1)->get();
将生成以下内容 SQL:
-- Showing rows 0 - 1 (2 total, Query took 0.0004 seconds.)
select * from `users` where exists (select * from `users` as `laravel_reserved_12` inner join `ignore_lists` on `laravel_reserved_12`.`id` = `ignore_lists`.`blocked_user_id` where `users`.`id` = `ignore_lists`.`user_id` and `blocked_user_id` = ?) and `verified` = ?
要包含 未阻止 指定用户 ($id
) 的用户:
/**
* Scope a query to only include users that did not block the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoDidNotBlock($query, $id)
{
// It will exclude the user with $id
return User::where('id', '!=', $id)
->whereDoesntHave('blockedUsers', function($q) use($id) {
$q->where('blocked_user_id', $id);
});
}
正在执行:
User::whoDidNotBlock(auth()->id())->where('verified', 1)->get();
将生成以下内容 SQL:
-- Showing rows 0 - 24 (992 total, Query took 0.0004 seconds.)
select * from `users` where `id` != ? and not exists (select * from `users` as `laravel_reserved_1` inner join `ignore_lists` on `laravel_reserved_1`.`id` = `ignore_lists`.`blocked_user_id` where `users`.`id` = `ignore_lists`.`user_id` and `blocked_user_id` = ?) and `verified` = ?
用户可以互相拉黑。一个用户可以屏蔽很多(其他)用户,一个用户可以被很多(其他)用户屏蔽。
在 User
模型中,我有这些 多对多 关系:
/**
* Get the users that are blocked by $this user.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function blockedUsers()
{
return $this->belongsToMany(User::class, 'ignore_lists', 'user_id', 'blocked_user_id');
}
/**
* Get the users that blocked $this user.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function blockedByUsers()
{
return $this->belongsToMany(User::class, 'ignore_lists', 'blocked_user_id', 'user_id');
}
(ignore_lists
是枢轴 table,它有 id
、user_id
、'blocked_user_id'
列)
我想创建以下查询范围:
1) 要包含被指定用户 ($id
) 阻止的 的用户:
/**
* Scope a query to only include users that are blocked by the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreBlockedBy($query, $id)
{
// How to do this? :)
}
用法示例: User::areBlockedBy(auth()->id())->where('verified', 1)->get();
2) 要包含被指定用户 ($id
) 未 阻止的用户:
/**
* Scope a query to only include users that are not blocked by the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeAreNotBlockedBy($query, $id)
{
// How to do this? :)
}
用法示例: User::areNotBlockedBy(auth()->id())->where('verified', 1)->get();
3) 要包含 阻止了 指定用户 ($id
) 的用户:
/**
* Scope a query to only include users that blocked the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoBlocked($query, $id)
{
// How to do this? :)
}
用法示例: User::whoBlocked(auth()->id())->where('verified', 1)->get();
4) 要包含 未阻止 指定用户 ($id
) 的用户:
/**
* Scope a query to only include users that did not block the specified user.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param $id
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWhoDidNotBlock($query, $id)
{
// How to do this? :)
}
用法示例: User::whoDidNotBlock(auth()->id())->where('verified', 1)->get();
你会怎么做? 我在 Laravel docs 中没有找到关于此的任何内容(也许我错过了)。 (我正在使用 Laravel 6.x)
我不确定,但我认为这可以通过两种方式完成:使用 Left Join 或使用 raw queries in whereIn...我可能错了,但我认为就性能而言 "left join" 解决方案会更好,对吗? (对此不确定,也许我完全错了)。
使用join(inner join)
性能优于whereIn
子查询。
在 MySQL 中,IN 子句中的子查询对外部查询中的每一行重新执行,从而创建 O(n^2)
。
我认为使用 whereHas
和 whereDoesntHave
进行查询会更具可读性。
1)关系方式blockedUsers()
已经收录了被指定user ($id)
屏蔽的用户,可以直接使用此方式:
User::where('id', $id)->first()->blockedUsers();
考虑先应用where('verified', 1)
,所以可以使用User::where('verified', 1)->areBlockedBy(auth()->id())
这样的查询,范围可以是这样:
public function scopeAreBlockedBy($query, $id)
{
return $query->whereHas('blockedByUsers', function($users) use($id) {
$users->where('ignore_lists.user_id', $id);
});
}
// better performance: however, when you apply another where condition, you need to specify the table name ->where('users.verified', 1)
public function scopeAreBlockedBy($query, $id)
{
return $query->join('ignore_lists', function($q) use ($id) {
$q->on('ignore_lists.blocked_user_id', '=', 'users.id')
->where('ignore_lists.user_id', $id);
})->select('users.*')->distinct();
}
我们对第二个查询使用 join
,这将提高性能,因为它不需要使用 where exists
。
用户中超过 300,000 条记录的示例 table:
解释扫描 301119+1+1
行并获取 575ms
的第一个查询 whereHas
:
解释第二个查询 join
,它扫描 3+1
行并获取 10.1ms
:
2) 要包含 未被 指定 user ($id)
阻止的用户,您可以使用 whereDoesntHave
闭包,如下所示:
public function scopeNotBlockedUsers($query, $id)
{
return $query->whereDoesntHave('blockedByUsers', function($users) use ($id){
$users->where('ignore_lists.user_id', $id);
});
}
我更喜欢在这里使用 whereDoesntHave
而不是 leftJoin
。因为当你像下面这样使用 leftjoin
时:
User::leftjoin('ignore_lists', function($q) use ($id) {
$q->on('ignore_lists.blocked_user_id', '=', 'users.id')
->where('ignore_lists.user_id', $id);
})->whereNull('ignore_lists.id')->select('users.*')->distinct()->get();
Mysql 需要创建一个临时 table 来存储 所有用户的记录 并合并一些 ignore_lists
。然后扫描这些记录并找出没有ignore_lists
的记录。 whereDosentHave
也会扫描 所有用户 。对于我的 mysql 服务器,where not exists
比 left join
快一点。它的执行计划看起来不错。这两个查询的性能差别不大。
因为 whereDoesntHave
更具可读性。我会选择whereDoesntHave
。
3) 要包括 阻止了指定的 user ($id)
的用户,要使用 whereHas
blockedUsers 像这样:
public function scopeWhoBlocked($query, $id)
{
return $query->whereHas('blockedUsers', function($q) use ($id) {
$q->where('ignore_lists.blocked_user_id', $id);
});
}
// better performance: however, when you apply another where condition, you need to specify the table name ->where('users.verified', 1)
public function scopeWhoBlocked($query, $id)
{
return $query->join('ignore_lists', function($q) use ($id) {
$q->on('ignore_lists.user_id', '=', 'users.id')
->where('ignore_lists.blocked_user_id', $id);
})->select('users.*')->distinct();
}
4) 要包含 未阻止 指定 user ($id)
的用户,请对 blockedByUsers 使用 whereDoesntHave
:
public function scopeWhoDidNotBlock($query, $id)
{
return $query->whereDoesntHave('blockedUsers', function($q) use ($id) {
$q->where('ignore_lists.blocked_user_id', $id);
});
}
PS: 记得在 foreign_key
上为 ignore_lists
添加索引 table.
您可以使用 Querying Relationship Existence whereHas
and Querying Relationship Absence whereDoesntHave
查询构建器函数来构建结果查询。
我已经包含了每个查询生成的 SQL 代码和查询时间(以毫秒为单位),这些查询时间是在 table 有 1000 个用户的双 Xeon 专用服务器上测试的。
我们不希望在使用areNotBlockedBy
和whoDidNotBlock
查询时在结果中获取当前用户,因此这些函数将排除使用$id
.[=32=的用户]
要包括 被指定用户 (
$id
) 阻止的用户:/** * Scope a query to only include users that are blocked by the specified user. * * @param \Illuminate\Database\Eloquent\Builder $query * @param $id * @return \Illuminate\Database\Eloquent\Builder */ public function scopeAreBlockedBy($query, $id) { return User::whereHas('blockedByUsers', function($q) use($id) { $q->where('user_id', $id); }); }
正在执行:
User::areBlockedBy(auth()->id())->where('verified', 1)->get();
将生成以下内容 SQL:
-- Showing rows 0 - 3 (4 total, Query took 0.0006 seconds.) select * from `users` where exists (select * from `users` as `laravel_reserved_9` inner join `ignore_lists` on `laravel_reserved_9`.`id` = `ignore_lists`.`user_id` where `users`.`id` = `ignore_lists`.`blocked_user_id` and `user_id` = ?) and `verified` = ?
要包括 未 被指定用户 (
$id
) 阻止的用户:/** * Scope a query to only include users that are not blocked by the specified user. * * @param \Illuminate\Database\Eloquent\Builder $query * @param $id * @return \Illuminate\Database\Eloquent\Builder */ public function scopeAreNotBlockedBy($query, $id) { // It will exclude the user with $id return User::where('id', '!=', $id) ->whereDoesntHave('blockedByUsers', function($q) use($id) { $q->where('user_id', $id); }); }
正在执行:
User::areNotBlockedBy(auth()->id())->where('verified', 1)->get();
将生成以下内容 SQL:
-- Showing rows 0 - 24 (990 total, Query took 0.0005 seconds.) select * from `users` where `id` != ? and not exists (select * from `users` as `laravel_reserved_0` inner join `ignore_lists` on `laravel_reserved_0`.`id` = `ignore_lists`.`user_id` where `users`.`id` = `ignore_lists`.`blocked_user_id` and `user_id` = ?) and `verified` = ?
要包括 阻止 指定用户 (
$id
) 的用户:/** * Scope a query to only include users that blocked the specified user. * * @param \Illuminate\Database\Eloquent\Builder $query * @param $id * @return \Illuminate\Database\Eloquent\Builder */ public function scopeWhoBlocked($query, $id) { return User::whereHas('blockedUsers', function($q) use($id) { $q->where('blocked_user_id', $id); }); }
正在执行:
User::whoBlocked(auth()->id())->where('verified', 1)->get();
将生成以下内容 SQL:
-- Showing rows 0 - 1 (2 total, Query took 0.0004 seconds.) select * from `users` where exists (select * from `users` as `laravel_reserved_12` inner join `ignore_lists` on `laravel_reserved_12`.`id` = `ignore_lists`.`blocked_user_id` where `users`.`id` = `ignore_lists`.`user_id` and `blocked_user_id` = ?) and `verified` = ?
要包含 未阻止 指定用户 (
$id
) 的用户:/** * Scope a query to only include users that did not block the specified user. * * @param \Illuminate\Database\Eloquent\Builder $query * @param $id * @return \Illuminate\Database\Eloquent\Builder */ public function scopeWhoDidNotBlock($query, $id) { // It will exclude the user with $id return User::where('id', '!=', $id) ->whereDoesntHave('blockedUsers', function($q) use($id) { $q->where('blocked_user_id', $id); }); }
正在执行:
User::whoDidNotBlock(auth()->id())->where('verified', 1)->get();
将生成以下内容 SQL:
-- Showing rows 0 - 24 (992 total, Query took 0.0004 seconds.) select * from `users` where `id` != ? and not exists (select * from `users` as `laravel_reserved_1` inner join `ignore_lists` on `laravel_reserved_1`.`id` = `ignore_lists`.`blocked_user_id` where `users`.`id` = `ignore_lists`.`user_id` and `blocked_user_id` = ?) and `verified` = ?