Reputation: 81
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?
Upvotes: 4
Views: 1314
Reputation: 2903
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.
Upvotes: 4
Reputation: 881
User::ofRoleType($role)->with('comments')->sortBy(function ($user) {
return $user->comments->sum('rating');
});
Upvotes: -2