简体   繁体   English

Laravel 多对多(在同一个用户表/模型上):查询范围以包含指定用户的相关信息

[英]Laravel Many-to-Many (on the same users table/Model): Query scopes to include related for the specified user

Users can block each other.用户可以互相屏蔽。 One user can block many (other) users, and one user can be blocked by many (other) users.一个用户可以阻止多个(其他)用户,一个用户可以被多个(其他)用户阻止。 In User model I have these many-to-many relationships: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 is the pivot table and it has id , user_id , 'blocked_user_id' columns) ignore_lists是数据透视表,它有iduser_id'blocked_user_id'列)

I want to create the following Query Scopes :我想创建以下查询范围

1) To include users that are blocked by the specified 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? :)
}

Example of usage: User::areBlockedBy(auth()->id())->where('verified', 1)->get();用法示例: User::areBlockedBy(auth()->id())->where('verified', 1)->get();

2) To include users that are not blocked by the specified user ( $id ): 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? :)
}

Example of usage: User::areNotBlockedBy(auth()->id())->where('verified', 1)->get();用法示例: User::areNotBlockedBy(auth()->id())->where('verified', 1)->get();

3) To include users that blocked the specified user ( $id ): 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? :)
}

Example of usage: User::whoBlocked(auth()->id())->where('verified', 1)->get();用法示例: User::whoBlocked(auth()->id())->where('verified', 1)->get();

4) To include users that did not block the specified user ( $id ): 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? :)
}

Example of usage: User::whoDidNotBlock(auth()->id())->where('verified', 1)->get();用法示例: User::whoDidNotBlock(auth()->id())->where('verified', 1)->get();


How would you do this?你会怎么做? I didn't find anything in the Laravel docs about this (maybe I missed it).我在Laravel 文档中没有找到任何关于此的内容(也许我错过了)。 (I'm using Laravel 6.x ) (我正在使用Laravel 6.x

I'm not sure, but I think this could be done in two ways: Using Left Join or using raw queries in whereIn ... I may be wrong, but I think the "left join" solution would be better as far as performance is concerned, right?我不确定,但我认为这可以通过两种方式完成:使用左连接或在whereIn 中使用原始查询......我可能错了,但我认为“左连接”解决方案在性能方面会更好是担心吧? (not sure about this, maybe I'm totally wrong). (不确定这一点,也许我完全错了)。

Use join(inner join) performance is better than whereIn subquery.使用join(inner join)性能优于whereIn子查询。

In MySQL, subselects within the IN clause are re-executed for every row in the outer query, thus creating O(n^2) .在 MySQL 中,对于外部查询中的每一行,都会重新执行 IN 子句中的子选择,从而创建O(n^2)

I think use whereHas and whereDoesntHave for query will be more readable.我认为使用whereHaswhereDoesntHave进行查询会更具可读性。

1) The relationship method blockedUsers() has already include users that are blocked by the specified user ($id) , you can use this method directly: 1)关系方法blockedUsers()已经包含了指定user ($id)屏蔽user ($id) ,可以直接使用这个方法:

User::where('id', $id)->first()->blockedUsers();

Considerate about applying the where('verified', 1) at first, so you can use query like User::where('verified', 1)->areBlockedBy(auth()->id()) , the scope can be like this:首先考虑应用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();
}

We use join for the second query that will improve the performance because it doesn't need to use where exists .我们对第二个查询使用join来提高性能,因为它不需要使用where exists

Example for 300,000+ records in users table:用户表中超过 300,000 条记录的示例:

Explain the first query whereHas which scan 301119+1+1 rows and takes 575ms :解释第一个查询whereHas扫描301119+1+1行并花费575ms 哪里有说明 哪里有时间

Explain the second query join which scan 3+1 rows and takes 10.1ms :解释扫描3+1行并花费10.1ms的第二个查询join 加盟说明 加入时间

2) To include users that are not blocked by the specified user ($id) , you can use whereDoesntHave closure like this one: 2) 要包含未被指定user ($id)阻止user ($id) ,您可以像这样使用whereDoesntHave闭包:

public function scopeNotBlockedUsers($query, $id)
{
    return $query->whereDoesntHave('blockedByUsers', function($users) use ($id){
           $users->where('ignore_lists.user_id', $id);
     });
}

I prefer to use whereDoesntHave instead of leftJoin here.我更喜欢在这里使用whereDoesntHave而不是leftJoin Because when you use leftjoin like this below:因为当你像下面这样使用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 need to create an temporary table for storing all the users' records and combine some ignore_lists .And then scan these records and find out the records which without ignore_lists . Mysql需要创建一个临时表来存储所有用户的记录,并结合一些ignore_lists 。然后扫描这些记录,找出没有ignore_lists的记录。 whereDosentHave will scan all users too. whereDosentHave也会扫描所有用户 For my mysql server, where not exists is a little faster than left join .对于我的 mysql 服务器, where not existsleft join快一点。 Its execution plan seems good.它的执行计划似乎不错。 The performance of these two queries are not much different.这两个查询的性能差别不大。 哪里有解释 左连接为空

For whereDoesntHave is more readable.对于whereDoesntHave更具可读性。 I will choose whereDoesntHave .我会选择whereDoesntHave whereDoesntHave 和 leftjoin

3) To include users that blocked the specified user ($id) , to use whereHas blockedUsers like this: 3) 要包含阻止指定user ($id) ,请使用whereHas disabledUsers 像这样:

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) To include users that did not block the specified user ($id) , use whereDoesntHave for blockedByUsers: 4) 要包含没有阻止指定user ($id) ,请使用whereDoesntHave为 blocksByUsers:

public function scopeWhoDidNotBlock($query, $id)
{
    return $query->whereDoesntHave('blockedUsers', function($q) use ($id) {
                $q->where('ignore_lists.blocked_user_id', $id);
           });
}

PS: Remember to add index on foreign_key for ignore_lists table. PS:记得为ignore_lists表在foreign_key上添加索引。

You can use Querying Relationship Existence whereHas and Querying Relationship Absence whereDoesntHave query builder functions to build your result queries.您可以使用查询关系存在whereHas查询关系缺失whereDoesntHave查询构建器函数来构建结果查询。

I have included each query generated SQL code and query time in milliseconds tested on a dual Xeon dedicated server on a table that has 1000 users.我已经包含了每个查询生成的 SQL 代码和查询时间(以毫秒为单位),这些查询时间是在具有 1000 个用户的表上的双 Xeon 专用服务器上测试的。

We don't want to get current user in the results when querying with areNotBlockedBy and whoDidNotBlock , so these functions will exclude the user with $id .当使用areNotBlockedBywhoDidNotBlock查询时,我们不想在结果中获取当前用户,因此这些函数将排除具有$id的用户。

  1. To include users that are blocked by the specified user ( $id ):包括指定用户(被阻止用户$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); }); }

    Executing:执行:

     User::areBlockedBy(auth()->id())->where('verified', 1)->get();

    Will generate the following SQL:将生成以下 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` = ?
  2. To include users that are not blocked by the specified user ( $id ):要包含未被指定用户 ( $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); }); }

    Executing:执行:

     User::areNotBlockedBy(auth()->id())->where('verified', 1)->get();

    Will generate the following SQL:将生成以下 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` = ?
  3. To include users that blocked the specified user ( $id ):要包括阻止指定用户 ( $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); }); }

    Executing:执行:

     User::whoBlocked(auth()->id())->where('verified', 1)->get();

    Will generate the following SQL:将生成以下 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` = ?
  4. To include users that did not block the specified user ( $id ):要包括未阻止指定用户 ( $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); }); }

    Executing:执行:

     User::whoDidNotBlock(auth()->id())->where('verified', 1)->get();

    Will generate the following SQL:将生成以下 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` = ?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM