简体   繁体   中英

Eloquent: How to sort by sum of column in other table

I have 2 tables: users and comments.

Each comment has a rating. One user can have many comments.

This is my migration

Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('email', 120)->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    ......
});

Schema::create('comments', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->bigInteger('user_id')->unsigned();
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    $table->string('commentable_type');
    $table->bigInteger('commentable_id');
    $table->char('enable', 1)->default(0);
    $table->char('to_stats', 1)->default(0);
    $table->tinyInteger('rating')->default(0);
    ...
});

Comment.php

class Comment extends Model
{
    ...
    public function commentable()
    {
        return $this->morphTo();
    }

    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

User.php

class User extends Authenticatable implements MustVerifyEmail
{
    ...
    public function comments()
    {
        return $this->hasMany('App\Comment');
    }

    public function commentsReceived()
    {
        return $this->hasMany('App\Comment', 'commentable_id', 'id');
    }

    public function scopeOfRoleType($query, $types)
    {
        return $query->whereHas('roles', function ($q) use ($types) {
            $q->whereIn('name', $types);
        });
    }

    public function userRatingCount()
    {
        return $this->hasMany('App\Comment', 'commentable_id', 'id')->where('enable', '=', '1')->where('to_stats', '=', '0');
    }
}

I want to show users list ordered by summary of rating column in comments table (let's say it, votes). Then, I need to display it from the most votes to the smallest.

I try this code, but it is not working:

$users = User::ofRoleType($role)->with('userRatingCount')->sum('comments.rating');

How can I fix this?

You can use subquery to do that.

First, we need to figure out what's the query will looks like.

select `users`.*,
       (
           select sum(rating)
           from `comments`
           where `user_id` = `users`.`id`
       ) as `comments_rating`
from `users`
order by `comments_rating` desc

Then, we just need to convert that query to Eloquent.

$commentsRating = Comment::selectRaw('sum(rating)')
    ->whereColumn('user_id', 'users.id')
    ->getQuery();

$users = User::select('users.*')
    ->selectSub($commentsRating, 'comments_rating')
    ->orderBy('comments_rating', 'DESC')
    ->get();

Last but not least, I'll show you the cool thing. We can move that query, to scopes. So, we can load that query whenever we want.

class User extends Model
{
    public function scopeWithCommentsRating($query)
    {
        $commentsRating = Comment::selectRaw('sum(rating)')
            ->whereColumn('user_id', 'users.id')
            ->getQuery();

        $base = $query->getQuery();
        if (is_null($base->columns)) {
            $query->select([$base->from.'.*']);
        }

        return $query->selectSub($commentsRating, 'comments_rating');
    }
}

Then,

$users = User::withCommentsRating()->orderBy('comments_rating', 'DESC')->get();

PS . I don't know what's the purpose of userRatingCount relation.

User::ofRoleType($role)->with('comments')->sortBy(function ($user) {
    return $user->comments->sum('rating');
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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