Order by sum column relationship in Laravel - laravel

I have this controller which grabs posts from a post table.
Every post in the posts table have the relation "hasMany" with another table likes.
Controller:
public function getDashboard(){
$posts = Post::orderBy('created_at', 'desc')->get();
return view('dashboard', ['posts' => $posts]);
}
I'd like to replace 'created at' with something like:
$post->likes->sum(like)
Don't know how to write the right syntax though.
EDIT:
Here are the tables.
Posts
--id
--body
Likes
--id
--post_id
--like
The column like can have the value 1 or -1.
I'd like to order on the summation of that column for each post.
So a post with one dislike(-1) and one like(1) will have the aggregated value of 0, hence will be placed after a post with one like(1).

You can use withCount() for this as:
Post::withCount('likes')->orderBy('likes_count')->get()
withCount() will place a {relation}_count column on your resulting
models
Update
Post::withCount(['likes' => function($q) {
$q->where('like', 1)
}])
->orderBy('likes_count')
->get()
Update2
You can use sortByDesc() to sort your collection as:
$posts = Post::get();
$posts = $posts->sortByDesc(function ($post) {
return $post->likes->sum('like');
});

If you want to sum column value from relationship and then sort records.
$users = User::addSelect(['likes' => Post::selectRaw('sum(likes) as total_likes')
->whereColumn('user_id', 'useres.id')
->groupBy('user_id')
])
->orderBy('likes', 'DESC')
->get()
->toArray();
Or
use below
$users = User::select("*",
\DB::raw('(SELECT SUM(likes) FROM likes_table WHERE likes_table.user_id = users.id) as tolal_likes'))
->orderBy('likes', 'DESC')
->get()
->toArray();

Related

How do I search by a table included using with() statement

I am trying to search with a column on a connected table using with. According to laravel the following should work.
$lineItems = Invoice::with(['invoiceHeader' => function ($query) {
$query->where('community_id', '=', 1);
}, 'invoiceLineItems'])
->limit(10)
->get()
->toArray();
However, I don't get anything from the invoiceHeader table and I get all the invoices available. If I take out the function I get the same but with invoiceHeader's table values showing up.
$lineItems = Invoice::with(['invoiceHeader', 'invoiceLineItems'])
->limit(10)
->get()
->toArray();
It seems I might be doing something of a right join where I get all the Invoices but then only the invoiceHeader values when applicable to the foreign key.
Edit:
I put ->toSql(); after the limit() and it shows I only get the following.
"select * from `invoice` limit 10"
You should use whereHas mixing with with:
$lineItems = Invoice::with(['invoiceHeader', 'invoiceLineItems'])
->whereHas('invoiceHeader', function ($query) {
return $query->where('community_id', 1);
})
->limit(10)
->get()
->toArray();

Laravel: Use order by in combination with whereHas

I want to retrieve a collection of data, which is ordered by the start_date of the relation
Basically I want to achieve this, with Laravel Models (the code below works perfectly)
$posts = DB::table('posts')
->leftJoin(
'threads',
'posts.id',
'=',
'threads.postable_id'
)
->where('threads.postable_type', '=', 'App\Post')
->orderBy('threads.start_date')
->paginate($request->input('limit', 2));
So in this case, I'm fetching ALL Posts and those are ordered by the start_date of the thread relation.
Those are not my actual tables but this works perfectly!
Because I'm using https://laravel.com/docs/8.x/eloquent-resources this is not the ideal solution to retrieve sorted data.
So instead I want to use the orderBy clause somewhere here
$posts = Post::whereHas('thread', function ($query) {
$query->where('end_date', '>=', Carbon::now());
});
But I just cannot make this work. I've tried this
$posts = Post::whereHas('thread', function ($query) {
$query->where('end_date', '>=', Carbon::now())
->orderBy('start_date');
});
and I also appended this to the actual relation:
public function thread(): MorphOne
{
return $this->morphOne('App\Thread', 'postable')->orderBy('start_date');
}
If you look at your code:
$posts = Post::whereHas('thread', function ($query) {
$query->where('end_date', '>=', Carbon::now())
->orderBy('start_date');
});
the whereHas will only return Post associate with a thread which the function return true.
Try this:
$posts = Post::with('thread')->has('thread')->orderBy('thread.start_date')->get();
This will fetch all Post with Thread only if they have at least one Thread and then orderBy the start_date of the Thread.
You don't have to do the whereHas function because when you call ->with('thread') it'll use you this :
public function thread(): MorphOne
{
return $this->morphOne('App\Thread', 'postable')->orderBy('start_date');
}
whereHas doesnt retrieve the relationship.
If you need even more power, you may use the whereHas and orWhereHas methods to define additional query constraints on your has queries, such as inspecting the content of a comment: Laravel whereHas
Don't do :
$posts = Post::with('thread')->orderBy('thread.start_date');
If there is no thread on some post, post without thread will be fetch with value null for their key thread and you will have an unexpected result when you try to orderBy.
First of all I want to thank Elie Morin for his help but I found out that I definitely need to use joins for that task.
In my example, I wanted to order the main query (posts) by the relation's start_date
Doing what you suggested
$posts = Post::with('thread')->has('thread')->orderBy('thread.start_date')->get();
Would only order the thread by start_date and not the ENTIRE query.
Which is why I came up with something like this:
$posts = Post::has('thread')
->select('posts.id')
->leftJoin(
'thread',
'posts.id',
'=',
'thread.postable_id'
)
->where('thread.postable_type', '=', 'App\Post')
->where('thread.end_date', '>=', Carbon::now())
->orderBy('thread.start_date')
->with('thread');
return PostResource::collection($posts->paginate(2));

Laravel Eloquent subquery and count subquery results

Let's say there's a Post model. It has a hasMany relationship called comments() defined in its model.
I'm trying to:
Get the count of comments
That belongs to a specific set of Posts, defined by an array of ids
Where the comments are posted in a certain date range
I thought the following would work:
$ids = [1, 2, 3, 4];
$results = Post::whereIn('id', $ids)
->with(['comments'])
->whereHas('comments', function($q) {
$q->whereMonth('created_at', Carbon::now()->month);
})
->withCount('comments')
->get();
And then do
$count = array_sum($results->pluck('comments_count')->toArray());
The result of the query does include comments_count so that's working, and $count works as well.
But the date range in the subquery is not applied, it counts all comments. What am I missing?
You will need to pass the same condition/subquery to the withCount method:
->withCount(['comments' => function($q) {
$q->whereMonth('created_at', Carbon::now()->month);
}])
https://laravel.com/api/5.5/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.html#method_withCount

Combinations of filters can be optional laravel eloquent relationships

I want to filter replies based on query parameter passed, query parameters can be post_id, comment_id and array of reply_ids all can be optional and can be used as combination
$query = Post::where('user_id', auth()->id());
if ( isset($data['reply_ids']) ) {
$query = $query->with('posts.comments.replies')->whereHas('posts.comments.replies', function($query) use ($data){
$query->whereIn('id', $data['reply_ids']);
});
}
Now, if I want to filter again with comment authors, that is on comments table, if I want to add filters of post author, that will be on posts table, how I can add those conditional, and still that will be optional?
Just add another if statements below. Those will be optional, sice you define your $query before your first filtering action.
I needed to use another eager loading with so that the query will be build on all the combination selected, so the final code will be something like
$query = Post::where('user_id', auth()->id());
if ( isset($data['reply_ids']) ) {
$query = $query->with('posts.comments.replies')->whereHas('posts.comments.replies', function($query) use ($data){
$query->whereIn('id', $data['reply_ids']);
});
}
$query = $query->with(['posts' => function($query){ $query->where('status', 'pending'); }])
$query->get()

Order by related table column

I have a query like:
$users = User::with('role')
->get();
How can I order the results by the related table, so it's something like:
$users = User::with('role')
->orderBy('role.id', 'DESC')
->get();
Is there a way to do it without joining the role table (since we're already doing with('role')?
what are you trying to order. the list of users or the roles.
if you are trying to sort the users base on role do.
$users = User::with('role')->orderBy('role_id', 'DESC')
->get();
if you are trying to sort the roles of the user then pocho's answer is correct.
$users = User::with(array('role' => function($query)
{
$query->orderBy('id', 'DESC');
}))->get();
From the documentation:
$users = User::with(array('role' => function($query)
{
$query->orderBy('id', 'DESC');
}))->get();
You can also do a raw query using DB::raw like in the examples here.
You can always sort the returned collection quite easily...
$users = User::with('role')
->get()
->sortBy(function($user, $key)
{
return $user->role->id;
});
This is assuming a user hasOne or belongsTo a role. If your relationship is something that can return multiple roles, then it becomes a bit more complex because you need to decide which of the user's roles to sort by.

Resources